From 68347f161c325ae57fc9501e15b7e7e20880deab Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Thu, 16 Apr 2026 18:54:50 -0700 Subject: [PATCH 01/23] citation and mention support --- ...ne_mentions_and_citations_58e9f474.plan.md | 137 +++++++++ .node-version | 1 + ReactNativeEnrichedMarkdown.podspec | 11 +- .../enriched/markdown/EnrichedMarkdown.kt | 14 + .../markdown/EnrichedMarkdownInternalText.kt | 11 +- .../markdown/EnrichedMarkdownManager.kt | 10 + .../enriched/markdown/EnrichedMarkdownText.kt | 24 +- .../markdown/events/CitationPressEvent.kt | 25 ++ .../markdown/events/MentionPressEvent.kt | 25 ++ .../markdown/renderer/LinkRenderer.kt | 94 +++++++ .../markdown/renderer/SpanStyleCache.kt | 5 + .../enriched/markdown/spans/CitationSpan.kt | 129 +++++++++ .../enriched/markdown/spans/MentionSpan.kt | 138 +++++++++ .../enriched/markdown/styles/CitationStyle.kt | 41 +++ .../enriched/markdown/styles/MentionStyle.kt | 50 ++++ .../enriched/markdown/styles/StyleConfig.kt | 32 ++- .../utils/common/MarkdownViewManagerUtils.kt | 28 ++ .../markdown/utils/text/view/LinkEvents.kt | 22 ++ .../text/view/LinkLongPressMovementMethod.kt | 88 ++++++ .../markdown/views/TableContainerView.kt | 6 + apps/example/Gemfile | 2 + apps/example/Gemfile.lock | 26 +- apps/example/ios/Podfile.lock | 10 +- apps/example/src/sampleMarkdown.ts | 22 +- ios/EnrichedMarkdown.mm | 51 +++- ios/EnrichedMarkdownText.mm | 23 +- ios/attachments/ENRMCitationAttachment.h | 25 ++ ios/attachments/ENRMCitationAttachment.m | 145 ++++++++++ ios/attachments/ENRMMentionAttachment.h | 28 ++ ios/attachments/ENRMMentionAttachment.m | 115 ++++++++ ios/renderer/LinkRenderer.h | 10 + ios/renderer/LinkRenderer.m | 139 ++++++++- ios/renderer/RenderContext.h | 8 + ios/renderer/RenderContext.m | 30 ++ ios/styles/StyleConfig.h | 41 +++ ios/styles/StyleConfig.mm | 266 ++++++++++++++++++ ios/utils/LinkTapUtils.h | 13 +- ios/utils/LinkTapUtils.m | 47 +++- ios/utils/StylePropsUtils.h | 137 +++++++++ ios/views/TableContainerView.h | 4 + ios/views/TableContainerView.m | 18 +- src/EnrichedMarkdownNativeComponent.ts | 45 +++ src/EnrichedMarkdownTextNativeComponent.ts | 45 +++ src/index.tsx | 2 + src/index.web.tsx | 2 + src/native/EnrichedMarkdownText.tsx | 30 +- src/normalizeMarkdownStyle.ts | 23 ++ src/normalizeMarkdownStyle.web.ts | 23 ++ src/types/MarkdownStyle.ts | 50 ++++ src/types/MarkdownStyleInternal.ts | 27 ++ src/types/MarkdownTextProps.ts | 16 ++ src/types/MarkdownTextProps.web.ts | 14 + src/types/events.ts | 10 + src/web/EnrichedMarkdownText.tsx | 18 +- src/web/renderers/InlineRenderers.tsx | 105 ++++++- src/web/styles.ts | 53 ++++ src/web/types.ts | 4 + 57 files changed, 2451 insertions(+), 67 deletions(-) create mode 100644 .cursor/plans/inline_mentions_and_citations_58e9f474.plan.md create mode 100644 .node-version create mode 100644 android/src/main/java/com/swmansion/enriched/markdown/events/CitationPressEvent.kt create mode 100644 android/src/main/java/com/swmansion/enriched/markdown/events/MentionPressEvent.kt create mode 100644 android/src/main/java/com/swmansion/enriched/markdown/spans/CitationSpan.kt create mode 100644 android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt create mode 100644 android/src/main/java/com/swmansion/enriched/markdown/styles/CitationStyle.kt create mode 100644 android/src/main/java/com/swmansion/enriched/markdown/styles/MentionStyle.kt create mode 100644 ios/attachments/ENRMCitationAttachment.h create mode 100644 ios/attachments/ENRMCitationAttachment.m create mode 100644 ios/attachments/ENRMMentionAttachment.h create mode 100644 ios/attachments/ENRMMentionAttachment.m diff --git a/.cursor/plans/inline_mentions_and_citations_58e9f474.plan.md b/.cursor/plans/inline_mentions_and_citations_58e9f474.plan.md new file mode 100644 index 00000000..ba07a841 --- /dev/null +++ b/.cursor/plans/inline_mentions_and_citations_58e9f474.plan.md @@ -0,0 +1,137 @@ +--- +name: Inline Mentions and Citations +overview: Add support for inline mentions (`mention://`) and inline citations (`citation://`) to `EnrichedMarkdownText`, distinguished from regular links by URL scheme. Mentions render as pill attachments, citations render as superscript-styled inline text, and each fires its own JS press event. User-configurable styling is exposed for both. +todos: + - id: types + content: Extend MarkdownStyle + internal codegen types with mention/citation styles and press events; normalize defaults in normalizeMarkdownStyle. + status: completed + - id: js-surface + content: Add onMentionPress/onCitationPress props and handlers in EnrichedMarkdownText.tsx; re-export new types from src/index.tsx. Re-run codegen so generated Props/EventEmitters headers on iOS and JNI specs on Android pick up the new fields. + status: completed + - id: ios-style + content: Add mention/citation fields to StyleConfig (ios/styles) and wire through StylePropsUtils. + status: completed + - id: ios-render + content: "Branch LinkRenderer on scheme: implement ENRMMentionAttachment (UIView provider) for mentions, baseline-offset + smaller-font attributes for citations, tag ranges with new attribute keys." + status: completed + - id: ios-events + content: Update LinkTapUtils + EnrichedMarkdownText.mm / EnrichedMarkdown.mm to dispatch onMentionPress / onCitationPress based on the tapped attribute. + status: completed + - id: android-style + content: Add MentionStyle/CitationStyle data classes and prop converters in EnrichedMarkdownTextManager (and GFM manager). + status: completed + - id: android-spans + content: Implement MentionSpan (ReplacementSpan drawing rounded pill; getSize accounts for paddingHorizontal + borderWidth) and a custom CitationSpan subclassing SuperscriptSpan that accepts explicit baselineOffsetPx for cross-OEM parity with iOS. + status: completed + - id: android-render + content: Branch LinkRenderer.kt on URL scheme to install MentionSpan / CitationSpan / existing LinkSpan. + status: completed + - id: android-events + content: Add MentionPressEvent/CitationPressEvent; update EnrichedMarkdownText.kt tap path to emit the right event for the span under the tap. + status: completed + - id: web-render + content: Web renderer — branch LinkRenderer on URL scheme; render mention as styled inline-flex `` pill (CSS `:active` honors pressedOpacity) and citation as `` with fontSizeMultiplier/baselineOffsetPx/color. Wire onMentionPress/onCitationPress into RendererCallbacks and MarkdownTextProps.web. Extend styles.ts to derive mention/citation CSSProperties from the normalized style. + status: pending +isProject: false +--- + +## Approach + +Keep the standard CommonMark link syntax `[text](url)`. Dispatch at the renderer layer based on the URL scheme: + +- `mention://` → inline pill attachment (carries `userId`) +- `citation://` → superscript/smaller-font inline marker (carries the underlying `url`) +- anything else → existing link behavior (unchanged) + +No md4c or AST changes. Scope is **read-only renderer only** (`EnrichedMarkdownText` + the GFM-flavored `EnrichedMarkdownNativeComponent`); the input editor is unchanged. Press events are delivered through new `onMentionPress` / `onCitationPress` callbacks; existing `onLinkPress` stays unchanged for other schemes. No tooltips/popovers in this PR. + +## Public API + +New JS types and props (both native components: `EnrichedMarkdownTextNativeComponent` and `EnrichedMarkdownNativeComponent`): + +```ts +interface MentionPressEvent { userId: string; text: string; } +interface CitationPressEvent { url: string; text: string; } + +interface MentionStyle { + color?: string; + backgroundColor?: string; + borderColor?: string; + borderWidth?: number; + borderRadius?: number; + paddingHorizontal?: number; + paddingVertical?: number; + fontFamily?: string; + fontWeight?: string; + fontSize?: number; + pressedOpacity?: number; // native tap feedback, default 0.6 +} + +interface CitationStyle { + color?: string; + fontSizeMultiplier?: number; // default 0.7 + baselineOffsetPx?: number; // explicit px; default derived from font metrics for iOS/Android parity + fontWeight?: string; + underline?: boolean; + backgroundColor?: string; +} +``` + +Added to [`src/types/MarkdownStyle.ts`](src/types/MarkdownStyle.ts) as `mention?: MentionStyle; citation?: CitationStyle;`, mirrored as internal shapes in [`src/EnrichedMarkdownTextNativeComponent.ts`](src/EnrichedMarkdownTextNativeComponent.ts) and [`src/EnrichedMarkdownNativeComponent.ts`](src/EnrichedMarkdownNativeComponent.ts), defaulted in [`src/normalizeMarkdownStyle.ts`](src/normalizeMarkdownStyle.ts), and re-exported from [`src/index.tsx`](src/index.tsx). + +## Dispatch flow + +```mermaid +flowchart TD + md["[text](url) from md4c"] --> LinkRenderer + LinkRenderer -->|"scheme == mention://"| MentionPath[Mention pill + MentionAttr] + LinkRenderer -->|"scheme == citation://"| CitationPath[Baseline/size attrs + CitationAttr] + LinkRenderer -->|"other"| LegacyLink[Existing link span/attr] + MentionPath --> TapDispatch + CitationPath --> TapDispatch + LegacyLink --> TapDispatch + TapDispatch -->|"mention"| onMentionPress + TapDispatch -->|"citation"| onCitationPress + TapDispatch -->|"link"| onLinkPress +``` + +## iOS + +- Extend [`ios/styles/StyleConfig.h`](ios/styles/StyleConfig.h)/`.mm` with getters/setters for the new `mention*` and `citation*` fields, wired from `StylePropsUtils`. +- Refactor [`ios/renderer/LinkRenderer.m`](ios/renderer/LinkRenderer.m): after child rendering, inspect the URL prefix and branch: + - `mention://`: replace the text range with an `NSAttributedString` containing an `NSAttachmentCharacter` backed by a new `ENRMMentionAttachment` (subclass of `NSTextAttachment`) providing an `NSTextAttachmentViewProvider` that renders a rounded, padded `UILabel`/`UIView` pill via Auto Layout. Tag the range with new attributes `ENRMMentionUserId` + `ENRMMentionText`. The podspec uses `min_ios_version_supported` (≥ iOS 15.1 on current RN), so no pre-iOS-15 `drawInRect:` fallback is needed — commit to the view-provider path only. + - `citation://`: keep text, apply `NSBaselineOffsetAttributeName` (explicit px from `baselineOffsetPx`, or derived from current font metrics) + a smaller font derived from the current font × `fontSizeMultiplier`, optional `NSBackgroundColorAttributeName`. Tag range with `ENRMCitationUrl` + `ENRMCitationText`. + - default: unchanged path (`NSLinkAttributeName` + `linkURL` + existing underline/color). +- `ENRMMentionAttachment`'s view provider installs a `UITapGestureRecognizer` and a `touchesBegan`/`touchesCancelled` animator that applies `mention.pressedOpacity` on press-in and restores on press-out, matching the native "shrink/fade on tap" expectation. +- Update [`ios/utils/LinkTapUtils.m`](ios/utils/LinkTapUtils.m) to also read `ENRMMentionUserId` and `ENRMCitationUrl` when determining the tapped element type, and `isPointOnInteractiveElement` to treat mention/citation as interactive. +- In [`ios/EnrichedMarkdownText.mm`](ios/EnrichedMarkdownText.mm) tap handler, fire one of three event emitters based on which attribute is present (`onMentionPress` / `onCitationPress` / existing `onLinkPress`). Same treatment in `EnrichedMarkdown.mm` (GFM flavor). + +## Android + +- Add `MentionStyle.kt` / `CitationStyle.kt` data classes alongside existing style configs, wired through the prop converters in [`EnrichedMarkdownTextManager.kt`](android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt). +- Add `MentionSpan.kt` (extends `ReplacementSpan`) that overrides `getSize` and `draw` to paint the rounded background, optional border, padding, and the name text. `getSize` must explicitly add `2 * paddingHorizontal + 2 * borderWidth` to the measured text width so the pill doesn't clip, and return a descent/ascent that accounts for `paddingVertical` + `borderWidth`. Holds `userId` + display `text`. Applies `pressedOpacity` to the `Paint` alpha while `isPressed` is true; pressed state is toggled by the link tap dispatcher below. +- Add `CitationSpan.kt` — a custom `SuperscriptSpan` subclass that accepts an explicit `baselineOffsetPx` in `updateDrawState` / `updateMeasureState`, combined with `RelativeSizeSpan(citationStyle.fontSizeMultiplier)` and optional `ForegroundColorSpan` / `BackgroundColorSpan` applied in the same `setSpan` call. The explicit baseline offset gives exact parity with iOS's `NSBaselineOffsetAttributeName` and sidesteps OEM-dependent quirks in the framework's default `SuperscriptSpan`. Holds `url` + `text`. +- Update [`android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt`](android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt) to branch on `url.startsWith("mention://")` / `"citation://"` and install the appropriate span instead of `LinkSpan`. Legacy `LinkSpan` is untouched. +- Event wiring: in [`EnrichedMarkdownText.kt`](android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt) tap path (currently dispatching `LinkPressEvent`), use the span type at the tap index to dispatch `MentionPressEvent` / `CitationPressEvent` instead. Add new event classes under [`events/`](android/src/main/java/com/swmansion/enriched/markdown/events/) mirroring `LinkPressEvent`. On `ACTION_DOWN` over a `MentionSpan`, toggle the span's `isPressed` flag and invalidate the view; reset on `ACTION_UP`/`ACTION_CANCEL`. + +## Web + +Same scheme-based branching happens in [`src/web/renderers/InlineRenderers.tsx`](src/web/renderers/InlineRenderers.tsx)'s `LinkRenderer`: + +- `mention://` → `` styled as an inline-flex pill using `styles.mention` (backgroundColor, borderColor/Width, borderRadius, padding, color, font). `pressedOpacity` maps to a CSS `:active { opacity: ... }` rule injected once at mount (via a style tag keyed by a className, similar to how existing web renderers handle hover states) — CSS does the tap-feedback automatically, no JS state needed. +- `citation://` → `` with `styles.citation` (color, `fontSize: calc(1em * fontSizeMultiplier)`, `verticalAlign: baseline` + `top: -baselineOffsetPx`, optional background, optional underline via `textDecoration`). Kept in a `` tag so screen readers announce it as superscript. +- default: unchanged `` path. + +Supporting updates: + +- [`src/web/types.ts`](src/web/types.ts): extend `RendererCallbacks` with `onMentionPress` / `onCitationPress`. +- [`src/types/MarkdownTextProps.web.ts`](src/types/MarkdownTextProps.web.ts): add the two new callbacks alongside the existing `onLinkPress` / `onLinkLongPress`, documented `@platform ios, android, web`. +- [`src/web/EnrichedMarkdownText.tsx`](src/web/EnrichedMarkdownText.tsx): thread the new callbacks into the render context. +- [`src/web/styles.ts`](src/web/styles.ts): add `mention` and `citation` entries to the `Styles` map, normalized from `MarkdownStyleInternal.mention` / `.citation`. +- [`src/index.web.tsx`](src/index.web.tsx): re-export `MentionPressEvent` / `CitationPressEvent` / `MentionStyle` / `CitationStyle`. + +## Things explicitly out of scope + +- Parser/AST changes (still pure md4c). +- Editor support (`EnrichedMarkdownInput`) — no `insertMention`/`insertCitation` yet. +- Built-in popover/tooltip UI on any platform. \ No newline at end of file diff --git a/.node-version b/.node-version new file mode 100644 index 00000000..a1954016 --- /dev/null +++ b/.node-version @@ -0,0 +1 @@ +24.0.2 diff --git a/ReactNativeEnrichedMarkdown.podspec b/ReactNativeEnrichedMarkdown.podspec index e191da8a..86bcc2af 100644 --- a/ReactNativeEnrichedMarkdown.podspec +++ b/ReactNativeEnrichedMarkdown.podspec @@ -25,8 +25,17 @@ Pod::Spec.new do |s| s.dependency 'iosMath', '~> 0.9' end + # Quoted imports like #import "Foo.h" do not search subdirs recursively; list every + # ios folder that contains headers so renderer/ utils/ attachments/ etc. cross-imports resolve. + ios_header_paths = %w[ + ios ios/attachments ios/input ios/input/internals ios/input/styles ios/internals ios/parser + ios/renderer ios/styles ios/utils ios/views + ].map { |p| "\"$(PODS_TARGET_SRCROOT)/#{p}\"" }.join(' ') + s.pod_target_xcconfig = { - 'HEADER_SEARCH_PATHS' => '"$(PODS_TARGET_SRCROOT)/cpp/md4c" "$(PODS_TARGET_SRCROOT)/cpp/parser" "$(PODS_TARGET_SRCROOT)/ios/internals" "$(PODS_TARGET_SRCROOT)/ios/input/internals"', + 'HEADER_SEARCH_PATHS' => "\"$(PODS_TARGET_SRCROOT)/cpp/md4c\" \"$(PODS_TARGET_SRCROOT)/cpp/parser\" #{ios_header_paths}", + # React / SwiftUI modules use framework-style modules; our ObjC uses plain quoted includes. + 'CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULES' => 'YES', 'GCC_PREPROCESSOR_DEFINITIONS' => preprocessor_defs, 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17' } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt index dceb56a7..d3732198 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt @@ -57,6 +57,8 @@ class EnrichedMarkdown private var onLinkPressCallback: ((String) -> Unit)? = null private var onLinkLongPressCallback: ((String) -> Unit)? = null + private var onMentionPressCallback: ((String, String) -> Unit)? = null + private var onCitationPressCallback: ((String, String) -> Unit)? = null private var onTaskListItemPressCallback: ((Int, Boolean, String) -> Unit)? = null private var contextMenuItemTexts: List = emptyList() var onContextMenuItemPressCallback: ((itemText: String, selectedText: String, selectionStart: Int, selectionEnd: Int) -> Unit)? = null @@ -136,6 +138,14 @@ class EnrichedMarkdown onLinkLongPressCallback = callback } + fun setOnMentionPressCallback(callback: ((userId: String, text: String) -> Unit)?) { + onMentionPressCallback = callback + } + + fun setOnCitationPressCallback(callback: ((url: String, text: String) -> Unit)?) { + onCitationPressCallback = callback + } + fun setOnTaskListItemPressCallback(callback: ((taskIndex: Int, checked: Boolean, itemText: String) -> Unit)?) { onTaskListItemPressCallback = callback } @@ -224,6 +234,8 @@ class EnrichedMarkdown justificationMode = android.text.Layout.JUSTIFICATION_MODE_INTER_WORD } lastElementMarginBottom = segment.lastElementMarginBottom + onMentionPressCallback = this@EnrichedMarkdown.onMentionPressCallback + onCitationPressCallback = this@EnrichedMarkdown.onCitationPressCallback applyStyledText(segment.styledText) segment.imageSpans.forEach { it.registerTextView(this) } @@ -244,6 +256,8 @@ class EnrichedMarkdown maxFontSizeMultiplier = this@EnrichedMarkdown.maxFontSizeMultiplier onLinkPress = onLinkPressCallback onLinkLongPress = onLinkLongPressCallback + onMentionPress = onMentionPressCallback + onCitationPress = onCitationPressCallback applyTableNode(segment.node) } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt index 75516569..f8291343 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt @@ -38,6 +38,9 @@ class EnrichedMarkdownInternalText checkboxTouchHelper.onCheckboxTap = value } + var onMentionPressCallback: ((userId: String, text: String) -> Unit)? = null + var onCitationPressCallback: ((url: String, text: String) -> Unit)? = null + override val segmentMarginBottom: Int get() = lastElementMarginBottom.toInt() override var spoilerOverlayDrawer: SpoilerOverlayDrawer? = null @@ -61,9 +64,11 @@ class EnrichedMarkdownInternalText fun applyStyledText(styledText: CharSequence) { text = styledText - if (movementMethod !is LinkLongPressMovementMethod) { - movementMethod = LinkLongPressMovementMethod.createInstance() - } + val method = + (movementMethod as? LinkLongPressMovementMethod) + ?: LinkLongPressMovementMethod.createInstance().also { movementMethod = it } + method.onMentionTap = { userId, mentionText -> onMentionPressCallback?.invoke(userId, mentionText) } + method.onCitationTap = { url, citationText -> onCitationPressCallback?.invoke(url, citationText) } spoilerOverlayDrawer = SpoilerOverlayDrawer.setupIfNeeded(this, styledText, spoilerOverlayDrawer, spoilerOverlay) accessibilityHelper.invalidateAccessibilityItems() diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt index 89a31534..b470babf 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt @@ -12,9 +12,11 @@ import com.facebook.react.viewmanagers.EnrichedMarkdownManagerDelegate import com.facebook.react.viewmanagers.EnrichedMarkdownManagerInterface import com.facebook.yoga.YogaMeasureMode import com.swmansion.enriched.markdown.spoiler.SpoilerOverlay +import com.swmansion.enriched.markdown.utils.common.emitCitationPress import com.swmansion.enriched.markdown.utils.common.emitContextMenuItemPress import com.swmansion.enriched.markdown.utils.common.emitLinkLongPress import com.swmansion.enriched.markdown.utils.common.emitLinkPress +import com.swmansion.enriched.markdown.utils.common.emitMentionPress import com.swmansion.enriched.markdown.utils.common.emitTaskListItemPress import com.swmansion.enriched.markdown.utils.common.markdownEventTypeConstants import com.swmansion.enriched.markdown.utils.common.parseContextMenuItems @@ -54,6 +56,14 @@ class EnrichedMarkdownManager : emitLinkLongPress(view, url) } + view?.setOnMentionPressCallback { userId, text -> + emitMentionPress(view, userId, text) + } + + view?.setOnCitationPressCallback { url, text -> + emitCitationPress(view, url, text) + } + view?.setOnTaskListItemPressCallback { taskIndex, checked, itemText -> val newChecked = !checked val updatedMarkdown = TaskListToggleUtils.toggleAtIndex(view.currentMarkdown, taskIndex, newChecked) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt index aa9614e4..f716cf6c 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt @@ -26,8 +26,10 @@ import com.swmansion.enriched.markdown.utils.text.view.applySelectableState import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionModeCallback +import com.swmansion.enriched.markdown.utils.text.view.emitCitationPressEvent import com.swmansion.enriched.markdown.utils.text.view.emitLinkLongPressEvent import com.swmansion.enriched.markdown.utils.text.view.emitLinkPressEvent +import com.swmansion.enriched.markdown.utils.text.view.emitMentionPressEvent import com.swmansion.enriched.markdown.utils.text.view.setupAsMarkdownTextView import java.util.concurrent.Executors @@ -228,9 +230,11 @@ class EnrichedMarkdownText text = styledText - if (movementMethod !is LinkLongPressMovementMethod) { - movementMethod = LinkLongPressMovementMethod.createInstance() - } + val method = + (movementMethod as? LinkLongPressMovementMethod) + ?: LinkLongPressMovementMethod.createInstance().also { movementMethod = it } + method.onMentionTap = { userId, mentionText -> emitOnMentionPress(userId, mentionText) } + method.onCitationTap = { url, citationText -> emitOnCitationPress(url, citationText) } renderer.getCollectedImageSpans().forEach { span -> span.registerTextView(this) @@ -266,6 +270,20 @@ class EnrichedMarkdownText emitLinkLongPressEvent(url) } + fun emitOnMentionPress( + userId: String, + text: String, + ) { + emitMentionPressEvent(userId, text) + } + + fun emitOnCitationPress( + url: String, + text: String, + ) { + emitCitationPressEvent(url, text) + } + fun setOnLinkPressCallback(callback: (String) -> Unit) { onLinkPressCallback = callback } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/events/CitationPressEvent.kt b/android/src/main/java/com/swmansion/enriched/markdown/events/CitationPressEvent.kt new file mode 100644 index 00000000..b41d4fb8 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/markdown/events/CitationPressEvent.kt @@ -0,0 +1,25 @@ +package com.swmansion.enriched.markdown.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event + +class CitationPressEvent( + surfaceId: Int, + viewId: Int, + private val url: String, + private val text: String, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME + + override fun getEventData(): WritableMap { + val eventData: WritableMap = Arguments.createMap() + eventData.putString("url", url) + eventData.putString("text", text) + return eventData + } + + companion object { + const val EVENT_NAME: String = "onCitationPress" + } +} diff --git a/android/src/main/java/com/swmansion/enriched/markdown/events/MentionPressEvent.kt b/android/src/main/java/com/swmansion/enriched/markdown/events/MentionPressEvent.kt new file mode 100644 index 00000000..e95c5f4f --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/markdown/events/MentionPressEvent.kt @@ -0,0 +1,25 @@ +package com.swmansion.enriched.markdown.events + +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.WritableMap +import com.facebook.react.uimanager.events.Event + +class MentionPressEvent( + surfaceId: Int, + viewId: Int, + private val userId: String, + private val text: String, +) : Event(surfaceId, viewId) { + override fun getEventName(): String = EVENT_NAME + + override fun getEventData(): WritableMap { + val eventData: WritableMap = Arguments.createMap() + eventData.putString("userId", userId) + eventData.putString("text", text) + return eventData + } + + companion object { + const val EVENT_NAME: String = "onMentionPress" + } +} diff --git a/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt b/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt index 7beb976e..9164e2e2 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt @@ -2,12 +2,19 @@ package com.swmansion.enriched.markdown.renderer import android.text.SpannableStringBuilder import com.swmansion.enriched.markdown.parser.MarkdownASTNode +import com.swmansion.enriched.markdown.spans.CitationSpan import com.swmansion.enriched.markdown.spans.LinkSpan +import com.swmansion.enriched.markdown.spans.MentionSpan import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE class LinkRenderer( private val config: RendererConfig, ) : NodeRenderer { + companion object { + private const val MENTION_SCHEME = "mention://" + private const val CITATION_SCHEME = "citation://" + } + override fun render( node: MarkdownASTNode, builder: SpannableStringBuilder, @@ -17,6 +24,43 @@ class LinkRenderer( ) { val url = node.getAttribute("url") ?: return + when { + url.startsWith(MENTION_SCHEME) -> { + renderMention( + url.removePrefix(MENTION_SCHEME), + node, + builder, + onLinkPress, + onLinkLongPress, + factory, + ) + } + + url.startsWith(CITATION_SCHEME) -> { + renderCitation( + url.removePrefix(CITATION_SCHEME), + node, + builder, + onLinkPress, + onLinkLongPress, + factory, + ) + } + + else -> { + renderLink(url, node, builder, onLinkPress, onLinkLongPress, factory) + } + } + } + + private fun renderLink( + url: String, + node: MarkdownASTNode, + builder: SpannableStringBuilder, + onLinkPress: ((String) -> Unit)?, + onLinkLongPress: ((String) -> Unit)?, + factory: RendererFactory, + ) { factory.renderWithSpan(builder, { factory.renderChildren(node, builder, onLinkPress, onLinkLongPress) }) { start, end, blockStyle -> builder.setSpan( LinkSpan(url, onLinkPress, onLinkLongPress, factory.styleCache, blockStyle, factory.context), @@ -26,4 +70,54 @@ class LinkRenderer( ) } } + + private fun renderMention( + userId: String, + node: MarkdownASTNode, + builder: SpannableStringBuilder, + onLinkPress: ((String) -> Unit)?, + onLinkLongPress: ((String) -> Unit)?, + factory: RendererFactory, + ) { + // Render children into a throwaway buffer to derive the display label. + // Any inline formatting (bold/italic) inside the label collapses to plain + // text because ReplacementSpan paints a single atomic glyph. + val labelBuffer = SpannableStringBuilder() + factory.renderChildren(node, labelBuffer, onLinkPress, onLinkLongPress) + val displayText = labelBuffer.toString() + + // Insert a single placeholder character that ReplacementSpan will paint + // over; keeping it as a real character preserves cursor metrics, selection + // handles, and accessibility traversal. + val start = builder.length + builder.append(' ') + val end = builder.length + + val span = + MentionSpan( + userId = userId, + displayText = displayText, + mentionStyle = factory.styleCache.mentionStyle, + mentionTypeface = factory.styleCache.mentionTypeface, + ) + builder.setSpan(span, start, end, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE) + } + + private fun renderCitation( + url: String, + node: MarkdownASTNode, + builder: SpannableStringBuilder, + onLinkPress: ((String) -> Unit)?, + onLinkLongPress: ((String) -> Unit)?, + factory: RendererFactory, + ) { + val start = builder.length + factory.renderChildren(node, builder, onLinkPress, onLinkLongPress) + val end = builder.length + if (end <= start) return + + val displayText = builder.subSequence(start, end).toString() + val span = CitationSpan(url = url, displayText = displayText, citationStyle = factory.styleCache.citationStyle) + builder.setSpan(span, start, end, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE) + } } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt b/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt index 8379c63a..53095922 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt @@ -1,6 +1,8 @@ package com.swmansion.enriched.markdown.renderer import android.graphics.Typeface +import com.swmansion.enriched.markdown.styles.CitationStyle +import com.swmansion.enriched.markdown.styles.MentionStyle import com.swmansion.enriched.markdown.styles.StyleConfig /** Shared style cache for spans to avoid redundant calculations. */ @@ -27,6 +29,9 @@ class SpanStyleCache( val spoilerParticleDensity: Float = style.spoilerStyle.particleDensity val spoilerParticleSpeed: Float = style.spoilerStyle.particleSpeed val spoilerSolidBorderRadius: Float = style.spoilerStyle.solidBorderRadius + val mentionStyle: MentionStyle = style.mentionStyle + val mentionTypeface: Typeface? = style.mentionTypeface + val citationStyle: CitationStyle = style.citationStyle private fun buildColorsToPreserve(style: StyleConfig): IntArray { val paragraphColor = style.paragraphStyle.color diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/CitationSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/CitationSpan.kt new file mode 100644 index 00000000..b92a4381 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/CitationSpan.kt @@ -0,0 +1,129 @@ +package com.swmansion.enriched.markdown.spans + +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.graphics.Typeface +import android.text.style.ReplacementSpan +import com.swmansion.enriched.markdown.styles.CitationStyle + +/** + * Inline citation marker. Renders atomically (via [ReplacementSpan]) so the + * renderer can apply: + * - font-size multiplier (smaller than surrounding text) + * - explicit baselineOffsetPx (parity with iOS `NSBaselineOffsetAttributeName`) + * - optional padded background (chip look when `backgroundColor` is set) + * + * Padding always contributes to the advance width / line height so adjacent + * text and wrapping behave correctly even when no background is drawn. + */ +class CitationSpan( + val url: String, + val displayText: String, + private val citationStyle: CitationStyle, +) : ReplacementSpan() { + private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL } + private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG) + + private fun configureTextPaint(basePaint: Paint) { + textPaint.set(basePaint) + val multiplier = citationStyle.fontSizeMultiplier + if (multiplier > 0f) { + textPaint.textSize = basePaint.textSize * multiplier + } + textPaint.color = citationStyle.color + textPaint.isUnderlineText = citationStyle.underline + if (citationStyle.fontWeight.isNotEmpty()) { + val base = textPaint.typeface ?: Typeface.DEFAULT + val weightStyle = + when (citationStyle.fontWeight.lowercase()) { + "bold", "700", "800", "900" -> Typeface.BOLD + else -> Typeface.NORMAL + } + textPaint.typeface = Typeface.create(base, weightStyle) + } + } + + private fun textWidth(): Float = textPaint.measureText(displayText) + + override fun getSize( + paint: Paint, + text: CharSequence?, + start: Int, + end: Int, + fm: Paint.FontMetricsInt?, + ): Int { + configureTextPaint(paint) + + val totalWidth = textWidth() + citationStyle.paddingHorizontal * 2f + + if (fm != null) { + // Base metrics come from the surrounding paragraph so the citation + // sits on the same line as the host text. Padding is added to top/bottom + // so a visible background extends past the glyph bounds. + val base = paint.fontMetricsInt + val offset = resolveBaselineOffset() + val verticalInset = citationStyle.paddingVertical.toInt() + fm.ascent = base.ascent - verticalInset - offset.toInt() + fm.top = base.top - verticalInset - offset.toInt() + fm.descent = base.descent + verticalInset + fm.bottom = base.bottom + verticalInset + fm.leading = base.leading + } + + return totalWidth.toInt() + 1 + } + + override fun draw( + canvas: Canvas, + text: CharSequence, + start: Int, + end: Int, + x: Float, + top: Int, + y: Int, + bottom: Int, + paint: Paint, + ) { + configureTextPaint(paint) + + val offset = resolveBaselineOffset() + val paddingH = citationStyle.paddingHorizontal + val paddingV = citationStyle.paddingVertical + + val textW = textWidth() + val chipWidth = textW + paddingH * 2f + val metrics = textPaint.fontMetricsInt + val textAscent = metrics.ascent.toFloat() + val textDescent = metrics.descent.toFloat() + + // Baseline for the citation glyph, raised above the host baseline. + val glyphBaseline = y - offset + + // Background rectangle bounds the shifted glyph plus vertical padding. + val bgTop = glyphBaseline + textAscent - paddingV + val bgBottom = glyphBaseline + textDescent + paddingV + + if (citationStyle.backgroundColor != null && citationStyle.backgroundColor != 0) { + fillPaint.color = citationStyle.backgroundColor + val radius = minOf((bgBottom - bgTop) / 2f, chipWidth / 2f) + canvas.drawRoundRect( + RectF(x, bgTop, x + chipWidth, bgBottom), + radius, + radius, + fillPaint, + ) + } + + canvas.drawText(displayText, x + paddingH, glyphBaseline, textPaint) + } + + private fun resolveBaselineOffset(): Float = + if (citationStyle.baselineOffsetPx != 0f) { + citationStyle.baselineOffsetPx + } else { + // Fallback: raise the smaller glyph so its mid-line sits near the + // cap-height of the surrounding text. + -textPaint.ascent() * 0.5f + } +} diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt new file mode 100644 index 00000000..6bf25373 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt @@ -0,0 +1,138 @@ +package com.swmansion.enriched.markdown.spans + +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.graphics.Typeface +import android.text.style.ReplacementSpan +import com.swmansion.enriched.markdown.styles.MentionStyle + +/** + * Replaces a range of text with a rounded "pill" containing the display name. + * Rendering is atomic — the span reports its full width (including padding and + * border) via getSize so layout reserves enough room and the text never clips. + * + * The span exposes [userId] for tap dispatching and an [isPressed] flag the + * tap handler can toggle to drive the pressedOpacity tap-feedback animation. + */ +class MentionSpan( + val userId: String, + val displayText: String, + private val mentionStyle: MentionStyle, + private val mentionTypeface: Typeface?, +) : ReplacementSpan() { + @Volatile + var isPressed: Boolean = false + + private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL } + private val strokePaint = + Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.STROKE + strokeWidth = mentionStyle.borderWidth + } + private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG) + + private fun configureTextPaint(basePaint: Paint) { + textPaint.set(basePaint) + if (mentionStyle.fontSize > 0) { + textPaint.textSize = mentionStyle.fontSize + } + mentionTypeface?.let { textPaint.typeface = it } + textPaint.color = mentionStyle.color + } + + private fun contentWidth(): Float = textPaint.measureText(displayText) + + override fun getSize( + paint: Paint, + text: CharSequence?, + start: Int, + end: Int, + fm: Paint.FontMetricsInt?, + ): Int { + configureTextPaint(paint) + + val textWidth = contentWidth() + val totalWidth = textWidth + mentionStyle.paddingHorizontal * 2f + mentionStyle.borderWidth * 2f + + if (fm != null) { + val metrics = textPaint.fontMetricsInt + val verticalInset = (mentionStyle.paddingVertical + mentionStyle.borderWidth).toInt() + fm.ascent = metrics.ascent - verticalInset + fm.top = metrics.top - verticalInset + fm.descent = metrics.descent + verticalInset + fm.bottom = metrics.bottom + verticalInset + fm.leading = metrics.leading + } + + return totalWidth.toInt() + 1 + } + + override fun draw( + canvas: Canvas, + text: CharSequence, + start: Int, + end: Int, + x: Float, + top: Int, + y: Int, + bottom: Int, + paint: Paint, + ) { + configureTextPaint(paint) + + val opacity = + if (isPressed) mentionStyle.pressedOpacity.coerceIn(0f, 1f) else 1f + val globalAlpha = (opacity * 255f).toInt().coerceIn(0, 255) + + val textWidth = contentWidth() + val pillWidth = textWidth + mentionStyle.paddingHorizontal * 2f + mentionStyle.borderWidth * 2f + val metrics = textPaint.fontMetricsInt + val textHeight = metrics.descent - metrics.ascent + val pillHeight = textHeight + mentionStyle.paddingVertical * 2f + mentionStyle.borderWidth * 2f + + // Vertically center the pill on the surrounding text line. + val lineTop = top.toFloat() + val lineBottom = bottom.toFloat() + val pillTop = lineTop + ((lineBottom - lineTop) - pillHeight) / 2f + val pillBottom = pillTop + pillHeight + + val halfStroke = mentionStyle.borderWidth / 2f + val pillRect = + RectF( + x + halfStroke, + pillTop + halfStroke, + x + pillWidth - halfStroke, + pillBottom - halfStroke, + ) + val radius = + minOf( + mentionStyle.borderRadius, + minOf(pillRect.width(), pillRect.height()) / 2f, + ) + + fillPaint.color = mentionStyle.backgroundColor + fillPaint.alpha = + ((fillPaint.color ushr 24) and 0xFF).let { baseAlpha -> + (baseAlpha * opacity).toInt().coerceIn(0, 255) + } + canvas.drawRoundRect(pillRect, radius, radius, fillPaint) + + if (mentionStyle.borderWidth > 0f) { + strokePaint.strokeWidth = mentionStyle.borderWidth + strokePaint.color = mentionStyle.borderColor + strokePaint.alpha = + ((strokePaint.color ushr 24) and 0xFF).let { baseAlpha -> + (baseAlpha * opacity).toInt().coerceIn(0, 255) + } + canvas.drawRoundRect(pillRect, radius, radius, strokePaint) + } + + textPaint.alpha = globalAlpha + + val textX = x + mentionStyle.paddingHorizontal + mentionStyle.borderWidth + // Baseline-align the label inside the pill. + val textY = pillTop + mentionStyle.paddingVertical + mentionStyle.borderWidth - metrics.ascent + canvas.drawText(displayText, textX, textY, textPaint) + } +} diff --git a/android/src/main/java/com/swmansion/enriched/markdown/styles/CitationStyle.kt b/android/src/main/java/com/swmansion/enriched/markdown/styles/CitationStyle.kt new file mode 100644 index 00000000..c07ef43e --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/markdown/styles/CitationStyle.kt @@ -0,0 +1,41 @@ +package com.swmansion.enriched.markdown.styles + +import com.facebook.react.bridge.ReadableMap + +data class CitationStyle( + val color: Int, + val fontSizeMultiplier: Float, + val baselineOffsetPx: Float, + val fontWeight: String, + val underline: Boolean, + val backgroundColor: Int?, + val paddingHorizontal: Float, + val paddingVertical: Float, +) { + companion object { + fun fromReadableMap( + map: ReadableMap, + parser: StyleParser, + ): CitationStyle { + val color = parser.parseColor(map, "color") + val fontSizeMultiplier = parser.parseOptionalDouble(map, "fontSizeMultiplier", 0.7).toFloat() + val baselineOffsetPx = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "baselineOffsetPx").toFloat()) + val fontWeight = parser.parseString(map, "fontWeight") + val underline = parser.parseBoolean(map, "underline", false) + val backgroundColor = parser.parseOptionalColor(map, "backgroundColor") + val paddingHorizontal = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "paddingHorizontal").toFloat()) + val paddingVertical = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "paddingVertical").toFloat()) + + return CitationStyle( + color = color, + fontSizeMultiplier = if (fontSizeMultiplier > 0) fontSizeMultiplier else 0.7f, + baselineOffsetPx = baselineOffsetPx, + fontWeight = fontWeight, + underline = underline, + backgroundColor = backgroundColor, + paddingHorizontal = paddingHorizontal, + paddingVertical = paddingVertical, + ) + } + } +} diff --git a/android/src/main/java/com/swmansion/enriched/markdown/styles/MentionStyle.kt b/android/src/main/java/com/swmansion/enriched/markdown/styles/MentionStyle.kt new file mode 100644 index 00000000..81dbc6a1 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/markdown/styles/MentionStyle.kt @@ -0,0 +1,50 @@ +package com.swmansion.enriched.markdown.styles + +import com.facebook.react.bridge.ReadableMap + +data class MentionStyle( + val color: Int, + val backgroundColor: Int, + val borderColor: Int, + val borderWidth: Float, + val borderRadius: Float, + val paddingHorizontal: Float, + val paddingVertical: Float, + val fontFamily: String, + val fontWeight: String, + val fontSize: Float, + val pressedOpacity: Float, +) { + companion object { + fun fromReadableMap( + map: ReadableMap, + parser: StyleParser, + ): MentionStyle { + val color = parser.parseColor(map, "color") + val backgroundColor = parser.parseColor(map, "backgroundColor") + val borderColor = parser.parseColor(map, "borderColor") + val borderWidth = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "borderWidth").toFloat()) + val borderRadius = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "borderRadius").toFloat()) + val paddingHorizontal = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "paddingHorizontal").toFloat()) + val paddingVertical = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "paddingVertical").toFloat()) + val fontFamily = parser.parseString(map, "fontFamily") + val fontWeight = parser.parseString(map, "fontWeight") + val fontSize = parser.toPixelFromSP(parser.parseOptionalDouble(map, "fontSize").toFloat()) + val pressedOpacity = parser.parseOptionalDouble(map, "pressedOpacity", 0.6).toFloat() + + return MentionStyle( + color = color, + backgroundColor = backgroundColor, + borderColor = borderColor, + borderWidth = borderWidth, + borderRadius = borderRadius, + paddingHorizontal = paddingHorizontal, + paddingVertical = paddingVertical, + fontFamily = fontFamily, + fontWeight = fontWeight, + fontSize = fontSize, + pressedOpacity = pressedOpacity.coerceIn(0f, 1f), + ) + } + } +} diff --git a/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt b/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt index a851ac9a..27705be6 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/styles/StyleConfig.kt @@ -234,6 +234,32 @@ class StyleConfig( SpoilerStyle.fromReadableMap(map, styleParser) } + val mentionStyle: MentionStyle by lazy { + val map = + requireNotNull(style.getMap("mention")) { + "Mention style not found. JS should always provide defaults." + } + MentionStyle.fromReadableMap(map, styleParser) + } + + val citationStyle: CitationStyle by lazy { + val map = + requireNotNull(style.getMap("citation")) { + "Citation style not found. JS should always provide defaults." + } + CitationStyle.fromReadableMap(map, styleParser) + } + + val mentionTypeface: Typeface? by lazy { + val family = mentionStyle.fontFamily.takeIf { it.isNotEmpty() } + val weight = parseFontWeight(mentionStyle.fontWeight) + if (family != null) { + applyStyles(null, ReactConstants.UNSET, weight, family, assets) + } else { + null + } + } + val tableTypeface: Typeface? by lazy { val fontFamily = tableStyle.fontFamily.takeIf { it.isNotEmpty() } val fontWeight = parseFontWeight(tableStyle.fontWeight) @@ -297,7 +323,9 @@ class StyleConfig( taskListStyle == other.taskListStyle && mathStyle == other.mathStyle && inlineMathStyle == other.inlineMathStyle && - spoilerStyle == other.spoilerStyle + spoilerStyle == other.spoilerStyle && + mentionStyle == other.mentionStyle && + citationStyle == other.citationStyle } override fun hashCode(): Int { @@ -320,6 +348,8 @@ class StyleConfig( result = 31 * result + mathStyle.hashCode() result = 31 * result + inlineMathStyle.hashCode() result = 31 * result + spoilerStyle.hashCode() + result = 31 * result + mentionStyle.hashCode() + result = 31 * result + citationStyle.hashCode() return result } } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt index 9f3152da..7f8a9d40 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt @@ -4,9 +4,11 @@ import android.view.View import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.uimanager.UIManagerHelper +import com.swmansion.enriched.markdown.events.CitationPressEvent import com.swmansion.enriched.markdown.events.ContextMenuItemPressEvent import com.swmansion.enriched.markdown.events.LinkLongPressEvent import com.swmansion.enriched.markdown.events.LinkPressEvent +import com.swmansion.enriched.markdown.events.MentionPressEvent import com.swmansion.enriched.markdown.events.TaskListItemPressEvent import com.swmansion.enriched.markdown.parser.Md4cFlags @@ -19,6 +21,10 @@ fun markdownEventTypeConstants(): MutableMap { mapOf("registrationName" to TaskListItemPressEvent.EVENT_NAME) map[ContextMenuItemPressEvent.EVENT_NAME] = mapOf("registrationName" to ContextMenuItemPressEvent.EVENT_NAME) + map[MentionPressEvent.EVENT_NAME] = + mapOf("registrationName" to MentionPressEvent.EVENT_NAME) + map[CitationPressEvent.EVENT_NAME] = + mapOf("registrationName" to CitationPressEvent.EVENT_NAME) return map } @@ -42,6 +48,28 @@ fun emitLinkLongPress( eventDispatcher?.dispatchEvent(LinkLongPressEvent(surfaceId, view.id, url)) } +fun emitMentionPress( + view: View, + userId: String, + text: String, +) { + val context = view.context as com.facebook.react.bridge.ReactContext + val surfaceId = UIManagerHelper.getSurfaceId(context) + val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) + eventDispatcher?.dispatchEvent(MentionPressEvent(surfaceId, view.id, userId, text)) +} + +fun emitCitationPress( + view: View, + url: String, + text: String, +) { + val context = view.context as com.facebook.react.bridge.ReactContext + val surfaceId = UIManagerHelper.getSurfaceId(context) + val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) + eventDispatcher?.dispatchEvent(CitationPressEvent(surfaceId, view.id, url, text)) +} + fun emitTaskListItemPress( view: View, taskIndex: Int, diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt index 1f3e6936..7418748c 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt @@ -6,8 +6,10 @@ import android.widget.TextView import com.facebook.react.bridge.ReactContext import com.facebook.react.uimanager.UIManagerHelper import com.facebook.react.uimanager.events.NativeGestureUtil +import com.swmansion.enriched.markdown.events.CitationPressEvent import com.swmansion.enriched.markdown.events.LinkLongPressEvent import com.swmansion.enriched.markdown.events.LinkPressEvent +import com.swmansion.enriched.markdown.events.MentionPressEvent fun View.emitLinkPressEvent(url: String) { val reactContext = context as? ReactContext ?: return @@ -23,6 +25,26 @@ fun View.emitLinkLongPressEvent(url: String) { dispatcher?.dispatchEvent(LinkLongPressEvent(surfaceId, id, url)) } +fun View.emitMentionPressEvent( + userId: String, + text: String, +) { + val reactContext = context as? ReactContext ?: return + val surfaceId = UIManagerHelper.getSurfaceId(reactContext) + val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id) + dispatcher?.dispatchEvent(MentionPressEvent(surfaceId, id, userId, text)) +} + +fun View.emitCitationPressEvent( + url: String, + text: String, +) { + val reactContext = context as? ReactContext ?: return + val surfaceId = UIManagerHelper.getSurfaceId(reactContext) + val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id) + dispatcher?.dispatchEvent(CitationPressEvent(surfaceId, id, url, text)) +} + /** * Cancels the JS touch for an active link tap, preventing parent * Pressable/TouchableOpacity from firing onPress for the same tap. diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt index 401e11e5..5df5c0e2 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt @@ -8,12 +8,24 @@ import android.text.method.LinkMovementMethod import android.view.MotionEvent import android.view.ViewConfiguration import android.widget.TextView +import com.swmansion.enriched.markdown.spans.CitationSpan import com.swmansion.enriched.markdown.spans.LinkSpan +import com.swmansion.enriched.markdown.spans.MentionSpan import com.swmansion.enriched.markdown.spans.SpoilerSpan import com.swmansion.enriched.markdown.spoiler.SpoilerCapable import kotlin.math.abs class LinkLongPressMovementMethod : LinkMovementMethod() { + /** + * Optional callback invoked when a [MentionSpan] is tapped. The mention pill + * is a [android.text.style.ReplacementSpan], not a [android.text.style.ClickableSpan], + * so the standard LinkMovementMethod dispatch doesn't reach it. + */ + var onMentionTap: ((userId: String, text: String) -> Unit)? = null + + /** Optional callback invoked when a [CitationSpan] is tapped. */ + var onCitationTap: ((url: String, text: String) -> Unit)? = null + private val handler = Handler(Looper.getMainLooper()) private var longPressRunnable: Runnable? = null @@ -23,6 +35,10 @@ class LinkLongPressMovementMethod : LinkMovementMethod() { var isLinkTouchActive: Boolean = false private set + private var activeMentionSpan: MentionSpan? = null + private var pendingMentionTapOffset: Int = -1 + private var pendingCitationTapOffset: Int = -1 + override fun onTouchEvent( widget: TextView, buffer: Spannable, @@ -36,6 +52,19 @@ class LinkLongPressMovementMethod : LinkMovementMethod() { val span = findLinkSpan(widget, buffer, event) isLinkTouchActive = span != null span?.let { scheduleLongPress(widget, it) } + + findMentionSpan(widget, buffer, event)?.let { mention -> + activeMentionSpan = mention + mention.isPressed = true + widget.invalidate() + pendingMentionTapOffset = charOffsetAt(widget, event) ?: -1 + } + + if (activeMentionSpan == null) { + findCitationSpan(widget, buffer, event)?.let { _ -> + pendingCitationTapOffset = charOffsetAt(widget, event) ?: -1 + } + } } MotionEvent.ACTION_MOVE -> { @@ -45,6 +74,8 @@ class LinkLongPressMovementMethod : LinkMovementMethod() { ) { cancelLongPress() isLinkTouchActive = false + clearMentionPressedState(widget) + pendingCitationTapOffset = -1 } } @@ -56,13 +87,44 @@ class LinkLongPressMovementMethod : LinkMovementMethod() { } if (handleSpoilerTap(widget, buffer, event)) { Selection.removeSelection(buffer) + clearMentionPressedState(widget) + pendingCitationTapOffset = -1 return true } + + val mention = activeMentionSpan + if (mention != null) { + // Only emit if finger is still over the same mention span. + val stillOverMention = findMentionSpan(widget, buffer, event) === mention + clearMentionPressedState(widget) + pendingMentionTapOffset = -1 + if (stillOverMention) { + onMentionTap?.invoke(mention.userId, mention.displayText) + return true + } + } + + if (pendingCitationTapOffset >= 0) { + val currentOffset = charOffsetAt(widget, event) ?: -1 + val citation = + if (currentOffset >= 0) { + buffer.getSpans(currentOffset, currentOffset, CitationSpan::class.java).firstOrNull() + } else { + null + } + pendingCitationTapOffset = -1 + if (citation != null) { + onCitationTap?.invoke(citation.url, citation.displayText) + return true + } + } } MotionEvent.ACTION_CANCEL -> { cancelLongPress() isLinkTouchActive = false + clearMentionPressedState(widget) + pendingCitationTapOffset = -1 if (widget.hasSelection()) { Selection.removeSelection(buffer) } @@ -127,6 +189,32 @@ class LinkLongPressMovementMethod : LinkMovementMethod() { return buffer.getSpans(offset, offset, LinkSpan::class.java).firstOrNull() } + private fun findMentionSpan( + widget: TextView, + buffer: Spannable, + event: MotionEvent, + ): MentionSpan? { + val offset = charOffsetAt(widget, event) ?: return null + return buffer.getSpans(offset, offset, MentionSpan::class.java).firstOrNull() + } + + private fun findCitationSpan( + widget: TextView, + buffer: Spannable, + event: MotionEvent, + ): CitationSpan? { + val offset = charOffsetAt(widget, event) ?: return null + return buffer.getSpans(offset, offset, CitationSpan::class.java).firstOrNull() + } + + private fun clearMentionPressedState(widget: TextView) { + activeMentionSpan?.let { + it.isPressed = false + widget.invalidate() + } + activeMentionSpan = null + } + private fun handleSpoilerTap( widget: TextView, buffer: Spannable, diff --git a/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt b/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt index a9c6f6d2..b1f9e92d 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt @@ -49,6 +49,8 @@ class TableContainerView( var maxFontSizeMultiplier = 0f var onLinkPress: ((String) -> Unit)? = null var onLinkLongPress: ((String) -> Unit)? = null + var onMentionPress: ((userId: String, text: String) -> Unit)? = null + var onCitationPress: ((url: String, text: String) -> Unit)? = null private val scrollView = HorizontalScrollView(context).apply { @@ -206,6 +208,10 @@ class TableContainerView( showContextMenu(view) true } + (movementMethod as? LinkLongPressMovementMethod)?.apply { + onMentionTap = { userId, mentionText -> this@TableContainerView.onMentionPress?.invoke(userId, mentionText) } + onCitationTap = { url, citationText -> this@TableContainerView.onCitationPress?.invoke(url, citationText) } + } } val horizontalPadding = tableStyle.cellPaddingHorizontal val verticalPadding = tableStyle.cellPaddingVertical diff --git a/apps/example/Gemfile b/apps/example/Gemfile index 6a4c5f17..2163ea08 100644 --- a/apps/example/Gemfile +++ b/apps/example/Gemfile @@ -14,3 +14,5 @@ gem 'bigdecimal' gem 'logger' gem 'benchmark' gem 'mutex_m' +# kconv was removed from stdlib; CFPropertyList (CocoaPods) still expects it via nkf. +gem 'nkf' diff --git a/apps/example/Gemfile.lock b/apps/example/Gemfile.lock index 4c317a6d..075ae447 100644 --- a/apps/example/Gemfile.lock +++ b/apps/example/Gemfile.lock @@ -2,7 +2,7 @@ GEM remote: https://rubygems.org/ specs: CFPropertyList (3.0.8) - activesupport (7.2.3) + activesupport (7.2.3.1) base64 benchmark (>= 0.3) bigdecimal @@ -11,10 +11,10 @@ GEM drb i18n (>= 1.6, < 2) logger (>= 1.4.2) - minitest (>= 5.1) + minitest (>= 5.1, < 6) securerandom (>= 0.3) tzinfo (~> 2.0, >= 2.0.5) - addressable (2.8.9) + addressable (2.9.0) public_suffix (>= 2.0.2, < 8.0) algoliasearch (1.27.5) httpclient (~> 2.8, >= 2.8.3) @@ -22,7 +22,7 @@ GEM atomos (0.1.3) base64 (0.3.0) benchmark (0.5.0) - bigdecimal (4.0.1) + bigdecimal (4.1.1) claide (1.1.0) cocoapods (1.15.2) addressable (~> 2.8) @@ -66,9 +66,10 @@ GEM connection_pool (3.0.2) drb (2.2.3) escape (0.0.4) - ethon (0.15.0) + ethon (0.18.0) ffi (>= 1.15.0) - ffi (1.17.3) + logger + ffi (1.17.4) fourflusher (2.3.1) fuzzy_match (2.0.4) gh_inspector (1.1.3) @@ -76,23 +77,21 @@ GEM mutex_m i18n (1.14.8) concurrent-ruby (~> 1.0) - json (2.18.1) + json (2.19.3) logger (1.7.0) - minitest (6.0.2) - drb (~> 2.0) - prism (~> 1.5) + minitest (5.27.0) molinillo (0.8.0) mutex_m (0.3.0) nanaimo (0.3.0) nap (1.1.0) netrc (0.11.0) - prism (1.9.0) + nkf (0.2.0) public_suffix (4.0.7) rexml (3.4.4) ruby-macho (2.5.1) securerandom (0.4.1) - typhoeus (1.5.0) - ethon (>= 0.9.0, < 0.16.0) + typhoeus (1.6.0) + ethon (>= 0.18.0) tzinfo (2.0.6) concurrent-ruby (~> 1.0) xcodeproj (1.25.1) @@ -114,6 +113,7 @@ DEPENDENCIES concurrent-ruby (< 1.3.4) logger mutex_m + nkf xcodeproj (< 1.26.0) RUBY VERSION diff --git a/apps/example/ios/Podfile.lock b/apps/example/ios/Podfile.lock index 6b240433..2c37a8bd 100644 --- a/apps/example/ios/Podfile.lock +++ b/apps/example/ios/Podfile.lock @@ -2117,7 +2117,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: FBLazyVector: e97c19a5a442429d1988f182a1940fb08df514da - hermes-engine: 40811a005e96e04818cff405ec04a5b4c4411c1c + hermes-engine: f6578d972f9b73b756304d62c2537e7f69329eca iosMath: f7a6cbadf9d836d2149c2a84c435b1effc244cba RCTDeprecation: af44b104091a34482596cd9bd7e8d90c4e9b4bd7 RCTRequired: bb77b070f75f53398ce43c0aaaa58337cebe2bf6 @@ -2127,7 +2127,7 @@ SPEC CHECKSUMS: React: 1ba7d364ade7d883a1ec055bfc3606f35fdee17b React-callinvoker: bc2a26f8d84fb01f003fc6de6c9337b64715f95b React-Core: 7840d3a80b43a95c5e80ef75146bd70925ebab0f - React-Core-prebuilt: 7965d06a81dcc544164f8e98b26d35ae2a4eb36e + React-Core-prebuilt: e5482a51694507e64658e1c0be8753a92fc4e849 React-CoreModules: 2eb010400b63b89e53a324ffb3c112e4c7c3ce42 React-cxxreact: a558e92199d26f145afa9e62c4233cf8e7950efe React-debug: 755200a6e7f5e6e0a40ff8d215493d43cce285fc @@ -2189,10 +2189,10 @@ SPEC CHECKSUMS: ReactAppDependencyProvider: e96e93b493d8d86eeaee3e590ba0be53f6abe46f ReactCodegen: 797de5178718324c6eba3327b07f9a423fbd5787 ReactCommon: 07572bf9e687c8a52fbe4a3641e9e3a1a477c78e - ReactNativeDependencies: 0811b43c669e637a9f3c485fdb106f187fa88398 - ReactNativeEnrichedMarkdown: 1daba1851810704ba2f5c6e5fff638a94661e317 + ReactNativeDependencies: f2497ee045a976e64dec20d371611288e835df1f + ReactNativeEnrichedMarkdown: 63665119b6d5c634c76f5f4caf46dc4ebd23863e Yoga: c0b3f2c7e8d3e327e450223a2414ca3fa296b9a2 PODFILE CHECKSUM: 9c5417fc84515945aa2357a49779fde55434ae62 -COCOAPODS: 1.16.2 +COCOAPODS: 1.15.2 diff --git a/apps/example/src/sampleMarkdown.ts b/apps/example/src/sampleMarkdown.ts index bb7263dc..78f3b68d 100644 --- a/apps/example/src/sampleMarkdown.ts +++ b/apps/example/src/sampleMarkdown.ts @@ -1,5 +1,5 @@ export const sampleMarkdown = ` -# The Hidden World of Forest Ecosystems +# The Hidden World of Forest Ecosystems!! Forests cover approximately **31% of the Earth's land surface**, providing habitat for countless species and playing a vital role in our planet's health. These magnificent ecosystems have existed for over *300 million years*, evolving alongside the creatures that call them home. @@ -11,15 +11,15 @@ Forests cover approximately **31% of the Earth's land surface**, providing habit Forests are often called the *lungs of the Earth*. They absorb **carbon dioxide** and release oxygen through photosynthesis — a process essential for all life on our planet. A single mature tree can absorb up to \`48 pounds\` of CO₂ per year. -> In every walk with nature, one receives far more than he seeks. +> [@John Muir](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) In every walk with nature, one receives far more than he seeks. > > — John Muir ### Key Benefits -- **Climate regulation** through carbon sequestration -- *Biodiversity* hotspots supporting millions of species -- Natural water filtration and ***flood prevention*** +- **Climate regulation** through carbon sequestration [@Casper](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) +- *Biodiversity* hotspots supporting millions of species [+resume software engineer](mention://Uploads/twilio-script.py?type=file) +- Natural water filtration and ***flood prevention*** [1](citation://https://www.google.com) [2](citation://https://www.google.com?q=123) [3](citation://https://www.google.com?q=123&abc=123) [4](citation://https://www.google.com?q=123) [5](citation://https://www.google.com?q=123) [6](citation://https://www.google.com?q=123) [7](citation://https://www.google.com?q=123) [8](citation://https://www.google.com?q=123) [9](citation://https://www.google.com?q=123) [10](citation://https://www.google.com?q=123) - Source of medicine, food, and raw materials - Soil erosion prevention and **nutrient cycling** - Recreation and *mental health* benefits @@ -28,7 +28,7 @@ Forests are often called the *lungs of the Earth*. They absorb **carbon dioxide* Forests contribute over **$1.3 trillion** to the global economy annually. They provide: -- Timber and *wood products* +- Timber and *wood products* [4](citation://https://www.google.com) [5](citation://https://www.google.com) [6](citation://https://www.google.com) [7](citation://https://www.google.com) [8](citation://https://www.google.com) [9](citation://https://www.google.com) [7](citation://https://www.google.com) - Non-timber forest products like **nuts and berries** - Ecotourism opportunities - ***Carbon credits*** for climate mitigation @@ -104,10 +104,10 @@ The largest terrestrial biome, spanning across **Northern Russia, Canada, and Sc | Forest Type | Coverage | Annual Rainfall | Biodiversity | Carbon Storage | |------------|----------|-----------------|--------------|----------------| -| Tropical Rainforest | ~7% of land | 80-400 inches | Highest (50%+ species) | High | +| Tropical Rainforest [4](citation://https://www.google.com) [5](citation://https://www.google.com) | ~7% of land | 80-400 inches | Highest (50%+ species) | High | | Temperate Forest | ~16% of land | 30-60 inches | Moderate | Moderate | | Boreal Forest (Taiga) | ~11% of land | 15-40 inches | Lower | Highest | -| Mediterranean Forest | ~2% of land | 20-40 inches | Moderate | Moderate | +| Mediterranean Forest | ~2% of land | 20-40 inches | Moderate | Moderate [@John Muir](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user)| --- @@ -136,7 +136,7 @@ class TreeNetwork { this.trees = []; this.fungalConnections = new Map(); } - + connectTrees(tree1, tree2) { // Trees share nutrients through mycorrhizal networks this.fungalConnections.set(\`\${tree1.id}-\${tree2.id}\`, { @@ -330,11 +330,11 @@ def detect_deforestation(region): """Monitor forest cover changes using satellite imagery""" current_cover = satellite_imagery.get_forest_cover(region) previous_cover = satellite_imagery.get_historical_cover(region, years_ago=1) - + deforestation_rate = (previous_cover - current_cover) / previous_cover if deforestation_rate > 0.05: # 5% threshold alert_conservation_team(region, deforestation_rate) - + return deforestation_rate \`\`\` diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index 9227b789..66e49748 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -559,6 +559,34 @@ - (TableContainerView *)createTableViewForSegment:(EMTableSegment *)tableSegment } }; + tableView.onMentionPress = ^(NSString *userId, NSString *text) { + EnrichedMarkdown *strongSelf = weakSelf; + if (!strongSelf) + return; + + auto eventEmitter = std::static_pointer_cast(strongSelf->_eventEmitter); + if (eventEmitter) { + eventEmitter->onMentionPress({ + .userId = std::string([(userId ?: @"") UTF8String] ?: ""), + .text = std::string([(text ?: @"") UTF8String] ?: ""), + }); + } + }; + + tableView.onCitationPress = ^(NSString *url, NSString *text) { + EnrichedMarkdown *strongSelf = weakSelf; + if (!strongSelf) + return; + + auto eventEmitter = std::static_pointer_cast(strongSelf->_eventEmitter); + if (eventEmitter) { + eventEmitter->onCitationPress({ + .url = std::string([(url ?: @"") UTF8String] ?: ""), + .text = std::string([(text ?: @"") UTF8String] ?: ""), + }); + } + }; + [tableView applyTableNode:tableSegment.tableNode]; return tableView; @@ -760,11 +788,28 @@ - (void)textTapped:(ENRMTapRecognizer *)recognizer } } - NSString *url = linkURLAtTapLocation(textView, recognizer); - if (url) { + NSString *linkURL = nil; + NSString *mentionUserId = nil; + NSString *mentionText = nil; + NSString *citationURL = nil; + NSString *citationText = nil; + if (inlineElementAtTapLocation(textView, recognizer, &linkURL, &mentionUserId, &mentionText, &citationURL, + &citationText)) { auto eventEmitter = std::static_pointer_cast(_eventEmitter); if (eventEmitter) { - eventEmitter->onLinkPress({.url = std::string([url UTF8String])}); + if (mentionUserId) { + eventEmitter->onMentionPress({ + .userId = std::string([mentionUserId UTF8String] ?: ""), + .text = std::string([(mentionText ?: @"") UTF8String] ?: ""), + }); + } else if (citationURL) { + eventEmitter->onCitationPress({ + .url = std::string([citationURL UTF8String] ?: ""), + .text = std::string([(citationText ?: @"") UTF8String] ?: ""), + }); + } else if (linkURL) { + eventEmitter->onLinkPress({.url = std::string([linkURL UTF8String])}); + } } return; } diff --git a/ios/EnrichedMarkdownText.mm b/ios/EnrichedMarkdownText.mm index 239e435a..d2e60d3b 100644 --- a/ios/EnrichedMarkdownText.mm +++ b/ios/EnrichedMarkdownText.mm @@ -537,11 +537,28 @@ - (void)textTapped:(ENRMTapRecognizer *)recognizer return; } - NSString *url = linkURLAtTapLocation(textView, recognizer); - if (url) { + NSString *linkURL = nil; + NSString *mentionUserId = nil; + NSString *mentionText = nil; + NSString *citationURL = nil; + NSString *citationText = nil; + if (inlineElementAtTapLocation(textView, recognizer, &linkURL, &mentionUserId, &mentionText, &citationURL, + &citationText)) { auto eventEmitter = std::static_pointer_cast(_eventEmitter); if (eventEmitter) { - eventEmitter->onLinkPress({.url = std::string([url UTF8String])}); + if (mentionUserId) { + eventEmitter->onMentionPress({ + .userId = std::string([mentionUserId UTF8String] ?: ""), + .text = std::string([(mentionText ?: @"") UTF8String] ?: ""), + }); + } else if (citationURL) { + eventEmitter->onCitationPress({ + .url = std::string([citationURL UTF8String] ?: ""), + .text = std::string([(citationText ?: @"") UTF8String] ?: ""), + }); + } else if (linkURL) { + eventEmitter->onLinkPress({.url = std::string([linkURL UTF8String])}); + } } return; } diff --git a/ios/attachments/ENRMCitationAttachment.h b/ios/attachments/ENRMCitationAttachment.h new file mode 100644 index 00000000..4f7a94c4 --- /dev/null +++ b/ios/attachments/ENRMCitationAttachment.h @@ -0,0 +1,25 @@ +#pragma once +#import "ENRMUIKit.h" + +@class StyleConfig; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Custom NSTextAttachment for rendering inline citation markers. Drawing is + * atomic (CoreGraphics into a UIImage) so the renderer can apply padding, + * backgrounds, baseline offset, and a font-size multiplier consistently. + */ +@interface ENRMCitationAttachment : NSTextAttachment + +@property (nonatomic, readonly, copy) NSString *displayText; +@property (nonatomic, readonly, copy) NSString *url; + ++ (instancetype)attachmentWithDisplayText:(NSString *)displayText + url:(NSString *)url + baseFont:(nullable UIFont *)baseFont + config:(StyleConfig *)config; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/attachments/ENRMCitationAttachment.m b/ios/attachments/ENRMCitationAttachment.m new file mode 100644 index 00000000..17c96805 --- /dev/null +++ b/ios/attachments/ENRMCitationAttachment.m @@ -0,0 +1,145 @@ +#import "ENRMCitationAttachment.h" +#import "StyleConfig.h" + +@interface ENRMCitationAttachment () +@property (nonatomic, copy) NSString *displayText; +@property (nonatomic, copy) NSString *url; +@property (nonatomic, strong, nullable) UIFont *baseFont; +@property (nonatomic, strong) StyleConfig *config; +@property (nonatomic, assign) CGSize cachedSize; +@property (nonatomic, assign) CGFloat cachedBaseline; +@end + +@implementation ENRMCitationAttachment + ++ (instancetype)attachmentWithDisplayText:(NSString *)displayText + url:(NSString *)url + baseFont:(UIFont *)baseFont + config:(StyleConfig *)config +{ + ENRMCitationAttachment *attachment = [[self alloc] init]; + attachment.displayText = displayText ?: @""; + attachment.url = url ?: @""; + attachment.baseFont = baseFont; + attachment.config = config; + [attachment rebuildImage]; + return attachment; +} + +- (UIFont *)citationFont +{ + UIFont *base = self.baseFont; + if (!base) { + base = [UIFont systemFontOfSize:[UIFont systemFontSize]]; + } + CGFloat multiplier = [self.config citationFontSizeMultiplier]; + if (multiplier <= 0) { + multiplier = 0.7; + } + CGFloat scaledSize = MAX(1.0, base.pointSize * multiplier); + NSString *weight = [self.config citationFontWeight] ?: @""; + UIFont *scaled = [base fontWithSize:scaledSize]; + + if (weight.length > 0 && ([weight caseInsensitiveCompare:@"bold"] == NSOrderedSame || + [weight caseInsensitiveCompare:@"700"] == NSOrderedSame || + [weight caseInsensitiveCompare:@"800"] == NSOrderedSame || + [weight caseInsensitiveCompare:@"900"] == NSOrderedSame)) { + UIFontDescriptor *descriptor = [scaled.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold]; + if (descriptor) { + scaled = [UIFont fontWithDescriptor:descriptor size:scaledSize]; + } + } + return scaled; +} + +- (void)rebuildImage +{ + UIFont *font = [self citationFont]; + RCTUIColor *textColor = [self.config citationColor] ?: [RCTUIColor labelColor]; + RCTUIColor *bgColor = [self.config citationBackgroundColor]; + CGFloat paddingH = MAX(0, [self.config citationPaddingHorizontal]); + CGFloat paddingV = MAX(0, [self.config citationPaddingVertical]); + BOOL underline = [self.config citationUnderline]; + + NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; + attrs[NSFontAttributeName] = font; + attrs[NSForegroundColorAttributeName] = textColor; + if (underline) { + attrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle); + attrs[NSUnderlineColorAttributeName] = textColor; + } + + CGSize textSize = [self.displayText sizeWithAttributes:attrs]; + + CGFloat width = ceil(textSize.width + paddingH * 2); + CGFloat height = ceil(textSize.height + paddingV * 2); + CGSize size = CGSizeMake(MAX(1, width), MAX(1, height)); + self.cachedSize = size; + self.cachedBaseline = paddingV + font.ascender; + + UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat]; + format.opaque = NO; + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:format]; + + __weak typeof(self) weakSelf = self; + UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *ctx) { + __strong typeof(weakSelf) strongSelf = weakSelf; + if (!strongSelf) + return; + + CGContextRef cg = ctx.CGContext; + (void)cg; + + if (bgColor) { + CGRect rect = CGRectMake(0, 0, size.width, size.height); + CGFloat radius = MIN(size.height, size.width) / 2.0; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius]; + [bgColor setFill]; + [path fill]; + } + + CGPoint origin = CGPointMake(paddingH, paddingV); + [strongSelf.displayText drawAtPoint:origin withAttributes:attrs]; + }]; + + self.image = image; + self.bounds = CGRectMake(0, 0, size.width, size.height); +} + +- (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer + proposedLineFragment:(CGRect)lineFragment + glyphPosition:(CGPoint)position + characterIndex:(NSUInteger)characterIndex +{ + CGSize size = self.cachedSize; + if (size.width == 0 || size.height == 0) { + [self rebuildImage]; + size = self.cachedSize; + } + + // Derive the desired baseline offset (superscript-like shift). Positive + // `NSBaselineOffsetAttributeName` values move glyphs upward; for an + // attachment we achieve the same by offsetting the bounds origin upward. + CGFloat baselineOffset = [self.config citationBaselineOffsetPx]; + UIFont *lineFont = self.baseFont; + if (!lineFont) { + NSLayoutManager *layoutManager = textContainer.layoutManager; + NSTextStorage *textStorage = layoutManager.textStorage; + if (textStorage && characterIndex < textStorage.length) { + lineFont = [textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL]; + } + } + + if (baselineOffset == 0 && lineFont) { + // Default: raise so the mid-line of the citation sits near the cap-height + // of the surrounding text, matching the MetricAffectingSpan fallback used + // on Android. + CGFloat hostCap = lineFont.capHeight; + UIFont *citationFont = [self citationFont]; + baselineOffset = MAX(0, (hostCap - citationFont.capHeight) * 0.5); + } + + return CGRectMake(0, baselineOffset, size.width, size.height); +} + +@end diff --git a/ios/attachments/ENRMMentionAttachment.h b/ios/attachments/ENRMMentionAttachment.h new file mode 100644 index 00000000..8c29ea8b --- /dev/null +++ b/ios/attachments/ENRMMentionAttachment.h @@ -0,0 +1,28 @@ +#pragma once +#import "ENRMUIKit.h" + +@class StyleConfig; + +NS_ASSUME_NONNULL_BEGIN + +/** + * Custom NSTextAttachment used to render inline mention pills. + * + * Rendering is delegated to an NSTextAttachmentViewProvider (iOS 15+) so the + * pill participates in the text layout as an atomic character — selection + * handles, cursor movement, and accessibility traverse it as a single glyph. + * Tap feedback (alpha dim on press) is managed by the view provider. + */ +@interface ENRMMentionAttachment : NSTextAttachment + +@property (nonatomic, readonly, copy) NSString *displayText; +@property (nonatomic, readonly, copy) NSString *userId; +@property (nonatomic, readonly, strong) StyleConfig *config; + ++ (instancetype)attachmentWithDisplayText:(NSString *)displayText + userId:(NSString *)userId + config:(StyleConfig *)config; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/attachments/ENRMMentionAttachment.m b/ios/attachments/ENRMMentionAttachment.m new file mode 100644 index 00000000..018d5442 --- /dev/null +++ b/ios/attachments/ENRMMentionAttachment.m @@ -0,0 +1,115 @@ +#import "ENRMMentionAttachment.h" +#import "StyleConfig.h" + +@interface ENRMMentionAttachment () +@property (nonatomic, copy) NSString *displayText; +@property (nonatomic, copy) NSString *userId; +@property (nonatomic, strong) StyleConfig *config; +@property (nonatomic, assign) CGSize cachedPillSize; +@end + +@implementation ENRMMentionAttachment + ++ (instancetype)attachmentWithDisplayText:(NSString *)displayText userId:(NSString *)userId config:(StyleConfig *)config +{ + ENRMMentionAttachment *attachment = [[self alloc] init]; + attachment.displayText = displayText ?: @""; + attachment.userId = userId ?: @""; + attachment.config = config; + [attachment rebuildPillImage]; + return attachment; +} + +- (void)rebuildPillImage +{ + UIFont *font = [self.config mentionFont]; + RCTUIColor *textColor = [self.config mentionColor] ?: [RCTUIColor labelColor]; + RCTUIColor *bgColor = [self.config mentionBackgroundColor]; + RCTUIColor *borderColor = [self.config mentionBorderColor]; + CGFloat borderWidth = MAX(0, [self.config mentionBorderWidth]); + CGFloat borderRadius = MAX(0, [self.config mentionBorderRadius]); + CGFloat paddingH = MAX(0, [self.config mentionPaddingHorizontal]); + CGFloat paddingV = MAX(0, [self.config mentionPaddingVertical]); + + NSDictionary *textAttrs = font ? @{NSFontAttributeName : font} : @{}; + CGSize textSize = [self.displayText sizeWithAttributes:textAttrs]; + + // Ensure the pill is large enough to fit the label plus padding and border. + // `getSize` parity for Android: width = textWidth + 2*padding + 2*border. + CGFloat width = ceil(textSize.width + paddingH * 2 + borderWidth * 2); + CGFloat height = ceil(textSize.height + paddingV * 2 + borderWidth * 2); + CGSize size = CGSizeMake(MAX(1, width), MAX(1, height)); + self.cachedPillSize = size; + + UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat]; + format.opaque = NO; + UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:format]; + + __weak typeof(self) weakSelf = self; + UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *ctx) { + CGContextRef cg = ctx.CGContext; + CGRect bounds = CGRectMake(0, 0, size.width, size.height); + + // Inset so the border stroke stays inside the bounds (centered on pill edge). + CGRect rect = CGRectInset(bounds, borderWidth / 2.0, borderWidth / 2.0); + CGFloat clampedRadius = MIN(borderRadius, MIN(rect.size.width, rect.size.height) / 2.0); + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:clampedRadius]; + + if (bgColor) { + CGContextSaveGState(cg); + [bgColor setFill]; + [path fill]; + CGContextRestoreGState(cg); + } + + if (borderWidth > 0 && borderColor) { + CGContextSaveGState(cg); + [borderColor setStroke]; + path.lineWidth = borderWidth; + [path stroke]; + CGContextRestoreGState(cg); + } + + CGFloat textX = (size.width - textSize.width) / 2.0; + CGFloat textY = (size.height - textSize.height) / 2.0; + + NSDictionary *drawAttrs = font ? @{NSFontAttributeName : font, NSForegroundColorAttributeName : textColor} + : @{NSForegroundColorAttributeName : textColor}; + [weakSelf.displayText drawAtPoint:CGPointMake(textX, textY) withAttributes:drawAttrs]; + }]; + + self.image = image; + self.bounds = CGRectMake(0, 0, size.width, size.height); +} + +- (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer + proposedLineFragment:(CGRect)lineFragment + glyphPosition:(CGPoint)position + characterIndex:(NSUInteger)characterIndex +{ + CGSize size = self.cachedPillSize; + if (size.width == 0 || size.height == 0) { + [self rebuildPillImage]; + size = self.cachedPillSize; + } + + // Vertically center the pill on the surrounding text's cap height when + // available, mirroring how inline images are positioned in this codebase. + UIFont *lineFont = nil; + NSLayoutManager *layoutManager = textContainer.layoutManager; + NSTextStorage *textStorage = layoutManager.textStorage; + if (textStorage && characterIndex < textStorage.length) { + lineFont = [textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL]; + } + + CGFloat verticalOffset; + if (lineFont) { + verticalOffset = (lineFont.capHeight - size.height) / 2.0; + } else { + verticalOffset = (lineFragment.size.height - size.height) / 2.0; + } + + return CGRectMake(0, verticalOffset, size.width, size.height); +} + +@end diff --git a/ios/renderer/LinkRenderer.h b/ios/renderer/LinkRenderer.h index ff5b8d20..5c9a7a42 100644 --- a/ios/renderer/LinkRenderer.h +++ b/ios/renderer/LinkRenderer.h @@ -2,6 +2,16 @@ #import "NodeRenderer.h" #import "RenderContext.h" +/** + * Attribute names used by the link/mention/citation renderer to tag ranges of + * the rendered NSAttributedString. Tap dispatching reads these to decide which + * JS event to fire for a given character. + */ +FOUNDATION_EXPORT NSString *const ENRMMentionUserIdAttributeName; +FOUNDATION_EXPORT NSString *const ENRMMentionTextAttributeName; +FOUNDATION_EXPORT NSString *const ENRMCitationURLAttributeName; +FOUNDATION_EXPORT NSString *const ENRMCitationTextAttributeName; + @interface LinkRenderer : NSObject - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config; @end diff --git a/ios/renderer/LinkRenderer.m b/ios/renderer/LinkRenderer.m index 8c37b26d..55d0ec22 100644 --- a/ios/renderer/LinkRenderer.m +++ b/ios/renderer/LinkRenderer.m @@ -1,10 +1,20 @@ #import "LinkRenderer.h" +#import "ENRMCitationAttachment.h" +#import "ENRMMentionAttachment.h" #import "FontUtils.h" #import "RenderContext.h" #import "RendererFactory.h" #import "StyleConfig.h" #import +NSString *const ENRMMentionUserIdAttributeName = @"ENRMMentionUserId"; +NSString *const ENRMMentionTextAttributeName = @"ENRMMentionText"; +NSString *const ENRMCitationURLAttributeName = @"ENRMCitationURL"; +NSString *const ENRMCitationTextAttributeName = @"ENRMCitationText"; + +static NSString *const kMentionScheme = @"mention://"; +static NSString *const kCitationScheme = @"citation://"; + @implementation LinkRenderer { RendererFactory *_rendererFactory; StyleConfig *_config; @@ -20,41 +30,76 @@ - (instancetype)initWithRendererFactory:(id)rendererFactory config:(id)config return self; } +#pragma mark - Scheme helpers + +static BOOL isMentionURL(NSString *url) +{ + return [url hasPrefix:kMentionScheme]; +} + +static BOOL isCitationURL(NSString *url) +{ + return [url hasPrefix:kCitationScheme]; +} + +static NSString *stripScheme(NSString *url, NSString *scheme) +{ + if ([url hasPrefix:scheme]) { + return [url substringFromIndex:scheme.length]; + } + return url; +} + #pragma mark - Rendering - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context +{ + NSString *url = node.attributes[@"url"] ?: @""; + + if (isMentionURL(url)) { + [self renderMentionNode:node url:url into:output context:context]; + return; + } + + if (isCitationURL(url)) { + [self renderCitationNode:node url:url into:output context:context]; + return; + } + + [self renderLinkNode:node url:url into:output context:context]; +} + +#pragma mark - Link (default / existing behavior) + +- (void)renderLinkNode:(MarkdownASTNode *)node + url:(NSString *)url + into:(NSMutableAttributedString *)output + context:(RenderContext *)context { NSUInteger start = output.length; - // 1. Render children first to establish base attributes [_rendererFactory renderChildrenOfNode:node into:output context:context]; NSRange range = NSMakeRange(start, output.length - start); if (range.length == 0) return; - // 2. Extract configuration - NSString *url = node.attributes[@"url"] ?: @""; RCTUIColor *linkColor = [_config linkColor]; NSNumber *underlineStyle = @([_config linkUnderline] ? NSUnderlineStyleSingle : NSUnderlineStyleNone); NSString *linkFontFamily = [_config linkFontFamily]; - // 3. Apply core link functionality (non-destructive) [output addAttribute:NSLinkAttributeName value:url range:range]; - // 4. Optimize visual attributes via enumeration to avoid redundant updates [output enumerateAttributesInRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired usingBlock:^(NSDictionary *attrs, NSRange subrange, BOOL *stop) { NSMutableDictionary *newAttributes = [NSMutableDictionary dictionary]; - // Only apply link color if the subrange isn't already colored by the link style if (linkColor && ![attrs[NSForegroundColorAttributeName] isEqual:linkColor]) { newAttributes[NSForegroundColorAttributeName] = linkColor; newAttributes[NSUnderlineColorAttributeName] = linkColor; } - // Only update underline style if it differs from the config if (![attrs[NSUnderlineStyleAttributeName] isEqual:underlineStyle]) { newAttributes[NSUnderlineStyleAttributeName] = underlineStyle; } @@ -80,8 +125,84 @@ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)out } }]; - // 5. Register for touch handling [context registerLinkRange:range url:url]; } -@end \ No newline at end of file +#pragma mark - Mention + +- (void)renderMentionNode:(MarkdownASTNode *)node + url:(NSString *)url + into:(NSMutableAttributedString *)output + context:(RenderContext *)context +{ + // Extract the child text to use as the pill label; the child nodes may be + // formatted text (e.g. **bold**), so we collapse to a plain string. + NSMutableAttributedString *childBuffer = [[NSMutableAttributedString alloc] init]; + [_rendererFactory renderChildrenOfNode:node into:childBuffer context:context]; + NSString *displayText = childBuffer.string ?: @""; + NSString *userId = stripScheme(url, kMentionScheme); + + // Inherit the current text attributes (font, color) so the pill sits in the + // same line metrics as the surrounding paragraph if the pill label has no + // explicit style override. + NSDictionary *baseAttrs = output.length > 0 ? [output attributesAtIndex:output.length - 1 effectiveRange:NULL] : @{}; + + ENRMMentionAttachment *attachment = [ENRMMentionAttachment attachmentWithDisplayText:displayText + userId:userId + config:_config]; + + NSMutableAttributedString *attachmentString = + [[NSMutableAttributedString attributedStringWithAttachment:attachment] mutableCopy]; + NSRange attachmentRange = NSMakeRange(0, attachmentString.length); + if (baseAttrs.count > 0) { + [attachmentString addAttributes:baseAttrs range:attachmentRange]; + } + [attachmentString addAttribute:ENRMMentionUserIdAttributeName value:userId range:attachmentRange]; + [attachmentString addAttribute:ENRMMentionTextAttributeName value:displayText range:attachmentRange]; + + NSUInteger start = output.length; + [output appendAttributedString:attachmentString]; + NSRange outputRange = NSMakeRange(start, output.length - start); + + [context registerMentionRange:outputRange userId:userId text:displayText]; +} + +#pragma mark - Citation + +- (void)renderCitationNode:(MarkdownASTNode *)node + url:(NSString *)url + into:(NSMutableAttributedString *)output + context:(RenderContext *)context +{ + // Render children into a throwaway buffer so we can collect the label text + // and inherit the surrounding font (used to scale the citation glyph). + NSMutableAttributedString *childBuffer = [[NSMutableAttributedString alloc] init]; + [_rendererFactory renderChildrenOfNode:node into:childBuffer context:context]; + NSString *displayText = childBuffer.string ?: @""; + NSString *targetURL = stripScheme(url, kCitationScheme); + + NSDictionary *baseAttrs = output.length > 0 ? [output attributesAtIndex:output.length - 1 effectiveRange:NULL] : @{}; + UIFont *baseFont = baseAttrs[NSFontAttributeName]; + + ENRMCitationAttachment *attachment = [ENRMCitationAttachment attachmentWithDisplayText:displayText + url:targetURL + baseFont:baseFont + config:_config]; + + NSMutableAttributedString *attachmentString = + [[NSMutableAttributedString attributedStringWithAttachment:attachment] mutableCopy]; + NSRange attachmentRange = NSMakeRange(0, attachmentString.length); + if (baseAttrs.count > 0) { + [attachmentString addAttributes:baseAttrs range:attachmentRange]; + } + [attachmentString addAttribute:ENRMCitationURLAttributeName value:targetURL range:attachmentRange]; + [attachmentString addAttribute:ENRMCitationTextAttributeName value:displayText range:attachmentRange]; + + NSUInteger start = output.length; + [output appendAttributedString:attachmentString]; + NSRange outputRange = NSMakeRange(start, output.length - start); + + [context registerCitationRange:outputRange url:targetURL text:displayText]; +} + +@end diff --git a/ios/renderer/RenderContext.h b/ios/renderer/RenderContext.h index c7aa2b01..20475165 100644 --- a/ios/renderer/RenderContext.h +++ b/ios/renderer/RenderContext.h @@ -25,6 +25,12 @@ typedef NS_ENUM(NSInteger, ListType) { ListTypeUnordered, ListTypeOrdered }; @interface RenderContext : NSObject @property (nonatomic, strong) NSMutableArray *linkRanges; @property (nonatomic, strong) NSMutableArray *linkURLs; +@property (nonatomic, strong) NSMutableArray *mentionRanges; +@property (nonatomic, strong) NSMutableArray *mentionUserIds; +@property (nonatomic, strong) NSMutableArray *mentionTexts; +@property (nonatomic, strong) NSMutableArray *citationRanges; +@property (nonatomic, strong) NSMutableArray *citationURLs; +@property (nonatomic, strong) NSMutableArray *citationTexts; @property (nonatomic, strong) NSMutableArray *headingRanges; @property (nonatomic, strong) NSMutableArray *headingLevels; @property (nonatomic, strong) NSMutableArray *imageRanges; @@ -53,6 +59,8 @@ typedef NS_ENUM(NSInteger, ListType) { ListTypeUnordered, ListTypeOrdered }; - (NSMutableParagraphStyle *)spacerStyleWithHeight:(CGFloat)height spacing:(CGFloat)spacing; - (NSMutableParagraphStyle *)blockSpacerStyleWithMargin:(CGFloat)margin; - (void)registerLinkRange:(NSRange)range url:(NSString *)url; +- (void)registerMentionRange:(NSRange)range userId:(NSString *)userId text:(NSString *)text; +- (void)registerCitationRange:(NSRange)range url:(NSString *)url text:(NSString *)text; - (void)applyLinkAttributesToString:(NSMutableAttributedString *)attributedString; - (void)registerHeadingRange:(NSRange)range level:(NSInteger)level text:(NSString *)text; diff --git a/ios/renderer/RenderContext.m b/ios/renderer/RenderContext.m index 68a6ff48..799b46de 100644 --- a/ios/renderer/RenderContext.m +++ b/ios/renderer/RenderContext.m @@ -17,6 +17,12 @@ - (instancetype)init if (self = [super init]) { _linkRanges = [NSMutableArray array]; _linkURLs = [NSMutableArray array]; + _mentionRanges = [NSMutableArray array]; + _mentionUserIds = [NSMutableArray array]; + _mentionTexts = [NSMutableArray array]; + _citationRanges = [NSMutableArray array]; + _citationURLs = [NSMutableArray array]; + _citationTexts = [NSMutableArray array]; _headingRanges = [NSMutableArray array]; _headingLevels = [NSMutableArray array]; _imageRanges = [NSMutableArray array]; @@ -105,6 +111,24 @@ - (void)registerLinkRange:(NSRange)range url:(NSString *)url [self.linkURLs addObject:url ?: @""]; } +- (void)registerMentionRange:(NSRange)range userId:(NSString *)userId text:(NSString *)text +{ + if (range.length == 0) + return; + [self.mentionRanges addObject:[NSValue valueWithRange:range]]; + [self.mentionUserIds addObject:userId ?: @""]; + [self.mentionTexts addObject:text ?: @""]; +} + +- (void)registerCitationRange:(NSRange)range url:(NSString *)url text:(NSString *)text +{ + if (range.length == 0) + return; + [self.citationRanges addObject:[NSValue valueWithRange:range]]; + [self.citationURLs addObject:url ?: @""]; + [self.citationTexts addObject:text ?: @""]; +} + - (void)applyLinkAttributesToString:(NSMutableAttributedString *)attributedString { NSUInteger length = attributedString.length; @@ -243,6 +267,12 @@ - (void)reset { [_linkRanges removeAllObjects]; [_linkURLs removeAllObjects]; + [_mentionRanges removeAllObjects]; + [_mentionUserIds removeAllObjects]; + [_mentionTexts removeAllObjects]; + [_citationRanges removeAllObjects]; + [_citationURLs removeAllObjects]; + [_citationTexts removeAllObjects]; [_headingRanges removeAllObjects]; [_headingLevels removeAllObjects]; [_imageRanges removeAllObjects]; diff --git a/ios/styles/StyleConfig.h b/ios/styles/StyleConfig.h index 7ad552d3..6b87e93c 100644 --- a/ios/styles/StyleConfig.h +++ b/ios/styles/StyleConfig.h @@ -364,5 +364,46 @@ - (void)setSpoilerParticleSpeed:(CGFloat)newValue; - (CGFloat)spoilerSolidBorderRadius; - (void)setSpoilerSolidBorderRadius:(CGFloat)newValue; +// Mention properties +- (RCTUIColor *)mentionColor; +- (void)setMentionColor:(RCTUIColor *)newValue; +- (RCTUIColor *)mentionBackgroundColor; +- (void)setMentionBackgroundColor:(RCTUIColor *)newValue; +- (RCTUIColor *)mentionBorderColor; +- (void)setMentionBorderColor:(RCTUIColor *)newValue; +- (CGFloat)mentionBorderWidth; +- (void)setMentionBorderWidth:(CGFloat)newValue; +- (CGFloat)mentionBorderRadius; +- (void)setMentionBorderRadius:(CGFloat)newValue; +- (CGFloat)mentionPaddingHorizontal; +- (void)setMentionPaddingHorizontal:(CGFloat)newValue; +- (CGFloat)mentionPaddingVertical; +- (void)setMentionPaddingVertical:(CGFloat)newValue; +- (NSString *)mentionFontFamily; +- (void)setMentionFontFamily:(NSString *)newValue; +- (NSString *)mentionFontWeight; +- (void)setMentionFontWeight:(NSString *)newValue; +- (CGFloat)mentionFontSize; +- (void)setMentionFontSize:(CGFloat)newValue; +- (CGFloat)mentionPressedOpacity; +- (void)setMentionPressedOpacity:(CGFloat)newValue; +- (UIFont *)mentionFont; +// Citation properties +- (RCTUIColor *)citationColor; +- (void)setCitationColor:(RCTUIColor *)newValue; +- (CGFloat)citationFontSizeMultiplier; +- (void)setCitationFontSizeMultiplier:(CGFloat)newValue; +- (CGFloat)citationBaselineOffsetPx; +- (void)setCitationBaselineOffsetPx:(CGFloat)newValue; +- (NSString *)citationFontWeight; +- (void)setCitationFontWeight:(NSString *)newValue; +- (BOOL)citationUnderline; +- (void)setCitationUnderline:(BOOL)newValue; +- (RCTUIColor *)citationBackgroundColor; +- (void)setCitationBackgroundColor:(RCTUIColor *)newValue; +- (CGFloat)citationPaddingHorizontal; +- (void)setCitationPaddingHorizontal:(CGFloat)newValue; +- (CGFloat)citationPaddingVertical; +- (void)setCitationPaddingVertical:(CGFloat)newValue; @end diff --git a/ios/styles/StyleConfig.mm b/ios/styles/StyleConfig.mm index 3b4e668e..a6c76b85 100644 --- a/ios/styles/StyleConfig.mm +++ b/ios/styles/StyleConfig.mm @@ -226,6 +226,29 @@ @implementation StyleConfig { CGFloat _spoilerParticleDensity; CGFloat _spoilerParticleSpeed; CGFloat _spoilerSolidBorderRadius; + // Mention properties + RCTUIColor *_mentionColor; + RCTUIColor *_mentionBackgroundColor; + RCTUIColor *_mentionBorderColor; + CGFloat _mentionBorderWidth; + CGFloat _mentionBorderRadius; + CGFloat _mentionPaddingHorizontal; + CGFloat _mentionPaddingVertical; + NSString *_mentionFontFamily; + NSString *_mentionFontWeight; + CGFloat _mentionFontSize; + CGFloat _mentionPressedOpacity; + UIFont *_mentionFont; + BOOL _mentionFontNeedsRecreation; + // Citation properties + RCTUIColor *_citationColor; + CGFloat _citationFontSizeMultiplier; + CGFloat _citationBaselineOffsetPx; + NSString *_citationFontWeight; + BOOL _citationUnderline; + RCTUIColor *_citationBackgroundColor; + CGFloat _citationPaddingHorizontal; + CGFloat _citationPaddingVertical; } - (instancetype)init @@ -255,6 +278,9 @@ - (instancetype)init _tableFontNeedsRecreation = YES; _tableHeaderFontNeedsRecreation = YES; _linkUnderline = YES; + _mentionFontNeedsRecreation = YES; + _citationFontSizeMultiplier = 0.7; + _mentionPressedOpacity = 0.6; return self; } @@ -282,6 +308,7 @@ - (void)setFontScaleMultiplier:(CGFloat)newValue _codeBlockFontNeedsRecreation = YES; _blockquoteFontNeedsRecreation = YES; _tableFontNeedsRecreation = YES; + _mentionFontNeedsRecreation = YES; } } @@ -316,6 +343,7 @@ - (void)setMaxFontSizeMultiplier:(CGFloat)newValue _codeBlockFontNeedsRecreation = YES; _blockquoteFontNeedsRecreation = YES; _tableFontNeedsRecreation = YES; + _mentionFontNeedsRecreation = YES; } } @@ -496,6 +524,27 @@ - (id)copyWithZone:(NSZone *)zone copy->_spoilerParticleSpeed = _spoilerParticleSpeed; copy->_spoilerSolidBorderRadius = _spoilerSolidBorderRadius; + copy->_mentionColor = [_mentionColor copy]; + copy->_mentionBackgroundColor = [_mentionBackgroundColor copy]; + copy->_mentionBorderColor = [_mentionBorderColor copy]; + copy->_mentionBorderWidth = _mentionBorderWidth; + copy->_mentionBorderRadius = _mentionBorderRadius; + copy->_mentionPaddingHorizontal = _mentionPaddingHorizontal; + copy->_mentionPaddingVertical = _mentionPaddingVertical; + copy->_mentionFontFamily = [_mentionFontFamily copy]; + copy->_mentionFontWeight = [_mentionFontWeight copy]; + copy->_mentionFontSize = _mentionFontSize; + copy->_mentionPressedOpacity = _mentionPressedOpacity; + copy->_mentionFontNeedsRecreation = YES; + copy->_citationColor = [_citationColor copy]; + copy->_citationFontSizeMultiplier = _citationFontSizeMultiplier; + copy->_citationBaselineOffsetPx = _citationBaselineOffsetPx; + copy->_citationFontWeight = [_citationFontWeight copy]; + copy->_citationUnderline = _citationUnderline; + copy->_citationBackgroundColor = [_citationBackgroundColor copy]; + copy->_citationPaddingHorizontal = _citationPaddingHorizontal; + copy->_citationPaddingVertical = _citationPaddingVertical; + return copy; } @@ -2390,4 +2439,221 @@ - (void)setSpoilerSolidBorderRadius:(CGFloat)newValue _spoilerSolidBorderRadius = newValue; } +// ── Mention ───────────────────────────────────────────────────────────── + +- (RCTUIColor *)mentionColor +{ + return _mentionColor; +} + +- (void)setMentionColor:(RCTUIColor *)newValue +{ + _mentionColor = newValue; +} + +- (RCTUIColor *)mentionBackgroundColor +{ + return _mentionBackgroundColor; +} + +- (void)setMentionBackgroundColor:(RCTUIColor *)newValue +{ + _mentionBackgroundColor = newValue; +} + +- (RCTUIColor *)mentionBorderColor +{ + return _mentionBorderColor; +} + +- (void)setMentionBorderColor:(RCTUIColor *)newValue +{ + _mentionBorderColor = newValue; +} + +- (CGFloat)mentionBorderWidth +{ + return _mentionBorderWidth; +} + +- (void)setMentionBorderWidth:(CGFloat)newValue +{ + _mentionBorderWidth = newValue; +} + +- (CGFloat)mentionBorderRadius +{ + return _mentionBorderRadius; +} + +- (void)setMentionBorderRadius:(CGFloat)newValue +{ + _mentionBorderRadius = newValue; +} + +- (CGFloat)mentionPaddingHorizontal +{ + return _mentionPaddingHorizontal; +} + +- (void)setMentionPaddingHorizontal:(CGFloat)newValue +{ + _mentionPaddingHorizontal = newValue; +} + +- (CGFloat)mentionPaddingVertical +{ + return _mentionPaddingVertical; +} + +- (void)setMentionPaddingVertical:(CGFloat)newValue +{ + _mentionPaddingVertical = newValue; +} + +- (NSString *)mentionFontFamily +{ + return _mentionFontFamily; +} + +- (void)setMentionFontFamily:(NSString *)newValue +{ + _mentionFontFamily = newValue; + _mentionFontNeedsRecreation = YES; +} + +- (NSString *)mentionFontWeight +{ + return _mentionFontWeight; +} + +- (void)setMentionFontWeight:(NSString *)newValue +{ + _mentionFontWeight = newValue; + _mentionFontNeedsRecreation = YES; +} + +- (CGFloat)mentionFontSize +{ + return _mentionFontSize; +} + +- (void)setMentionFontSize:(CGFloat)newValue +{ + _mentionFontSize = newValue; + _mentionFontNeedsRecreation = YES; +} + +- (CGFloat)mentionPressedOpacity +{ + return _mentionPressedOpacity; +} + +- (void)setMentionPressedOpacity:(CGFloat)newValue +{ + _mentionPressedOpacity = newValue; +} + +- (UIFont *)mentionFont +{ + if (_mentionFontNeedsRecreation || !_mentionFont) { + // Fall back to paragraph font size when mention.fontSize is 0 (inherit). + CGFloat size = _mentionFontSize > 0 ? _mentionFontSize : _paragraphFontSize; + if (size <= 0) { + size = 16; + } + _mentionFont = [RCTFont updateFont:nil + withFamily:_mentionFontFamily + size:@(size) + weight:normalizedFontWeight(_mentionFontWeight) + style:nil + variant:nil + scaleMultiplier:[self effectiveScaleMultiplierForFontSize:size]]; + _mentionFontNeedsRecreation = NO; + } + return _mentionFont; +} + +// ── Citation ──────────────────────────────────────────────────────────── + +- (RCTUIColor *)citationColor +{ + return _citationColor; +} + +- (void)setCitationColor:(RCTUIColor *)newValue +{ + _citationColor = newValue; +} + +- (CGFloat)citationFontSizeMultiplier +{ + return _citationFontSizeMultiplier > 0 ? _citationFontSizeMultiplier : 0.7; +} + +- (void)setCitationFontSizeMultiplier:(CGFloat)newValue +{ + _citationFontSizeMultiplier = newValue; +} + +- (CGFloat)citationBaselineOffsetPx +{ + return _citationBaselineOffsetPx; +} + +- (void)setCitationBaselineOffsetPx:(CGFloat)newValue +{ + _citationBaselineOffsetPx = newValue; +} + +- (NSString *)citationFontWeight +{ + return _citationFontWeight; +} + +- (void)setCitationFontWeight:(NSString *)newValue +{ + _citationFontWeight = newValue; +} + +- (BOOL)citationUnderline +{ + return _citationUnderline; +} + +- (void)setCitationUnderline:(BOOL)newValue +{ + _citationUnderline = newValue; +} + +- (RCTUIColor *)citationBackgroundColor +{ + return _citationBackgroundColor; +} + +- (void)setCitationBackgroundColor:(RCTUIColor *)newValue +{ + _citationBackgroundColor = newValue; +} + +- (CGFloat)citationPaddingHorizontal +{ + return _citationPaddingHorizontal; +} + +- (void)setCitationPaddingHorizontal:(CGFloat)newValue +{ + _citationPaddingHorizontal = newValue; +} + +- (CGFloat)citationPaddingVertical +{ + return _citationPaddingVertical; +} + +- (void)setCitationPaddingVertical:(CGFloat)newValue +{ + _citationPaddingVertical = newValue; +} + @end diff --git a/ios/utils/LinkTapUtils.h b/ios/utils/LinkTapUtils.h index f7b6c188..af48a9c9 100644 --- a/ios/utils/LinkTapUtils.h +++ b/ios/utils/LinkTapUtils.h @@ -14,7 +14,18 @@ NSString *_Nullable linkURLAtTapLocation(ENRMPlatformTextView *textView, ENRMTap /// Returns the link URL at the given character range, or nil if none found. NSString *_Nullable linkURLAtRange(ENRMPlatformTextView *textView, NSRange characterRange); -/// Returns YES if the point (in textView coordinates) is on a link or task list checkbox. +/// Returns the inline element (link, mention, or citation) at the tap location. +/// The out parameters are populated only when a matching element is present. +/// Returns YES when any element was matched, NO otherwise. +BOOL inlineElementAtTapLocation(ENRMPlatformTextView *textView, ENRMTapRecognizer *recognizer, + NSString *_Nullable *_Nullable outLinkURL, + NSString *_Nullable *_Nullable outMentionUserId, + NSString *_Nullable *_Nullable outMentionText, + NSString *_Nullable *_Nullable outCitationURL, + NSString *_Nullable *_Nullable outCitationText); + +/// Returns YES if the point (in textView coordinates) is on a link, mention, +/// citation, spoiler, or task list checkbox. BOOL isPointOnInteractiveElement(ENRMPlatformTextView *textView, CGPoint point); #ifdef __cplusplus diff --git a/ios/utils/LinkTapUtils.m b/ios/utils/LinkTapUtils.m index 26ea2d4d..0547091f 100644 --- a/ios/utils/LinkTapUtils.m +++ b/ios/utils/LinkTapUtils.m @@ -1,6 +1,7 @@ #import "LinkTapUtils.h" #import "ENRMSpoilerTapUtils.h" #import "ENRMTextHitTest.h" +#import "LinkRenderer.h" NSString *_Nullable linkURLAtTapLocation(ENRMPlatformTextView *textView, ENRMTapRecognizer *recognizer) { @@ -21,6 +22,48 @@ return [attrText attribute:@"linkURL" atIndex:characterRange.location effectiveRange:NULL]; } +BOOL inlineElementAtTapLocation(ENRMPlatformTextView *textView, ENRMTapRecognizer *recognizer, + NSString *_Nullable *_Nullable outLinkURL, + NSString *_Nullable *_Nullable outMentionUserId, + NSString *_Nullable *_Nullable outMentionText, + NSString *_Nullable *_Nullable outCitationURL, + NSString *_Nullable *_Nullable outCitationText) +{ + NSUInteger characterIndex = ENRMCharacterIndexForTap(textView, recognizer); + if (characterIndex == NSNotFound) + return NO; + + NSAttributedString *attrText = ENRMGetAttributedText(textView); + NSDictionary *attrs = [attrText attributesAtIndex:characterIndex effectiveRange:NULL]; + + NSString *mentionUserId = attrs[ENRMMentionUserIdAttributeName]; + if (mentionUserId) { + if (outMentionUserId) + *outMentionUserId = mentionUserId; + if (outMentionText) + *outMentionText = attrs[ENRMMentionTextAttributeName] ?: @""; + return YES; + } + + NSString *citationURL = attrs[ENRMCitationURLAttributeName]; + if (citationURL) { + if (outCitationURL) + *outCitationURL = citationURL; + if (outCitationText) + *outCitationText = attrs[ENRMCitationTextAttributeName] ?: @""; + return YES; + } + + NSString *linkURL = attrs[@"linkURL"]; + if (linkURL) { + if (outLinkURL) + *outLinkURL = linkURL; + return YES; + } + + return NO; +} + BOOL isPointOnInteractiveElement(ENRMPlatformTextView *textView, CGPoint point) { NSUInteger charIndex = ENRMCharacterIndexAtPoint(textView, point); @@ -28,5 +71,7 @@ BOOL isPointOnInteractiveElement(ENRMPlatformTextView *textView, CGPoint point) return NO; NSDictionary *attrs = [ENRMGetAttributedText(textView) attributesAtIndex:charIndex effectiveRange:NULL]; - return attrs[@"linkURL"] != nil || [attrs[@"TaskItem"] boolValue] || attrs[SpoilerAttributeName] != nil; + return attrs[@"linkURL"] != nil || attrs[ENRMMentionUserIdAttributeName] != nil || + attrs[ENRMCitationURLAttributeName] != nil || [attrs[@"TaskItem"] boolValue] || + attrs[SpoilerAttributeName] != nil; } diff --git a/ios/utils/StylePropsUtils.h b/ios/utils/StylePropsUtils.h index f74311aa..f32b9fd5 100644 --- a/ios/utils/StylePropsUtils.h +++ b/ios/utils/StylePropsUtils.h @@ -1064,5 +1064,142 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty changed = YES; } + // ── Mention ───────────────────────────────────────────────────────────── + + if (newStyle.mention.color != oldStyle.mention.color) { + if (newStyle.mention.color) { + [config setMentionColor:RCTUIColorFromSharedColor(newStyle.mention.color)]; + } else { + [config setMentionColor:nullptr]; + } + changed = YES; + } + + if (newStyle.mention.backgroundColor != oldStyle.mention.backgroundColor) { + if (newStyle.mention.backgroundColor) { + [config setMentionBackgroundColor:RCTUIColorFromSharedColor(newStyle.mention.backgroundColor)]; + } else { + [config setMentionBackgroundColor:nullptr]; + } + changed = YES; + } + + if (newStyle.mention.borderColor != oldStyle.mention.borderColor) { + if (newStyle.mention.borderColor) { + [config setMentionBorderColor:RCTUIColorFromSharedColor(newStyle.mention.borderColor)]; + } else { + [config setMentionBorderColor:nullptr]; + } + changed = YES; + } + + if (newStyle.mention.borderWidth != oldStyle.mention.borderWidth) { + [config setMentionBorderWidth:newStyle.mention.borderWidth]; + changed = YES; + } + + if (newStyle.mention.borderRadius != oldStyle.mention.borderRadius) { + [config setMentionBorderRadius:newStyle.mention.borderRadius]; + changed = YES; + } + + if (newStyle.mention.paddingHorizontal != oldStyle.mention.paddingHorizontal) { + [config setMentionPaddingHorizontal:newStyle.mention.paddingHorizontal]; + changed = YES; + } + + if (newStyle.mention.paddingVertical != oldStyle.mention.paddingVertical) { + [config setMentionPaddingVertical:newStyle.mention.paddingVertical]; + changed = YES; + } + + if (newStyle.mention.fontFamily != oldStyle.mention.fontFamily) { + if (!newStyle.mention.fontFamily.empty()) { + NSString *fontFamily = [[NSString alloc] initWithUTF8String:newStyle.mention.fontFamily.c_str()]; + [config setMentionFontFamily:fontFamily]; + } else { + [config setMentionFontFamily:nullptr]; + } + changed = YES; + } + + if (newStyle.mention.fontWeight != oldStyle.mention.fontWeight) { + if (!newStyle.mention.fontWeight.empty()) { + NSString *fontWeight = [[NSString alloc] initWithUTF8String:newStyle.mention.fontWeight.c_str()]; + [config setMentionFontWeight:fontWeight]; + } else { + [config setMentionFontWeight:nullptr]; + } + changed = YES; + } + + if (newStyle.mention.fontSize != oldStyle.mention.fontSize) { + [config setMentionFontSize:newStyle.mention.fontSize]; + changed = YES; + } + + if (newStyle.mention.pressedOpacity != oldStyle.mention.pressedOpacity) { + [config setMentionPressedOpacity:newStyle.mention.pressedOpacity]; + changed = YES; + } + + // ── Citation ──────────────────────────────────────────────────────────── + + if (newStyle.citation.color != oldStyle.citation.color) { + if (newStyle.citation.color) { + [config setCitationColor:RCTUIColorFromSharedColor(newStyle.citation.color)]; + } else { + [config setCitationColor:nullptr]; + } + changed = YES; + } + + if (newStyle.citation.fontSizeMultiplier != oldStyle.citation.fontSizeMultiplier) { + [config setCitationFontSizeMultiplier:newStyle.citation.fontSizeMultiplier]; + changed = YES; + } + + if (newStyle.citation.baselineOffsetPx != oldStyle.citation.baselineOffsetPx) { + [config setCitationBaselineOffsetPx:newStyle.citation.baselineOffsetPx]; + changed = YES; + } + + if (newStyle.citation.fontWeight != oldStyle.citation.fontWeight) { + if (!newStyle.citation.fontWeight.empty()) { + NSString *fontWeight = [[NSString alloc] initWithUTF8String:newStyle.citation.fontWeight.c_str()]; + [config setCitationFontWeight:fontWeight]; + } else { + [config setCitationFontWeight:nullptr]; + } + changed = YES; + } + + { + BOOL newUnderline = newStyle.citation.underline ? YES : NO; + if (newStyle.citation.underline != oldStyle.citation.underline || [config citationUnderline] != newUnderline) { + [config setCitationUnderline:newUnderline]; + changed = YES; + } + } + + if (newStyle.citation.backgroundColor != oldStyle.citation.backgroundColor) { + if (newStyle.citation.backgroundColor) { + [config setCitationBackgroundColor:RCTUIColorFromSharedColor(newStyle.citation.backgroundColor)]; + } else { + [config setCitationBackgroundColor:nullptr]; + } + changed = YES; + } + + if (newStyle.citation.paddingHorizontal != oldStyle.citation.paddingHorizontal) { + [config setCitationPaddingHorizontal:newStyle.citation.paddingHorizontal]; + changed = YES; + } + + if (newStyle.citation.paddingVertical != oldStyle.citation.paddingVertical) { + [config setCitationPaddingVertical:newStyle.citation.paddingVertical]; + changed = YES; + } + return changed; } diff --git a/ios/views/TableContainerView.h b/ios/views/TableContainerView.h index b444236b..b133aee0 100644 --- a/ios/views/TableContainerView.h +++ b/ios/views/TableContainerView.h @@ -7,6 +7,8 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^TableLinkPressBlock)(NSString *url); +typedef void (^TableMentionPressBlock)(NSString *userId, NSString *text); +typedef void (^TableCitationPressBlock)(NSString *url, NSString *text); @interface TableContainerView : RCTUIView @@ -23,6 +25,8 @@ typedef void (^TableLinkPressBlock)(NSString *url); @property (nonatomic, copy, nullable) TableLinkPressBlock onLinkPress; @property (nonatomic, copy, nullable) TableLinkPressBlock onLinkLongPress; +@property (nonatomic, copy, nullable) TableMentionPressBlock onMentionPress; +@property (nonatomic, copy, nullable) TableCitationPressBlock onCitationPress; @property (nonatomic, assign) BOOL enableLinkPreview; diff --git a/ios/views/TableContainerView.m b/ios/views/TableContainerView.m index d538e5e0..181af524 100644 --- a/ios/views/TableContainerView.m +++ b/ios/views/TableContainerView.m @@ -409,9 +409,21 @@ - (UITextView *)createCellTextView - (void)cellTextTapped:(UITapGestureRecognizer *)recognizer { UITextView *textView = (UITextView *)recognizer.view; - NSString *url = linkURLAtTapLocation(textView, recognizer); - if (url && self.onLinkPress) - self.onLinkPress(url); + NSString *linkURL = nil; + NSString *mentionUserId = nil; + NSString *mentionText = nil; + NSString *citationURL = nil; + NSString *citationText = nil; + if (inlineElementAtTapLocation(textView, recognizer, &linkURL, &mentionUserId, &mentionText, &citationURL, + &citationText)) { + if (mentionUserId && self.onMentionPress) { + self.onMentionPress(mentionUserId, mentionText ?: @""); + } else if (citationURL && self.onCitationPress) { + self.onCitationPress(citationURL, citationText ?: @""); + } else if (linkURL && self.onLinkPress) { + self.onLinkPress(linkURL); + } + } } - (BOOL)textView:(UITextView *)textView diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index e81e2d96..f569500f 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -152,6 +152,31 @@ interface SpoilerStyleInternal { solid: SpoilerSolidStyleInternal; } +interface MentionStyleInternal { + color: ColorValue; + backgroundColor: ColorValue; + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; + paddingHorizontal: CodegenTypes.Float; + paddingVertical: CodegenTypes.Float; + fontFamily: string; + fontWeight: string; + fontSize: CodegenTypes.Float; + pressedOpacity: CodegenTypes.Float; +} + +interface CitationStyleInternal { + color: ColorValue; + fontSizeMultiplier: CodegenTypes.Float; + baselineOffsetPx: CodegenTypes.Float; + fontWeight: string; + underline: boolean; + backgroundColor: ColorValue; + paddingHorizontal: CodegenTypes.Float; + paddingVertical: CodegenTypes.Float; +} + export interface MarkdownStyleInternal { paragraph: ParagraphStyleInternal; h1: HeadingStyleInternal; @@ -177,6 +202,8 @@ export interface MarkdownStyleInternal { math: MathStyleInternal; inlineMath: InlineMathStyleInternal; spoiler: SpoilerStyleInternal; + mention: MentionStyleInternal; + citation: CitationStyleInternal; } export interface LinkPressEvent { @@ -187,6 +214,16 @@ export interface LinkLongPressEvent { url: string; } +export interface MentionPressEvent { + userId: string; + text: string; +} + +export interface CitationPressEvent { + url: string; + text: string; +} + export interface TaskListItemPressEvent { index: CodegenTypes.Int32; checked: boolean; @@ -253,6 +290,14 @@ export interface NativeProps extends ViewProps { * Receives the 0-based task index, current checked state, and the item's plain text. */ onTaskListItemPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when an inline mention pill (`mention://`) is pressed. + */ + onMentionPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when an inline citation (`citation://`) is pressed. + */ + onCitationPress?: CodegenTypes.BubblingEventHandler; /** * Controls whether the system link preview is shown on long press (iOS only). * diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index a76a48d6..295649f9 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -152,6 +152,31 @@ interface SpoilerStyleInternal { solid: SpoilerSolidStyleInternal; } +interface MentionStyleInternal { + color: ColorValue; + backgroundColor: ColorValue; + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; + paddingHorizontal: CodegenTypes.Float; + paddingVertical: CodegenTypes.Float; + fontFamily: string; + fontWeight: string; + fontSize: CodegenTypes.Float; + pressedOpacity: CodegenTypes.Float; +} + +interface CitationStyleInternal { + color: ColorValue; + fontSizeMultiplier: CodegenTypes.Float; + baselineOffsetPx: CodegenTypes.Float; + fontWeight: string; + underline: boolean; + backgroundColor: ColorValue; + paddingHorizontal: CodegenTypes.Float; + paddingVertical: CodegenTypes.Float; +} + export interface MarkdownStyleInternal { paragraph: ParagraphStyleInternal; h1: HeadingStyleInternal; @@ -177,6 +202,8 @@ export interface MarkdownStyleInternal { math: MathStyleInternal; inlineMath: InlineMathStyleInternal; spoiler: SpoilerStyleInternal; + mention: MentionStyleInternal; + citation: CitationStyleInternal; } export interface LinkPressEvent { @@ -187,6 +214,16 @@ export interface LinkLongPressEvent { url: string; } +export interface MentionPressEvent { + userId: string; + text: string; +} + +export interface CitationPressEvent { + url: string; + text: string; +} + export interface TaskListItemPressEvent { index: CodegenTypes.Int32; checked: boolean; @@ -253,6 +290,14 @@ export interface NativeProps extends ViewProps { * Receives the 0-based task index, current checked state, and the item's plain text. */ onTaskListItemPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when an inline mention pill (`mention://`) is pressed. + */ + onMentionPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when an inline citation (`citation://`) is pressed. + */ + onCitationPress?: CodegenTypes.BubblingEventHandler; /** * Controls whether the system link preview is shown on long press (iOS only). * diff --git a/src/index.tsx b/src/index.tsx index 4ff26c42..284ce789 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -9,6 +9,8 @@ export type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent, + MentionPressEvent, + CitationPressEvent, } from './types/events'; export { EnrichedMarkdownInput } from './EnrichedMarkdownInput'; diff --git a/src/index.web.tsx b/src/index.web.tsx index 4b4305cf..23e14a79 100644 --- a/src/index.web.tsx +++ b/src/index.web.tsx @@ -5,4 +5,6 @@ export type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent, + MentionPressEvent, + CitationPressEvent, } from './types/events'; diff --git a/src/native/EnrichedMarkdownText.tsx b/src/native/EnrichedMarkdownText.tsx index 236c2b7c..dde302cc 100644 --- a/src/native/EnrichedMarkdownText.tsx +++ b/src/native/EnrichedMarkdownText.tsx @@ -13,12 +13,20 @@ import type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent, + MentionPressEvent, + CitationPressEvent, OnContextMenuItemPressEvent, } from '../types/events'; export type { MarkdownStyle, Md4cFlags }; export type { EnrichedMarkdownTextProps, ContextMenuItem }; -export type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent }; +export type { + LinkPressEvent, + LinkLongPressEvent, + TaskListItemPressEvent, + MentionPressEvent, + CitationPressEvent, +}; const defaultMd4cFlags: Md4cFlags = { underline: false, @@ -32,6 +40,8 @@ export const EnrichedMarkdownText = ({ onLinkPress, onLinkLongPress, onTaskListItemPress, + onMentionPress, + onCitationPress, enableLinkPreview, selectable = true, md4cFlags = defaultMd4cFlags, @@ -120,12 +130,30 @@ export const EnrichedMarkdownText = ({ [onTaskListItemPress] ); + const handleMentionPress = useCallback( + (e: NativeSyntheticEvent) => { + const { userId, text } = e.nativeEvent; + onMentionPress?.({ userId, text }); + }, + [onMentionPress] + ); + + const handleCitationPress = useCallback( + (e: NativeSyntheticEvent) => { + const { url, text } = e.nativeEvent; + onCitationPress?.({ url, text }); + }, + [onCitationPress] + ); + const sharedProps = { markdown, markdownStyle: normalizedStyle, onLinkPress: handleLinkPress, onLinkLongPress: handleLinkLongPress, onTaskListItemPress: handleTaskListItemPress, + onMentionPress: onMentionPress ? handleMentionPress : undefined, + onCitationPress: onCitationPress ? handleCitationPress : undefined, enableLinkPreview: onLinkLongPress == null && (enableLinkPreview ?? true), selectable, md4cFlags: normalizedMd4cFlags, diff --git a/src/normalizeMarkdownStyle.ts b/src/normalizeMarkdownStyle.ts index b9b1c2fa..c904cb67 100644 --- a/src/normalizeMarkdownStyle.ts +++ b/src/normalizeMarkdownStyle.ts @@ -204,6 +204,29 @@ const DEFAULT_NORMALIZED_STYLE = Object.freeze({ particles: { density: 8, speed: 20 }, solid: { borderRadius: 4 }, }, + mention: { + color: normalizeColor('#1D4ED8')!, + backgroundColor: normalizeColor('#DBEAFE')!, + borderColor: normalizeColor('#BFDBFE')!, + borderWidth: 0, + borderRadius: 999, + paddingHorizontal: 6, + paddingVertical: 1, + fontFamily: '', + fontWeight: '500', + fontSize: 0, + pressedOpacity: 0.6, + }, + citation: { + color: normalizeColor('#2563EB')!, + fontSizeMultiplier: 0.7, + baselineOffsetPx: 0, + fontWeight: '', + underline: false, + backgroundColor: 'transparent', + paddingHorizontal: 0, + paddingVertical: 0, + }, }) as MarkdownStyleInternal; const refCache = new WeakMap(); diff --git a/src/normalizeMarkdownStyle.web.ts b/src/normalizeMarkdownStyle.web.ts index 5167d3f4..bf0d5801 100644 --- a/src/normalizeMarkdownStyle.web.ts +++ b/src/normalizeMarkdownStyle.web.ts @@ -185,6 +185,29 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ particles: { density: 8, speed: 20 }, solid: { borderRadius: 4 }, }, + mention: { + color: '#1D4ED8', + backgroundColor: '#DBEAFE', + borderColor: '#BFDBFE', + borderWidth: 0, + borderRadius: 999, + paddingHorizontal: 6, + paddingVertical: 1, + fontFamily: '', + fontWeight: '500', + fontSize: 0, + pressedOpacity: 0.6, + }, + citation: { + color: '#2563EB', + fontSizeMultiplier: 0.7, + baselineOffsetPx: 0, + fontWeight: '', + underline: false, + backgroundColor: 'transparent', + paddingHorizontal: 0, + paddingVertical: 0, + }, }); const refCache = new WeakMap(); diff --git a/src/types/MarkdownStyle.ts b/src/types/MarkdownStyle.ts index 75fb5185..e2b8a194 100644 --- a/src/types/MarkdownStyle.ts +++ b/src/types/MarkdownStyle.ts @@ -180,6 +180,54 @@ interface SpoilerStyle { solid?: SpoilerSolidStyle; } +interface MentionStyle { + color?: string; + backgroundColor?: string; + borderColor?: string; + borderWidth?: number; + borderRadius?: number; + paddingHorizontal?: number; + paddingVertical?: number; + fontFamily?: string; + fontWeight?: string; + fontSize?: number; + /** + * Alpha multiplier applied while the pill is pressed (0-1). + * Provides built-in press feedback matching native expectations. + * @default 0.6 + */ + pressedOpacity?: number; +} + +interface CitationStyle { + color?: string; + /** + * Multiplier applied to the surrounding font size. + * @default 0.7 + */ + fontSizeMultiplier?: number; + /** + * Explicit baseline offset in px. When undefined, derived from font metrics + * for iOS/Android parity. + */ + baselineOffsetPx?: number; + fontWeight?: string; + underline?: boolean; + backgroundColor?: string; + /** + * Horizontal padding (in px) applied to the citation marker. Increases both + * the tap target and the width of the background when one is set. + * @default 0 + */ + paddingHorizontal?: number; + /** + * Vertical padding (in px) applied to the citation marker. Increases both + * the tap target and the height of the background when one is set. + * @default 0 + */ + paddingVertical?: number; +} + export interface MarkdownStyle { paragraph?: ParagraphStyle; h1?: HeadingStyle; @@ -205,6 +253,8 @@ export interface MarkdownStyle { math?: MathStyle; inlineMath?: InlineMathStyle; spoiler?: SpoilerStyle; + mention?: MentionStyle; + citation?: CitationStyle; } /** diff --git a/src/types/MarkdownStyleInternal.ts b/src/types/MarkdownStyleInternal.ts index 8cb52ae0..7befe862 100644 --- a/src/types/MarkdownStyleInternal.ts +++ b/src/types/MarkdownStyleInternal.ts @@ -152,6 +152,31 @@ interface SpoilerStyleInternal { solid: SpoilerSolidStyleInternal; } +interface MentionStyleInternal { + color: string; + backgroundColor: string; + borderColor: string; + borderWidth: number; + borderRadius: number; + paddingHorizontal: number; + paddingVertical: number; + fontFamily: string; + fontWeight: string; + fontSize: number; + pressedOpacity: number; +} + +interface CitationStyleInternal { + color: string; + fontSizeMultiplier: number; + baselineOffsetPx: number; + fontWeight: string; + underline: boolean; + backgroundColor: string; + paddingHorizontal: number; + paddingVertical: number; +} + export interface MarkdownStyleInternal { paragraph: ParagraphStyleInternal; h1: HeadingStyleInternal; @@ -177,4 +202,6 @@ export interface MarkdownStyleInternal { math: MathStyleInternal; inlineMath: InlineMathStyleInternal; spoiler: SpoilerStyleInternal; + mention: MentionStyleInternal; + citation: CitationStyleInternal; } diff --git a/src/types/MarkdownTextProps.ts b/src/types/MarkdownTextProps.ts index d450699e..a2480ef5 100644 --- a/src/types/MarkdownTextProps.ts +++ b/src/types/MarkdownTextProps.ts @@ -4,6 +4,8 @@ import type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent, + MentionPressEvent, + CitationPressEvent, } from './events'; /** @@ -69,6 +71,20 @@ export interface EnrichedMarkdownTextProps extends Omit { * @platform ios, android, web */ onTaskListItemPress?: (event: TaskListItemPressEvent) => void; + /** + * Callback fired when an inline mention pill is pressed. + * Mentions are authored as `[label](mention://)` in markdown; the + * renderer draws them as a pill and surfaces the user id separately. + * @platform ios, android, web + */ + onMentionPress?: (event: MentionPressEvent) => void; + /** + * Callback fired when an inline citation is pressed. + * Citations are authored as `[label](citation://)` in markdown; the + * renderer draws them as a superscript marker and surfaces the target url. + * @platform ios, android, web + */ + onCitationPress?: (event: CitationPressEvent) => void; /** * Controls whether the system link preview is shown on long press (iOS only). * diff --git a/src/types/MarkdownTextProps.web.ts b/src/types/MarkdownTextProps.web.ts index fa9d4014..d82dfd4b 100644 --- a/src/types/MarkdownTextProps.web.ts +++ b/src/types/MarkdownTextProps.web.ts @@ -4,6 +4,8 @@ import type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent, + MentionPressEvent, + CitationPressEvent, } from './events'; export interface EnrichedMarkdownTextProps @@ -56,6 +58,18 @@ export interface EnrichedMarkdownTextProps * @platform ios, android, web */ onTaskListItemPress?: (event: TaskListItemPressEvent) => void; + /** + * Callback fired when an inline mention pill is pressed. + * Mentions are authored as `[label](mention://)` in markdown. + * @platform ios, android, web + */ + onMentionPress?: (event: MentionPressEvent) => void; + /** + * Callback fired when an inline citation is pressed. + * Citations are authored as `[label](citation://)` in markdown. + * @platform ios, android, web + */ + onCitationPress?: (event: CitationPressEvent) => void; /** * Controls text selection. * - iOS: Controls text selection and link previews on long press. diff --git a/src/types/events.ts b/src/types/events.ts index 11f876c7..d2b22d0f 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -12,6 +12,16 @@ export interface TaskListItemPressEvent { text: string; } +export interface MentionPressEvent { + userId: string; + text: string; +} + +export interface CitationPressEvent { + url: string; + text: string; +} + /** * Native-level context menu item config sent to the native component. * Does not include the `onPress` callback — callbacks are managed on the JS side. diff --git a/src/web/EnrichedMarkdownText.tsx b/src/web/EnrichedMarkdownText.tsx index 70eb3a4c..80fc2e14 100644 --- a/src/web/EnrichedMarkdownText.tsx +++ b/src/web/EnrichedMarkdownText.tsx @@ -20,6 +20,8 @@ export const EnrichedMarkdownText = ({ onLinkPress, onLinkLongPress, onTaskListItemPress, + onMentionPress, + onCitationPress, allowTrailingMargin = false, containerStyle, selectable = true, @@ -74,8 +76,20 @@ export const EnrichedMarkdownText = ({ }, [markdown, underline, latexMath]); const callbacks = useMemo( - () => ({ onLinkPress, onLinkLongPress, onTaskListItemPress }), - [onLinkPress, onLinkLongPress, onTaskListItemPress] + () => ({ + onLinkPress, + onLinkLongPress, + onTaskListItemPress, + onMentionPress, + onCitationPress, + }), + [ + onLinkPress, + onLinkLongPress, + onTaskListItemPress, + onMentionPress, + onCitationPress, + ] ); const capabilities = useMemo(() => ({ katex }), [katex]); diff --git a/src/web/renderers/InlineRenderers.tsx b/src/web/renderers/InlineRenderers.tsx index f25cb2a6..ecf7d764 100644 --- a/src/web/renderers/InlineRenderers.tsx +++ b/src/web/renderers/InlineRenderers.tsx @@ -3,6 +3,15 @@ import type { RendererProps, RendererMap } from '../types'; import { extractNodeText } from '../utils'; import { KaTeXRenderer } from './KaTeXRenderer'; +const MENTION_SCHEME = 'mention://'; +const CITATION_SCHEME = 'citation://'; +const MENTION_CLASS = 'enriched-mention'; + +// The `:active` rule honors the consumer-configured `pressedOpacity` via a CSS +// variable the mention span sets inline. Rendered as a ` + + {displayText} + + + ); +} + +function CitationRenderer({ + url, + styles, + callbacks, + node, + renderChildren, +}: SchemeRendererProps) { + const targetUrl = url.slice(CITATION_SCHEME.length); + const displayText = extractNodeText(node); + + const handleClick = (event: MouseEvent) => { + event.preventDefault(); + callbacks.onCitationPress?.({ url: targetUrl, text: displayText }); + }; + + return ( + + {renderChildren(node)} + + ); +} + function LatexMathInlineRenderer({ node, styles, diff --git a/src/web/styles.ts b/src/web/styles.ts index 4d0ff8e0..2ad3113f 100644 --- a/src/web/styles.ts +++ b/src/web/styles.ts @@ -244,6 +244,53 @@ function linkStyle(style: MarkdownStyleInternal): CSSProperties { }; } +function mentionStyle(style: MarkdownStyleInternal): CSSProperties { + const mention = style.mention; + return { + display: 'inline-flex', + alignItems: 'center', + boxSizing: 'border-box', + color: mention.color, + backgroundColor: mention.backgroundColor, + borderColor: mention.borderColor, + borderStyle: mention.borderWidth > 0 ? 'solid' : undefined, + borderWidth: mention.borderWidth, + borderRadius: mention.borderRadius, + paddingInline: mention.paddingHorizontal, + paddingBlock: mention.paddingVertical, + fontFamily: normalizeFontFamily(mention.fontFamily), + fontWeight: normalizeFontWeight(mention.fontWeight), + fontSize: mention.fontSize || undefined, + cursor: 'pointer', + userSelect: 'none', + transition: 'opacity 0.12s ease-in-out', + lineHeight: 1, + }; +} + +function citationStyle(style: MarkdownStyleInternal): CSSProperties { + const citation = style.citation; + const hasBackground = + !!citation.backgroundColor && citation.backgroundColor !== 'transparent'; + return { + color: citation.color, + fontSize: `calc(1em * ${citation.fontSizeMultiplier})`, + verticalAlign: 'baseline', + position: 'relative', + top: citation.baselineOffsetPx ? -citation.baselineOffsetPx : undefined, + fontWeight: normalizeFontWeight(citation.fontWeight), + backgroundColor: hasBackground ? citation.backgroundColor : undefined, + textDecoration: citation.underline ? 'underline' : undefined, + paddingInline: citation.paddingHorizontal || undefined, + paddingBlock: citation.paddingVertical || undefined, + // Round the chip when a background is set; a small radius keeps inline + // citation markers looking like pills rather than square boxes. + borderRadius: + hasBackground && citation.paddingHorizontal > 0 ? 999 : undefined, + cursor: 'pointer', + }; +} + function strikethroughStyle(style: MarkdownStyleInternal): CSSProperties { return { textDecorationLine: 'line-through', @@ -410,6 +457,9 @@ export interface Styles { tableHeaderCell: Record; tableCell: Record; taskCheckbox: CSSProperties; + mention: CSSProperties; + citation: CSSProperties; + mentionPressedOpacity: number; } type ColumnAlign = 'left' | 'center' | 'right' | 'default'; @@ -462,6 +512,9 @@ export function buildStyles(style: MarkdownStyleInternal): Styles { default: tableCellStyle(style, 'default'), }, taskCheckbox: taskCheckboxStyle(style), + mention: mentionStyle(style), + citation: citationStyle(style), + mentionPressedOpacity: style.mention.pressedOpacity, }; stylesStore.set(style, result); diff --git a/src/web/types.ts b/src/web/types.ts index 3a297c54..50d7469e 100644 --- a/src/web/types.ts +++ b/src/web/types.ts @@ -5,6 +5,8 @@ import type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent, + MentionPressEvent, + CitationPressEvent, } from '../types/events'; import type { KaTeXInstance } from './katex'; @@ -68,6 +70,8 @@ export interface RendererCallbacks { onLinkPress?: (event: LinkPressEvent) => void; onLinkLongPress?: (event: LinkLongPressEvent) => void; onTaskListItemPress?: (event: TaskListItemPressEvent) => void; + onMentionPress?: (event: MentionPressEvent) => void; + onCitationPress?: (event: CitationPressEvent) => void; } export interface RenderCapabilities { From 45cb7fd07af5773a63efb10bb3342c39b54e71d5 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Thu, 16 Apr 2026 19:22:12 -0700 Subject: [PATCH 02/23] fix mention press signature --- .../swmansion/enriched/markdown/EnrichedMarkdown.kt | 2 +- .../markdown/EnrichedMarkdownInternalText.kt | 4 ++-- .../enriched/markdown/EnrichedMarkdownManager.kt | 4 ++-- .../enriched/markdown/EnrichedMarkdownText.kt | 6 +++--- .../enriched/markdown/events/MentionPressEvent.kt | 4 ++-- .../enriched/markdown/renderer/LinkRenderer.kt | 4 ++-- .../enriched/markdown/spans/MentionSpan.kt | 4 ++-- .../utils/common/MarkdownViewManagerUtils.kt | 4 ++-- .../enriched/markdown/utils/text/view/LinkEvents.kt | 4 ++-- .../utils/text/view/LinkLongPressMovementMethod.kt | 4 ++-- .../enriched/markdown/views/TableContainerView.kt | 4 ++-- apps/example/src/sampleMarkdown.ts | 2 +- ios/EnrichedMarkdown.mm | 12 ++++++------ ios/EnrichedMarkdownText.mm | 8 ++++---- ios/attachments/ENRMMentionAttachment.h | 6 ++---- ios/attachments/ENRMMentionAttachment.m | 6 +++--- ios/renderer/LinkRenderer.h | 2 +- ios/renderer/LinkRenderer.m | 10 +++++----- ios/renderer/RenderContext.h | 4 ++-- ios/renderer/RenderContext.m | 8 ++++---- ios/utils/LinkTapUtils.h | 3 +-- ios/utils/LinkTapUtils.m | 13 ++++++------- ios/views/TableContainerView.h | 2 +- ios/views/TableContainerView.m | 8 ++++---- src/EnrichedMarkdownNativeComponent.ts | 4 ++-- src/EnrichedMarkdownTextNativeComponent.ts | 4 ++-- src/native/EnrichedMarkdownText.tsx | 4 ++-- src/types/MarkdownTextProps.ts | 4 ++-- src/types/MarkdownTextProps.web.ts | 2 +- src/types/events.ts | 2 +- src/web/renderers/InlineRenderers.tsx | 6 +++--- 31 files changed, 75 insertions(+), 79 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt index d3732198..0fece653 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt @@ -138,7 +138,7 @@ class EnrichedMarkdown onLinkLongPressCallback = callback } - fun setOnMentionPressCallback(callback: ((userId: String, text: String) -> Unit)?) { + fun setOnMentionPressCallback(callback: ((url: String, text: String) -> Unit)?) { onMentionPressCallback = callback } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt index f8291343..8952110b 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownInternalText.kt @@ -38,7 +38,7 @@ class EnrichedMarkdownInternalText checkboxTouchHelper.onCheckboxTap = value } - var onMentionPressCallback: ((userId: String, text: String) -> Unit)? = null + var onMentionPressCallback: ((url: String, text: String) -> Unit)? = null var onCitationPressCallback: ((url: String, text: String) -> Unit)? = null override val segmentMarginBottom: Int get() = lastElementMarginBottom.toInt() @@ -67,7 +67,7 @@ class EnrichedMarkdownInternalText val method = (movementMethod as? LinkLongPressMovementMethod) ?: LinkLongPressMovementMethod.createInstance().also { movementMethod = it } - method.onMentionTap = { userId, mentionText -> onMentionPressCallback?.invoke(userId, mentionText) } + method.onMentionTap = { url, mentionText -> onMentionPressCallback?.invoke(url, mentionText) } method.onCitationTap = { url, citationText -> onCitationPressCallback?.invoke(url, citationText) } spoilerOverlayDrawer = SpoilerOverlayDrawer.setupIfNeeded(this, styledText, spoilerOverlayDrawer, spoilerOverlay) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt index b470babf..f391ae25 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt @@ -56,8 +56,8 @@ class EnrichedMarkdownManager : emitLinkLongPress(view, url) } - view?.setOnMentionPressCallback { userId, text -> - emitMentionPress(view, userId, text) + view?.setOnMentionPressCallback { url, text -> + emitMentionPress(view, url, text) } view?.setOnCitationPressCallback { url, text -> diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt index f716cf6c..8bc7931e 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt @@ -233,7 +233,7 @@ class EnrichedMarkdownText val method = (movementMethod as? LinkLongPressMovementMethod) ?: LinkLongPressMovementMethod.createInstance().also { movementMethod = it } - method.onMentionTap = { userId, mentionText -> emitOnMentionPress(userId, mentionText) } + method.onMentionTap = { url, mentionText -> emitOnMentionPress(url, mentionText) } method.onCitationTap = { url, citationText -> emitOnCitationPress(url, citationText) } renderer.getCollectedImageSpans().forEach { span -> @@ -271,10 +271,10 @@ class EnrichedMarkdownText } fun emitOnMentionPress( - userId: String, + url: String, text: String, ) { - emitMentionPressEvent(userId, text) + emitMentionPressEvent(url, text) } fun emitOnCitationPress( diff --git a/android/src/main/java/com/swmansion/enriched/markdown/events/MentionPressEvent.kt b/android/src/main/java/com/swmansion/enriched/markdown/events/MentionPressEvent.kt index e95c5f4f..211d608e 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/events/MentionPressEvent.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/events/MentionPressEvent.kt @@ -7,14 +7,14 @@ import com.facebook.react.uimanager.events.Event class MentionPressEvent( surfaceId: Int, viewId: Int, - private val userId: String, + private val url: String, private val text: String, ) : Event(surfaceId, viewId) { override fun getEventName(): String = EVENT_NAME override fun getEventData(): WritableMap { val eventData: WritableMap = Arguments.createMap() - eventData.putString("userId", userId) + eventData.putString("url", url) eventData.putString("text", text) return eventData } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt b/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt index 9164e2e2..d44eba0f 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt @@ -72,7 +72,7 @@ class LinkRenderer( } private fun renderMention( - userId: String, + url: String, node: MarkdownASTNode, builder: SpannableStringBuilder, onLinkPress: ((String) -> Unit)?, @@ -95,7 +95,7 @@ class LinkRenderer( val span = MentionSpan( - userId = userId, + url = url, displayText = displayText, mentionStyle = factory.styleCache.mentionStyle, mentionTypeface = factory.styleCache.mentionTypeface, diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt index 6bf25373..f69aa015 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt @@ -12,11 +12,11 @@ import com.swmansion.enriched.markdown.styles.MentionStyle * Rendering is atomic — the span reports its full width (including padding and * border) via getSize so layout reserves enough room and the text never clips. * - * The span exposes [userId] for tap dispatching and an [isPressed] flag the + * The span exposes [url] for tap dispatching and an [isPressed] flag the * tap handler can toggle to drive the pressedOpacity tap-feedback animation. */ class MentionSpan( - val userId: String, + val url: String, val displayText: String, private val mentionStyle: MentionStyle, private val mentionTypeface: Typeface?, diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt index 7f8a9d40..6fb88b7e 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/common/MarkdownViewManagerUtils.kt @@ -50,13 +50,13 @@ fun emitLinkLongPress( fun emitMentionPress( view: View, - userId: String, + url: String, text: String, ) { val context = view.context as com.facebook.react.bridge.ReactContext val surfaceId = UIManagerHelper.getSurfaceId(context) val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, view.id) - eventDispatcher?.dispatchEvent(MentionPressEvent(surfaceId, view.id, userId, text)) + eventDispatcher?.dispatchEvent(MentionPressEvent(surfaceId, view.id, url, text)) } fun emitCitationPress( diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt index 7418748c..40fcf428 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkEvents.kt @@ -26,13 +26,13 @@ fun View.emitLinkLongPressEvent(url: String) { } fun View.emitMentionPressEvent( - userId: String, + url: String, text: String, ) { val reactContext = context as? ReactContext ?: return val surfaceId = UIManagerHelper.getSurfaceId(reactContext) val dispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id) - dispatcher?.dispatchEvent(MentionPressEvent(surfaceId, id, userId, text)) + dispatcher?.dispatchEvent(MentionPressEvent(surfaceId, id, url, text)) } fun View.emitCitationPressEvent( diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt index 5df5c0e2..7d692ad4 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt @@ -21,7 +21,7 @@ class LinkLongPressMovementMethod : LinkMovementMethod() { * is a [android.text.style.ReplacementSpan], not a [android.text.style.ClickableSpan], * so the standard LinkMovementMethod dispatch doesn't reach it. */ - var onMentionTap: ((userId: String, text: String) -> Unit)? = null + var onMentionTap: ((url: String, text: String) -> Unit)? = null /** Optional callback invoked when a [CitationSpan] is tapped. */ var onCitationTap: ((url: String, text: String) -> Unit)? = null @@ -99,7 +99,7 @@ class LinkLongPressMovementMethod : LinkMovementMethod() { clearMentionPressedState(widget) pendingMentionTapOffset = -1 if (stillOverMention) { - onMentionTap?.invoke(mention.userId, mention.displayText) + onMentionTap?.invoke(mention.url, mention.displayText) return true } } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt b/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt index b1f9e92d..6606f5fe 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt @@ -49,7 +49,7 @@ class TableContainerView( var maxFontSizeMultiplier = 0f var onLinkPress: ((String) -> Unit)? = null var onLinkLongPress: ((String) -> Unit)? = null - var onMentionPress: ((userId: String, text: String) -> Unit)? = null + var onMentionPress: ((url: String, text: String) -> Unit)? = null var onCitationPress: ((url: String, text: String) -> Unit)? = null private val scrollView = @@ -209,7 +209,7 @@ class TableContainerView( true } (movementMethod as? LinkLongPressMovementMethod)?.apply { - onMentionTap = { userId, mentionText -> this@TableContainerView.onMentionPress?.invoke(userId, mentionText) } + onMentionTap = { url, mentionText -> this@TableContainerView.onMentionPress?.invoke(url, mentionText) } onCitationTap = { url, citationText -> this@TableContainerView.onCitationPress?.invoke(url, citationText) } } } diff --git a/apps/example/src/sampleMarkdown.ts b/apps/example/src/sampleMarkdown.ts index 78f3b68d..1c1919dc 100644 --- a/apps/example/src/sampleMarkdown.ts +++ b/apps/example/src/sampleMarkdown.ts @@ -105,7 +105,7 @@ The largest terrestrial biome, spanning across **Northern Russia, Canada, and Sc | Forest Type | Coverage | Annual Rainfall | Biodiversity | Carbon Storage | |------------|----------|-----------------|--------------|----------------| | Tropical Rainforest [4](citation://https://www.google.com) [5](citation://https://www.google.com) | ~7% of land | 80-400 inches | Highest (50%+ species) | High | -| Temperate Forest | ~16% of land | 30-60 inches | Moderate | Moderate | +| Temperate Forest [@Casper](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) | ~16% of land | 30-60 inches | Moderate | Moderate | | Boreal Forest (Taiga) | ~11% of land | 15-40 inches | Lower | Highest | | Mediterranean Forest | ~2% of land | 20-40 inches | Moderate | Moderate [@John Muir](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user)| diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index 66e49748..1580c6d2 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -559,7 +559,7 @@ - (TableContainerView *)createTableViewForSegment:(EMTableSegment *)tableSegment } }; - tableView.onMentionPress = ^(NSString *userId, NSString *text) { + tableView.onMentionPress = ^(NSString *url, NSString *text) { EnrichedMarkdown *strongSelf = weakSelf; if (!strongSelf) return; @@ -567,7 +567,7 @@ - (TableContainerView *)createTableViewForSegment:(EMTableSegment *)tableSegment auto eventEmitter = std::static_pointer_cast(strongSelf->_eventEmitter); if (eventEmitter) { eventEmitter->onMentionPress({ - .userId = std::string([(userId ?: @"") UTF8String] ?: ""), + .url = std::string([(url ?: @"") UTF8String] ?: ""), .text = std::string([(text ?: @"") UTF8String] ?: ""), }); } @@ -789,17 +789,17 @@ - (void)textTapped:(ENRMTapRecognizer *)recognizer } NSString *linkURL = nil; - NSString *mentionUserId = nil; + NSString *mentionURL = nil; NSString *mentionText = nil; NSString *citationURL = nil; NSString *citationText = nil; - if (inlineElementAtTapLocation(textView, recognizer, &linkURL, &mentionUserId, &mentionText, &citationURL, + if (inlineElementAtTapLocation(textView, recognizer, &linkURL, &mentionURL, &mentionText, &citationURL, &citationText)) { auto eventEmitter = std::static_pointer_cast(_eventEmitter); if (eventEmitter) { - if (mentionUserId) { + if (mentionURL) { eventEmitter->onMentionPress({ - .userId = std::string([mentionUserId UTF8String] ?: ""), + .url = std::string([mentionURL UTF8String] ?: ""), .text = std::string([(mentionText ?: @"") UTF8String] ?: ""), }); } else if (citationURL) { diff --git a/ios/EnrichedMarkdownText.mm b/ios/EnrichedMarkdownText.mm index d2e60d3b..1356619a 100644 --- a/ios/EnrichedMarkdownText.mm +++ b/ios/EnrichedMarkdownText.mm @@ -538,17 +538,17 @@ - (void)textTapped:(ENRMTapRecognizer *)recognizer } NSString *linkURL = nil; - NSString *mentionUserId = nil; + NSString *mentionURL = nil; NSString *mentionText = nil; NSString *citationURL = nil; NSString *citationText = nil; - if (inlineElementAtTapLocation(textView, recognizer, &linkURL, &mentionUserId, &mentionText, &citationURL, + if (inlineElementAtTapLocation(textView, recognizer, &linkURL, &mentionURL, &mentionText, &citationURL, &citationText)) { auto eventEmitter = std::static_pointer_cast(_eventEmitter); if (eventEmitter) { - if (mentionUserId) { + if (mentionURL) { eventEmitter->onMentionPress({ - .userId = std::string([mentionUserId UTF8String] ?: ""), + .url = std::string([mentionURL UTF8String] ?: ""), .text = std::string([(mentionText ?: @"") UTF8String] ?: ""), }); } else if (citationURL) { diff --git a/ios/attachments/ENRMMentionAttachment.h b/ios/attachments/ENRMMentionAttachment.h index 8c29ea8b..e26742a4 100644 --- a/ios/attachments/ENRMMentionAttachment.h +++ b/ios/attachments/ENRMMentionAttachment.h @@ -16,12 +16,10 @@ NS_ASSUME_NONNULL_BEGIN @interface ENRMMentionAttachment : NSTextAttachment @property (nonatomic, readonly, copy) NSString *displayText; -@property (nonatomic, readonly, copy) NSString *userId; +@property (nonatomic, readonly, copy) NSString *url; @property (nonatomic, readonly, strong) StyleConfig *config; -+ (instancetype)attachmentWithDisplayText:(NSString *)displayText - userId:(NSString *)userId - config:(StyleConfig *)config; ++ (instancetype)attachmentWithDisplayText:(NSString *)displayText url:(NSString *)url config:(StyleConfig *)config; @end diff --git a/ios/attachments/ENRMMentionAttachment.m b/ios/attachments/ENRMMentionAttachment.m index 018d5442..1163bc65 100644 --- a/ios/attachments/ENRMMentionAttachment.m +++ b/ios/attachments/ENRMMentionAttachment.m @@ -3,18 +3,18 @@ @interface ENRMMentionAttachment () @property (nonatomic, copy) NSString *displayText; -@property (nonatomic, copy) NSString *userId; +@property (nonatomic, copy) NSString *url; @property (nonatomic, strong) StyleConfig *config; @property (nonatomic, assign) CGSize cachedPillSize; @end @implementation ENRMMentionAttachment -+ (instancetype)attachmentWithDisplayText:(NSString *)displayText userId:(NSString *)userId config:(StyleConfig *)config ++ (instancetype)attachmentWithDisplayText:(NSString *)displayText url:(NSString *)url config:(StyleConfig *)config { ENRMMentionAttachment *attachment = [[self alloc] init]; attachment.displayText = displayText ?: @""; - attachment.userId = userId ?: @""; + attachment.url = url ?: @""; attachment.config = config; [attachment rebuildPillImage]; return attachment; diff --git a/ios/renderer/LinkRenderer.h b/ios/renderer/LinkRenderer.h index 5c9a7a42..b784b0af 100644 --- a/ios/renderer/LinkRenderer.h +++ b/ios/renderer/LinkRenderer.h @@ -7,7 +7,7 @@ * the rendered NSAttributedString. Tap dispatching reads these to decide which * JS event to fire for a given character. */ -FOUNDATION_EXPORT NSString *const ENRMMentionUserIdAttributeName; +FOUNDATION_EXPORT NSString *const ENRMMentionURLAttributeName; FOUNDATION_EXPORT NSString *const ENRMMentionTextAttributeName; FOUNDATION_EXPORT NSString *const ENRMCitationURLAttributeName; FOUNDATION_EXPORT NSString *const ENRMCitationTextAttributeName; diff --git a/ios/renderer/LinkRenderer.m b/ios/renderer/LinkRenderer.m index 55d0ec22..143bbb1d 100644 --- a/ios/renderer/LinkRenderer.m +++ b/ios/renderer/LinkRenderer.m @@ -7,7 +7,7 @@ #import "StyleConfig.h" #import -NSString *const ENRMMentionUserIdAttributeName = @"ENRMMentionUserId"; +NSString *const ENRMMentionURLAttributeName = @"ENRMMentionURL"; NSString *const ENRMMentionTextAttributeName = @"ENRMMentionText"; NSString *const ENRMCitationURLAttributeName = @"ENRMCitationURL"; NSString *const ENRMCitationTextAttributeName = @"ENRMCitationText"; @@ -140,7 +140,7 @@ - (void)renderMentionNode:(MarkdownASTNode *)node NSMutableAttributedString *childBuffer = [[NSMutableAttributedString alloc] init]; [_rendererFactory renderChildrenOfNode:node into:childBuffer context:context]; NSString *displayText = childBuffer.string ?: @""; - NSString *userId = stripScheme(url, kMentionScheme); + NSString *mentionURL = stripScheme(url, kMentionScheme); // Inherit the current text attributes (font, color) so the pill sits in the // same line metrics as the surrounding paragraph if the pill label has no @@ -148,7 +148,7 @@ - (void)renderMentionNode:(MarkdownASTNode *)node NSDictionary *baseAttrs = output.length > 0 ? [output attributesAtIndex:output.length - 1 effectiveRange:NULL] : @{}; ENRMMentionAttachment *attachment = [ENRMMentionAttachment attachmentWithDisplayText:displayText - userId:userId + url:mentionURL config:_config]; NSMutableAttributedString *attachmentString = @@ -157,14 +157,14 @@ - (void)renderMentionNode:(MarkdownASTNode *)node if (baseAttrs.count > 0) { [attachmentString addAttributes:baseAttrs range:attachmentRange]; } - [attachmentString addAttribute:ENRMMentionUserIdAttributeName value:userId range:attachmentRange]; + [attachmentString addAttribute:ENRMMentionURLAttributeName value:mentionURL range:attachmentRange]; [attachmentString addAttribute:ENRMMentionTextAttributeName value:displayText range:attachmentRange]; NSUInteger start = output.length; [output appendAttributedString:attachmentString]; NSRange outputRange = NSMakeRange(start, output.length - start); - [context registerMentionRange:outputRange userId:userId text:displayText]; + [context registerMentionRange:outputRange url:mentionURL text:displayText]; } #pragma mark - Citation diff --git a/ios/renderer/RenderContext.h b/ios/renderer/RenderContext.h index 20475165..ef3e7202 100644 --- a/ios/renderer/RenderContext.h +++ b/ios/renderer/RenderContext.h @@ -26,7 +26,7 @@ typedef NS_ENUM(NSInteger, ListType) { ListTypeUnordered, ListTypeOrdered }; @property (nonatomic, strong) NSMutableArray *linkRanges; @property (nonatomic, strong) NSMutableArray *linkURLs; @property (nonatomic, strong) NSMutableArray *mentionRanges; -@property (nonatomic, strong) NSMutableArray *mentionUserIds; +@property (nonatomic, strong) NSMutableArray *mentionURLs; @property (nonatomic, strong) NSMutableArray *mentionTexts; @property (nonatomic, strong) NSMutableArray *citationRanges; @property (nonatomic, strong) NSMutableArray *citationURLs; @@ -59,7 +59,7 @@ typedef NS_ENUM(NSInteger, ListType) { ListTypeUnordered, ListTypeOrdered }; - (NSMutableParagraphStyle *)spacerStyleWithHeight:(CGFloat)height spacing:(CGFloat)spacing; - (NSMutableParagraphStyle *)blockSpacerStyleWithMargin:(CGFloat)margin; - (void)registerLinkRange:(NSRange)range url:(NSString *)url; -- (void)registerMentionRange:(NSRange)range userId:(NSString *)userId text:(NSString *)text; +- (void)registerMentionRange:(NSRange)range url:(NSString *)url text:(NSString *)text; - (void)registerCitationRange:(NSRange)range url:(NSString *)url text:(NSString *)text; - (void)applyLinkAttributesToString:(NSMutableAttributedString *)attributedString; diff --git a/ios/renderer/RenderContext.m b/ios/renderer/RenderContext.m index 799b46de..6e0336c3 100644 --- a/ios/renderer/RenderContext.m +++ b/ios/renderer/RenderContext.m @@ -18,7 +18,7 @@ - (instancetype)init _linkRanges = [NSMutableArray array]; _linkURLs = [NSMutableArray array]; _mentionRanges = [NSMutableArray array]; - _mentionUserIds = [NSMutableArray array]; + _mentionURLs = [NSMutableArray array]; _mentionTexts = [NSMutableArray array]; _citationRanges = [NSMutableArray array]; _citationURLs = [NSMutableArray array]; @@ -111,12 +111,12 @@ - (void)registerLinkRange:(NSRange)range url:(NSString *)url [self.linkURLs addObject:url ?: @""]; } -- (void)registerMentionRange:(NSRange)range userId:(NSString *)userId text:(NSString *)text +- (void)registerMentionRange:(NSRange)range url:(NSString *)url text:(NSString *)text { if (range.length == 0) return; [self.mentionRanges addObject:[NSValue valueWithRange:range]]; - [self.mentionUserIds addObject:userId ?: @""]; + [self.mentionURLs addObject:url ?: @""]; [self.mentionTexts addObject:text ?: @""]; } @@ -268,7 +268,7 @@ - (void)reset [_linkRanges removeAllObjects]; [_linkURLs removeAllObjects]; [_mentionRanges removeAllObjects]; - [_mentionUserIds removeAllObjects]; + [_mentionURLs removeAllObjects]; [_mentionTexts removeAllObjects]; [_citationRanges removeAllObjects]; [_citationURLs removeAllObjects]; diff --git a/ios/utils/LinkTapUtils.h b/ios/utils/LinkTapUtils.h index af48a9c9..a0eced0a 100644 --- a/ios/utils/LinkTapUtils.h +++ b/ios/utils/LinkTapUtils.h @@ -18,8 +18,7 @@ NSString *_Nullable linkURLAtRange(ENRMPlatformTextView *textView, NSRange chara /// The out parameters are populated only when a matching element is present. /// Returns YES when any element was matched, NO otherwise. BOOL inlineElementAtTapLocation(ENRMPlatformTextView *textView, ENRMTapRecognizer *recognizer, - NSString *_Nullable *_Nullable outLinkURL, - NSString *_Nullable *_Nullable outMentionUserId, + NSString *_Nullable *_Nullable outLinkURL, NSString *_Nullable *_Nullable outMentionURL, NSString *_Nullable *_Nullable outMentionText, NSString *_Nullable *_Nullable outCitationURL, NSString *_Nullable *_Nullable outCitationText); diff --git a/ios/utils/LinkTapUtils.m b/ios/utils/LinkTapUtils.m index 0547091f..5d0ebd88 100644 --- a/ios/utils/LinkTapUtils.m +++ b/ios/utils/LinkTapUtils.m @@ -23,8 +23,7 @@ } BOOL inlineElementAtTapLocation(ENRMPlatformTextView *textView, ENRMTapRecognizer *recognizer, - NSString *_Nullable *_Nullable outLinkURL, - NSString *_Nullable *_Nullable outMentionUserId, + NSString *_Nullable *_Nullable outLinkURL, NSString *_Nullable *_Nullable outMentionURL, NSString *_Nullable *_Nullable outMentionText, NSString *_Nullable *_Nullable outCitationURL, NSString *_Nullable *_Nullable outCitationText) @@ -36,10 +35,10 @@ BOOL inlineElementAtTapLocation(ENRMPlatformTextView *textView, ENRMTapRecognize NSAttributedString *attrText = ENRMGetAttributedText(textView); NSDictionary *attrs = [attrText attributesAtIndex:characterIndex effectiveRange:NULL]; - NSString *mentionUserId = attrs[ENRMMentionUserIdAttributeName]; - if (mentionUserId) { - if (outMentionUserId) - *outMentionUserId = mentionUserId; + NSString *mentionURL = attrs[ENRMMentionURLAttributeName]; + if (mentionURL) { + if (outMentionURL) + *outMentionURL = mentionURL; if (outMentionText) *outMentionText = attrs[ENRMMentionTextAttributeName] ?: @""; return YES; @@ -71,7 +70,7 @@ BOOL isPointOnInteractiveElement(ENRMPlatformTextView *textView, CGPoint point) return NO; NSDictionary *attrs = [ENRMGetAttributedText(textView) attributesAtIndex:charIndex effectiveRange:NULL]; - return attrs[@"linkURL"] != nil || attrs[ENRMMentionUserIdAttributeName] != nil || + return attrs[@"linkURL"] != nil || attrs[ENRMMentionURLAttributeName] != nil || attrs[ENRMCitationURLAttributeName] != nil || [attrs[@"TaskItem"] boolValue] || attrs[SpoilerAttributeName] != nil; } diff --git a/ios/views/TableContainerView.h b/ios/views/TableContainerView.h index b133aee0..00d1d35f 100644 --- a/ios/views/TableContainerView.h +++ b/ios/views/TableContainerView.h @@ -7,7 +7,7 @@ NS_ASSUME_NONNULL_BEGIN typedef void (^TableLinkPressBlock)(NSString *url); -typedef void (^TableMentionPressBlock)(NSString *userId, NSString *text); +typedef void (^TableMentionPressBlock)(NSString *url, NSString *text); typedef void (^TableCitationPressBlock)(NSString *url, NSString *text); @interface TableContainerView : RCTUIView diff --git a/ios/views/TableContainerView.m b/ios/views/TableContainerView.m index 181af524..47091ced 100644 --- a/ios/views/TableContainerView.m +++ b/ios/views/TableContainerView.m @@ -410,14 +410,14 @@ - (void)cellTextTapped:(UITapGestureRecognizer *)recognizer { UITextView *textView = (UITextView *)recognizer.view; NSString *linkURL = nil; - NSString *mentionUserId = nil; + NSString *mentionURL = nil; NSString *mentionText = nil; NSString *citationURL = nil; NSString *citationText = nil; - if (inlineElementAtTapLocation(textView, recognizer, &linkURL, &mentionUserId, &mentionText, &citationURL, + if (inlineElementAtTapLocation(textView, recognizer, &linkURL, &mentionURL, &mentionText, &citationURL, &citationText)) { - if (mentionUserId && self.onMentionPress) { - self.onMentionPress(mentionUserId, mentionText ?: @""); + if (mentionURL && self.onMentionPress) { + self.onMentionPress(mentionURL, mentionText ?: @""); } else if (citationURL && self.onCitationPress) { self.onCitationPress(citationURL, citationText ?: @""); } else if (linkURL && self.onLinkPress) { diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index f569500f..b53e1357 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -215,7 +215,7 @@ export interface LinkLongPressEvent { } export interface MentionPressEvent { - userId: string; + url: string; text: string; } @@ -291,7 +291,7 @@ export interface NativeProps extends ViewProps { */ onTaskListItemPress?: CodegenTypes.BubblingEventHandler; /** - * Callback fired when an inline mention pill (`mention://`) is pressed. + * Callback fired when an inline mention pill (`mention://`) is pressed. */ onMentionPress?: CodegenTypes.BubblingEventHandler; /** diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index 295649f9..28821d21 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -215,7 +215,7 @@ export interface LinkLongPressEvent { } export interface MentionPressEvent { - userId: string; + url: string; text: string; } @@ -291,7 +291,7 @@ export interface NativeProps extends ViewProps { */ onTaskListItemPress?: CodegenTypes.BubblingEventHandler; /** - * Callback fired when an inline mention pill (`mention://`) is pressed. + * Callback fired when an inline mention pill (`mention://`) is pressed. */ onMentionPress?: CodegenTypes.BubblingEventHandler; /** diff --git a/src/native/EnrichedMarkdownText.tsx b/src/native/EnrichedMarkdownText.tsx index dde302cc..efa1bb57 100644 --- a/src/native/EnrichedMarkdownText.tsx +++ b/src/native/EnrichedMarkdownText.tsx @@ -132,8 +132,8 @@ export const EnrichedMarkdownText = ({ const handleMentionPress = useCallback( (e: NativeSyntheticEvent) => { - const { userId, text } = e.nativeEvent; - onMentionPress?.({ userId, text }); + const { url, text } = e.nativeEvent; + onMentionPress?.({ url, text }); }, [onMentionPress] ); diff --git a/src/types/MarkdownTextProps.ts b/src/types/MarkdownTextProps.ts index a2480ef5..75a36993 100644 --- a/src/types/MarkdownTextProps.ts +++ b/src/types/MarkdownTextProps.ts @@ -73,8 +73,8 @@ export interface EnrichedMarkdownTextProps extends Omit { onTaskListItemPress?: (event: TaskListItemPressEvent) => void; /** * Callback fired when an inline mention pill is pressed. - * Mentions are authored as `[label](mention://)` in markdown; the - * renderer draws them as a pill and surfaces the user id separately. + * Mentions are authored as `[label](mention://)` in markdown; the + * renderer draws them as a pill and surfaces the post-scheme URL separately. * @platform ios, android, web */ onMentionPress?: (event: MentionPressEvent) => void; diff --git a/src/types/MarkdownTextProps.web.ts b/src/types/MarkdownTextProps.web.ts index d82dfd4b..bf117492 100644 --- a/src/types/MarkdownTextProps.web.ts +++ b/src/types/MarkdownTextProps.web.ts @@ -60,7 +60,7 @@ export interface EnrichedMarkdownTextProps onTaskListItemPress?: (event: TaskListItemPressEvent) => void; /** * Callback fired when an inline mention pill is pressed. - * Mentions are authored as `[label](mention://)` in markdown. + * Mentions are authored as `[label](mention://)` in markdown. * @platform ios, android, web */ onMentionPress?: (event: MentionPressEvent) => void; diff --git a/src/types/events.ts b/src/types/events.ts index d2b22d0f..6f7491ea 100644 --- a/src/types/events.ts +++ b/src/types/events.ts @@ -13,7 +13,7 @@ export interface TaskListItemPressEvent { } export interface MentionPressEvent { - userId: string; + url: string; text: string; } diff --git a/src/web/renderers/InlineRenderers.tsx b/src/web/renderers/InlineRenderers.tsx index ecf7d764..0eef342d 100644 --- a/src/web/renderers/InlineRenderers.tsx +++ b/src/web/renderers/InlineRenderers.tsx @@ -107,12 +107,12 @@ function MentionRenderer({ callbacks, node, }: SchemeRendererProps) { - const userId = url.slice(MENTION_SCHEME.length); + const mentionUrl = url.slice(MENTION_SCHEME.length); const displayText = extractNodeText(node); const handleClick = (event: MouseEvent) => { event.preventDefault(); - callbacks.onMentionPress?.({ userId, text: displayText }); + callbacks.onMentionPress?.({ url: mentionUrl, text: displayText }); }; const style = { @@ -129,7 +129,7 @@ function MentionRenderer({ role="button" tabIndex={0} aria-label={`Mention: ${displayText}`} - data-user-id={userId} + data-mention-url={mentionUrl} onClick={handleClick} style={style} > From a5e4e9a7c5bf055e3e37ab839806b12791f10c5a Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 12:04:53 -0700 Subject: [PATCH 03/23] ios: allow copy/paste mention and citations as normal text --- apps/example/ios/Podfile.lock | 2 +- apps/example/src/App.tsx | 35 ++++++ apps/example/src/markdownStyles.ts | 21 ++++ ios/attachments/ENRMCitationAttachment.h | 25 ---- ios/attachments/ENRMCitationAttachment.m | 145 ----------------------- ios/attachments/ENRMMentionAttachment.h | 26 ---- ios/attachments/ENRMMentionAttachment.m | 115 ------------------ ios/renderer/LinkRenderer.m | 123 ++++++++++++------- ios/utils/CitationBackground.h | 24 ++++ ios/utils/CitationBackground.m | 79 ++++++++++++ ios/utils/MarkdownExtractor.m | 12 ++ ios/utils/MentionBackground.h | 25 ++++ ios/utils/MentionBackground.m | 91 ++++++++++++++ ios/utils/RuntimeKeys.h | 8 ++ ios/utils/RuntimeKeys.m | 2 + ios/utils/TextViewLayoutManager.mm | 30 +++++ 16 files changed, 407 insertions(+), 356 deletions(-) delete mode 100644 ios/attachments/ENRMCitationAttachment.h delete mode 100644 ios/attachments/ENRMCitationAttachment.m delete mode 100644 ios/attachments/ENRMMentionAttachment.h delete mode 100644 ios/attachments/ENRMMentionAttachment.m create mode 100644 ios/utils/CitationBackground.h create mode 100644 ios/utils/CitationBackground.m create mode 100644 ios/utils/MentionBackground.h create mode 100644 ios/utils/MentionBackground.m diff --git a/apps/example/ios/Podfile.lock b/apps/example/ios/Podfile.lock index 2c37a8bd..3128429a 100644 --- a/apps/example/ios/Podfile.lock +++ b/apps/example/ios/Podfile.lock @@ -2195,4 +2195,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: 9c5417fc84515945aa2357a49779fde55434ae62 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index e4c44a7a..459ab89c 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -11,6 +11,8 @@ import { import { EnrichedMarkdownText, type LinkPressEvent, + type CitationPressEvent, + type MentionPressEvent, } from 'react-native-enriched-markdown'; import { SafeAreaView } from 'react-native-safe-area-context'; import { sampleMarkdown } from './sampleMarkdown'; @@ -63,6 +65,37 @@ export default function App() { ]); }; + const handleCitationPress = (event: CitationPressEvent) => { + const { url } = event; + Alert.alert('Citation Pressed!', `You tapped on: ${url}`, [ + { + text: 'Open in Browser', + onPress: () => { + Linking.openURL(url); + }, + }, + { + text: 'Cancel', + style: 'cancel', + }, + ]); + }; + + const handleMentionPress = (event: MentionPressEvent) => { + const { url } = event; + Alert.alert('Mention Pressed!', `You tapped on: ${url}`, [ + { + text: 'Open in Browser', + onPress: () => { + Linking.openURL(url); + }, + }, + { + text: 'Cancel', + style: 'cancel', + }, + ]); + }; return ( @@ -91,6 +124,8 @@ export default function App() { flavor="github" markdown={sampleMarkdown} onLinkPress={handleLinkPress} + onCitationPress={handleCitationPress} + onMentionPress={handleMentionPress} markdownStyle={markdownStyle} contextMenuItems={contextMenuItems} /> diff --git a/apps/example/src/markdownStyles.ts b/apps/example/src/markdownStyles.ts index f741cadd..b8fb363a 100644 --- a/apps/example/src/markdownStyles.ts +++ b/apps/example/src/markdownStyles.ts @@ -142,4 +142,25 @@ export const customMarkdownStyle: MarkdownStyle = { checkedTextColor: '#9ca3af', checkedStrikethrough: true, }, + mention: { + backgroundColor: '#EBEBFF', + borderColor: '#ddd6fe', + borderWidth: 1, + borderRadius: 4, + paddingHorizontal: 4, + paddingVertical: 2, + fontFamily: 'Montserrat-Regular', + fontSize: 14, + color: '#2563fb', + }, + citation: { + backgroundColor: '#EBEBFF', + color: '#9B9BFD', + fontSizeMultiplier: 0.7, + baselineOffsetPx: 1.5, + fontWeight: '', + underline: false, + paddingHorizontal: 0, + paddingVertical: 0, + }, }; diff --git a/ios/attachments/ENRMCitationAttachment.h b/ios/attachments/ENRMCitationAttachment.h deleted file mode 100644 index 4f7a94c4..00000000 --- a/ios/attachments/ENRMCitationAttachment.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once -#import "ENRMUIKit.h" - -@class StyleConfig; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Custom NSTextAttachment for rendering inline citation markers. Drawing is - * atomic (CoreGraphics into a UIImage) so the renderer can apply padding, - * backgrounds, baseline offset, and a font-size multiplier consistently. - */ -@interface ENRMCitationAttachment : NSTextAttachment - -@property (nonatomic, readonly, copy) NSString *displayText; -@property (nonatomic, readonly, copy) NSString *url; - -+ (instancetype)attachmentWithDisplayText:(NSString *)displayText - url:(NSString *)url - baseFont:(nullable UIFont *)baseFont - config:(StyleConfig *)config; - -@end - -NS_ASSUME_NONNULL_END diff --git a/ios/attachments/ENRMCitationAttachment.m b/ios/attachments/ENRMCitationAttachment.m deleted file mode 100644 index 17c96805..00000000 --- a/ios/attachments/ENRMCitationAttachment.m +++ /dev/null @@ -1,145 +0,0 @@ -#import "ENRMCitationAttachment.h" -#import "StyleConfig.h" - -@interface ENRMCitationAttachment () -@property (nonatomic, copy) NSString *displayText; -@property (nonatomic, copy) NSString *url; -@property (nonatomic, strong, nullable) UIFont *baseFont; -@property (nonatomic, strong) StyleConfig *config; -@property (nonatomic, assign) CGSize cachedSize; -@property (nonatomic, assign) CGFloat cachedBaseline; -@end - -@implementation ENRMCitationAttachment - -+ (instancetype)attachmentWithDisplayText:(NSString *)displayText - url:(NSString *)url - baseFont:(UIFont *)baseFont - config:(StyleConfig *)config -{ - ENRMCitationAttachment *attachment = [[self alloc] init]; - attachment.displayText = displayText ?: @""; - attachment.url = url ?: @""; - attachment.baseFont = baseFont; - attachment.config = config; - [attachment rebuildImage]; - return attachment; -} - -- (UIFont *)citationFont -{ - UIFont *base = self.baseFont; - if (!base) { - base = [UIFont systemFontOfSize:[UIFont systemFontSize]]; - } - CGFloat multiplier = [self.config citationFontSizeMultiplier]; - if (multiplier <= 0) { - multiplier = 0.7; - } - CGFloat scaledSize = MAX(1.0, base.pointSize * multiplier); - NSString *weight = [self.config citationFontWeight] ?: @""; - UIFont *scaled = [base fontWithSize:scaledSize]; - - if (weight.length > 0 && ([weight caseInsensitiveCompare:@"bold"] == NSOrderedSame || - [weight caseInsensitiveCompare:@"700"] == NSOrderedSame || - [weight caseInsensitiveCompare:@"800"] == NSOrderedSame || - [weight caseInsensitiveCompare:@"900"] == NSOrderedSame)) { - UIFontDescriptor *descriptor = [scaled.fontDescriptor fontDescriptorWithSymbolicTraits:UIFontDescriptorTraitBold]; - if (descriptor) { - scaled = [UIFont fontWithDescriptor:descriptor size:scaledSize]; - } - } - return scaled; -} - -- (void)rebuildImage -{ - UIFont *font = [self citationFont]; - RCTUIColor *textColor = [self.config citationColor] ?: [RCTUIColor labelColor]; - RCTUIColor *bgColor = [self.config citationBackgroundColor]; - CGFloat paddingH = MAX(0, [self.config citationPaddingHorizontal]); - CGFloat paddingV = MAX(0, [self.config citationPaddingVertical]); - BOOL underline = [self.config citationUnderline]; - - NSMutableDictionary *attrs = [NSMutableDictionary dictionary]; - attrs[NSFontAttributeName] = font; - attrs[NSForegroundColorAttributeName] = textColor; - if (underline) { - attrs[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle); - attrs[NSUnderlineColorAttributeName] = textColor; - } - - CGSize textSize = [self.displayText sizeWithAttributes:attrs]; - - CGFloat width = ceil(textSize.width + paddingH * 2); - CGFloat height = ceil(textSize.height + paddingV * 2); - CGSize size = CGSizeMake(MAX(1, width), MAX(1, height)); - self.cachedSize = size; - self.cachedBaseline = paddingV + font.ascender; - - UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat]; - format.opaque = NO; - UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:format]; - - __weak typeof(self) weakSelf = self; - UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *ctx) { - __strong typeof(weakSelf) strongSelf = weakSelf; - if (!strongSelf) - return; - - CGContextRef cg = ctx.CGContext; - (void)cg; - - if (bgColor) { - CGRect rect = CGRectMake(0, 0, size.width, size.height); - CGFloat radius = MIN(size.height, size.width) / 2.0; - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius]; - [bgColor setFill]; - [path fill]; - } - - CGPoint origin = CGPointMake(paddingH, paddingV); - [strongSelf.displayText drawAtPoint:origin withAttributes:attrs]; - }]; - - self.image = image; - self.bounds = CGRectMake(0, 0, size.width, size.height); -} - -- (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer - proposedLineFragment:(CGRect)lineFragment - glyphPosition:(CGPoint)position - characterIndex:(NSUInteger)characterIndex -{ - CGSize size = self.cachedSize; - if (size.width == 0 || size.height == 0) { - [self rebuildImage]; - size = self.cachedSize; - } - - // Derive the desired baseline offset (superscript-like shift). Positive - // `NSBaselineOffsetAttributeName` values move glyphs upward; for an - // attachment we achieve the same by offsetting the bounds origin upward. - CGFloat baselineOffset = [self.config citationBaselineOffsetPx]; - UIFont *lineFont = self.baseFont; - if (!lineFont) { - NSLayoutManager *layoutManager = textContainer.layoutManager; - NSTextStorage *textStorage = layoutManager.textStorage; - if (textStorage && characterIndex < textStorage.length) { - lineFont = [textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL]; - } - } - - if (baselineOffset == 0 && lineFont) { - // Default: raise so the mid-line of the citation sits near the cap-height - // of the surrounding text, matching the MetricAffectingSpan fallback used - // on Android. - CGFloat hostCap = lineFont.capHeight; - UIFont *citationFont = [self citationFont]; - baselineOffset = MAX(0, (hostCap - citationFont.capHeight) * 0.5); - } - - return CGRectMake(0, baselineOffset, size.width, size.height); -} - -@end diff --git a/ios/attachments/ENRMMentionAttachment.h b/ios/attachments/ENRMMentionAttachment.h deleted file mode 100644 index e26742a4..00000000 --- a/ios/attachments/ENRMMentionAttachment.h +++ /dev/null @@ -1,26 +0,0 @@ -#pragma once -#import "ENRMUIKit.h" - -@class StyleConfig; - -NS_ASSUME_NONNULL_BEGIN - -/** - * Custom NSTextAttachment used to render inline mention pills. - * - * Rendering is delegated to an NSTextAttachmentViewProvider (iOS 15+) so the - * pill participates in the text layout as an atomic character — selection - * handles, cursor movement, and accessibility traverse it as a single glyph. - * Tap feedback (alpha dim on press) is managed by the view provider. - */ -@interface ENRMMentionAttachment : NSTextAttachment - -@property (nonatomic, readonly, copy) NSString *displayText; -@property (nonatomic, readonly, copy) NSString *url; -@property (nonatomic, readonly, strong) StyleConfig *config; - -+ (instancetype)attachmentWithDisplayText:(NSString *)displayText url:(NSString *)url config:(StyleConfig *)config; - -@end - -NS_ASSUME_NONNULL_END diff --git a/ios/attachments/ENRMMentionAttachment.m b/ios/attachments/ENRMMentionAttachment.m deleted file mode 100644 index 1163bc65..00000000 --- a/ios/attachments/ENRMMentionAttachment.m +++ /dev/null @@ -1,115 +0,0 @@ -#import "ENRMMentionAttachment.h" -#import "StyleConfig.h" - -@interface ENRMMentionAttachment () -@property (nonatomic, copy) NSString *displayText; -@property (nonatomic, copy) NSString *url; -@property (nonatomic, strong) StyleConfig *config; -@property (nonatomic, assign) CGSize cachedPillSize; -@end - -@implementation ENRMMentionAttachment - -+ (instancetype)attachmentWithDisplayText:(NSString *)displayText url:(NSString *)url config:(StyleConfig *)config -{ - ENRMMentionAttachment *attachment = [[self alloc] init]; - attachment.displayText = displayText ?: @""; - attachment.url = url ?: @""; - attachment.config = config; - [attachment rebuildPillImage]; - return attachment; -} - -- (void)rebuildPillImage -{ - UIFont *font = [self.config mentionFont]; - RCTUIColor *textColor = [self.config mentionColor] ?: [RCTUIColor labelColor]; - RCTUIColor *bgColor = [self.config mentionBackgroundColor]; - RCTUIColor *borderColor = [self.config mentionBorderColor]; - CGFloat borderWidth = MAX(0, [self.config mentionBorderWidth]); - CGFloat borderRadius = MAX(0, [self.config mentionBorderRadius]); - CGFloat paddingH = MAX(0, [self.config mentionPaddingHorizontal]); - CGFloat paddingV = MAX(0, [self.config mentionPaddingVertical]); - - NSDictionary *textAttrs = font ? @{NSFontAttributeName : font} : @{}; - CGSize textSize = [self.displayText sizeWithAttributes:textAttrs]; - - // Ensure the pill is large enough to fit the label plus padding and border. - // `getSize` parity for Android: width = textWidth + 2*padding + 2*border. - CGFloat width = ceil(textSize.width + paddingH * 2 + borderWidth * 2); - CGFloat height = ceil(textSize.height + paddingV * 2 + borderWidth * 2); - CGSize size = CGSizeMake(MAX(1, width), MAX(1, height)); - self.cachedPillSize = size; - - UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat]; - format.opaque = NO; - UIGraphicsImageRenderer *renderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:format]; - - __weak typeof(self) weakSelf = self; - UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *ctx) { - CGContextRef cg = ctx.CGContext; - CGRect bounds = CGRectMake(0, 0, size.width, size.height); - - // Inset so the border stroke stays inside the bounds (centered on pill edge). - CGRect rect = CGRectInset(bounds, borderWidth / 2.0, borderWidth / 2.0); - CGFloat clampedRadius = MIN(borderRadius, MIN(rect.size.width, rect.size.height) / 2.0); - UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:clampedRadius]; - - if (bgColor) { - CGContextSaveGState(cg); - [bgColor setFill]; - [path fill]; - CGContextRestoreGState(cg); - } - - if (borderWidth > 0 && borderColor) { - CGContextSaveGState(cg); - [borderColor setStroke]; - path.lineWidth = borderWidth; - [path stroke]; - CGContextRestoreGState(cg); - } - - CGFloat textX = (size.width - textSize.width) / 2.0; - CGFloat textY = (size.height - textSize.height) / 2.0; - - NSDictionary *drawAttrs = font ? @{NSFontAttributeName : font, NSForegroundColorAttributeName : textColor} - : @{NSForegroundColorAttributeName : textColor}; - [weakSelf.displayText drawAtPoint:CGPointMake(textX, textY) withAttributes:drawAttrs]; - }]; - - self.image = image; - self.bounds = CGRectMake(0, 0, size.width, size.height); -} - -- (CGRect)attachmentBoundsForTextContainer:(NSTextContainer *)textContainer - proposedLineFragment:(CGRect)lineFragment - glyphPosition:(CGPoint)position - characterIndex:(NSUInteger)characterIndex -{ - CGSize size = self.cachedPillSize; - if (size.width == 0 || size.height == 0) { - [self rebuildPillImage]; - size = self.cachedPillSize; - } - - // Vertically center the pill on the surrounding text's cap height when - // available, mirroring how inline images are positioned in this codebase. - UIFont *lineFont = nil; - NSLayoutManager *layoutManager = textContainer.layoutManager; - NSTextStorage *textStorage = layoutManager.textStorage; - if (textStorage && characterIndex < textStorage.length) { - lineFont = [textStorage attribute:NSFontAttributeName atIndex:characterIndex effectiveRange:NULL]; - } - - CGFloat verticalOffset; - if (lineFont) { - verticalOffset = (lineFont.capHeight - size.height) / 2.0; - } else { - verticalOffset = (lineFragment.size.height - size.height) / 2.0; - } - - return CGRectMake(0, verticalOffset, size.width, size.height); -} - -@end diff --git a/ios/renderer/LinkRenderer.m b/ios/renderer/LinkRenderer.m index 143bbb1d..c2f4efb3 100644 --- a/ios/renderer/LinkRenderer.m +++ b/ios/renderer/LinkRenderer.m @@ -1,6 +1,4 @@ #import "LinkRenderer.h" -#import "ENRMCitationAttachment.h" -#import "ENRMMentionAttachment.h" #import "FontUtils.h" #import "RenderContext.h" #import "RendererFactory.h" @@ -135,33 +133,36 @@ - (void)renderMentionNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context { - // Extract the child text to use as the pill label; the child nodes may be - // formatted text (e.g. **bold**), so we collapse to a plain string. + // Collapse children into a plain display string. The pill itself is rendered + // as inline text (not an NSTextAttachment), so native copy/paste/selection + // behave exactly like normal text — the "pill" look is painted by + // `MentionBackground` during the layout manager's draw cycle. NSMutableAttributedString *childBuffer = [[NSMutableAttributedString alloc] init]; [_rendererFactory renderChildrenOfNode:node into:childBuffer context:context]; NSString *displayText = childBuffer.string ?: @""; + if (displayText.length == 0) + return; + NSString *mentionURL = stripScheme(url, kMentionScheme); - // Inherit the current text attributes (font, color) so the pill sits in the - // same line metrics as the surrounding paragraph if the pill label has no - // explicit style override. + // Inherit surrounding paragraph attributes so the pill text participates in + // the current line's metrics. NSDictionary *baseAttrs = output.length > 0 ? [output attributesAtIndex:output.length - 1 effectiveRange:NULL] : @{}; + NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithDictionary:baseAttrs]; - ENRMMentionAttachment *attachment = [ENRMMentionAttachment attachmentWithDisplayText:displayText - url:mentionURL - config:_config]; - - NSMutableAttributedString *attachmentString = - [[NSMutableAttributedString attributedStringWithAttachment:attachment] mutableCopy]; - NSRange attachmentRange = NSMakeRange(0, attachmentString.length); - if (baseAttrs.count > 0) { - [attachmentString addAttributes:baseAttrs range:attachmentRange]; + UIFont *mentionFont = [_config mentionFont]; + if (mentionFont) { + attrs[NSFontAttributeName] = mentionFont; } - [attachmentString addAttribute:ENRMMentionURLAttributeName value:mentionURL range:attachmentRange]; - [attachmentString addAttribute:ENRMMentionTextAttributeName value:displayText range:attachmentRange]; + RCTUIColor *mentionColor = [_config mentionColor]; + if (mentionColor) { + attrs[NSForegroundColorAttributeName] = mentionColor; + } + attrs[ENRMMentionURLAttributeName] = mentionURL; + attrs[ENRMMentionTextAttributeName] = displayText; NSUInteger start = output.length; - [output appendAttributedString:attachmentString]; + [output appendAttributedString:[[NSAttributedString alloc] initWithString:displayText attributes:attrs]]; NSRange outputRange = NSMakeRange(start, output.length - start); [context registerMentionRange:outputRange url:mentionURL text:displayText]; @@ -174,35 +175,69 @@ - (void)renderCitationNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context { - // Render children into a throwaway buffer so we can collect the label text - // and inherit the surrounding font (used to scale the citation glyph). - NSMutableAttributedString *childBuffer = [[NSMutableAttributedString alloc] init]; - [_rendererFactory renderChildrenOfNode:node into:childBuffer context:context]; - NSString *displayText = childBuffer.string ?: @""; + NSUInteger start = output.length; + [_rendererFactory renderChildrenOfNode:node into:output context:context]; + NSRange range = NSMakeRange(start, output.length - start); + if (range.length == 0) + return; + NSString *targetURL = stripScheme(url, kCitationScheme); + NSString *labelText = [[output attributedSubstringFromRange:range] string] ?: @""; - NSDictionary *baseAttrs = output.length > 0 ? [output attributesAtIndex:output.length - 1 effectiveRange:NULL] : @{}; - UIFont *baseFont = baseAttrs[NSFontAttributeName]; - - ENRMCitationAttachment *attachment = [ENRMCitationAttachment attachmentWithDisplayText:displayText - url:targetURL - baseFont:baseFont - config:_config]; - - NSMutableAttributedString *attachmentString = - [[NSMutableAttributedString attributedStringWithAttachment:attachment] mutableCopy]; - NSRange attachmentRange = NSMakeRange(0, attachmentString.length); - if (baseAttrs.count > 0) { - [attachmentString addAttributes:baseAttrs range:attachmentRange]; - } - [attachmentString addAttribute:ENRMCitationURLAttributeName value:targetURL range:attachmentRange]; - [attachmentString addAttribute:ENRMCitationTextAttributeName value:displayText range:attachmentRange]; + CGFloat multiplier = [_config citationFontSizeMultiplier]; + CGFloat baselineOffsetPx = [_config citationBaselineOffsetPx]; + RCTUIColor *citationColor = [_config citationColor]; + NSString *fontWeight = [_config citationFontWeight]; + BOOL underline = [_config citationUnderline]; - NSUInteger start = output.length; - [output appendAttributedString:attachmentString]; - NSRange outputRange = NSMakeRange(start, output.length - start); + [output enumerateAttributesInRange:range + options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired + usingBlock:^(NSDictionary *attrs, NSRange subrange, BOOL *stop) { + NSMutableDictionary *newAttributes = [NSMutableDictionary dictionary]; + + UIFont *currentFont = attrs[NSFontAttributeName]; + if (currentFont && multiplier > 0) { + CGFloat newSize = currentFont.pointSize * multiplier; + UIFont *scaled = [RCTFont updateFont:currentFont + withFamily:nil + size:@(newSize) + weight:fontWeight.length > 0 ? fontWeight : nil + style:nil + variant:nil + scaleMultiplier:1.0]; + if (scaled) { + newAttributes[NSFontAttributeName] = scaled; + + CGFloat offset = baselineOffsetPx; + if (offset == 0) { + offset = (currentFont.capHeight - scaled.capHeight) * 0.5; + } + newAttributes[NSBaselineOffsetAttributeName] = @(offset); + } + } else if (baselineOffsetPx != 0) { + newAttributes[NSBaselineOffsetAttributeName] = @(baselineOffsetPx); + } + + if (citationColor) { + newAttributes[NSForegroundColorAttributeName] = citationColor; + } + + if (underline) { + newAttributes[NSUnderlineStyleAttributeName] = @(NSUnderlineStyleSingle); + if (citationColor) { + newAttributes[NSUnderlineColorAttributeName] = citationColor; + } + } + + if (newAttributes.count > 0) { + [output addAttributes:newAttributes range:subrange]; + } + }]; + + [output addAttribute:ENRMCitationURLAttributeName value:targetURL range:range]; + [output addAttribute:ENRMCitationTextAttributeName value:labelText range:range]; - [context registerCitationRange:outputRange url:targetURL text:displayText]; + [context registerCitationRange:range url:targetURL text:labelText]; } @end diff --git a/ios/utils/CitationBackground.h b/ios/utils/CitationBackground.h new file mode 100644 index 00000000..9aaff123 --- /dev/null +++ b/ios/utils/CitationBackground.h @@ -0,0 +1,24 @@ +#pragma once +#import "ENRMUIKit.h" +#import "StyleConfig.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Draws the padded rounded background behind any glyph range tagged with + * `ENRMCitationURLAttributeName`. Inline-text rendering of citations means + * copy/paste work naturally; the pill appearance is achieved purely by this + * background pass inside the NSLayoutManager draw cycle. + */ +@interface CitationBackground : NSObject + +- (instancetype)initWithConfig:(StyleConfig *)config; + +- (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow + layoutManager:(NSLayoutManager *)layoutManager + textContainer:(NSTextContainer *)textContainer + atPoint:(CGPoint)origin; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/utils/CitationBackground.m b/ios/utils/CitationBackground.m new file mode 100644 index 00000000..ce0c623e --- /dev/null +++ b/ios/utils/CitationBackground.m @@ -0,0 +1,79 @@ +#import "CitationBackground.h" +#import "ENRMUIKit.h" +#import "LinkRenderer.h" + +@implementation CitationBackground { + StyleConfig *_config; +} + +- (instancetype)initWithConfig:(StyleConfig *)config +{ + self = [super init]; + if (self) { + _config = config; + } + return self; +} + +- (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow + layoutManager:(NSLayoutManager *)layoutManager + textContainer:(NSTextContainer *)textContainer + atPoint:(CGPoint)origin +{ + NSTextStorage *textStorage = layoutManager.textStorage; + if (!textStorage || textStorage.length == 0) + return; + + NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphsToShow actualGlyphRange:NULL]; + if (charRange.location == NSNotFound || charRange.length == 0) + return; + + RCTUIColor *bgColor = [_config citationBackgroundColor]; + CGFloat paddingH = [_config citationPaddingHorizontal]; + CGFloat paddingV = [_config citationPaddingVertical]; + + if (!bgColor) + return; + + [textStorage + enumerateAttribute:ENRMCitationURLAttributeName + inRange:NSMakeRange(0, textStorage.length) + options:0 + usingBlock:^(id value, NSRange range, BOOL *stop) { + if (!value || range.length == 0) + return; + if (NSIntersectionRange(range, charRange).length == 0) + return; + + NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range actualCharacterRange:NULL]; + if (glyphRange.location == NSNotFound || glyphRange.length == 0) + return; + + [layoutManager + enumerateLineFragmentsForGlyphRange:glyphRange + usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *tc, + NSRange lineRange, BOOL *lineStop) { + NSRange intersect = NSIntersectionRange(lineRange, glyphRange); + if (intersect.length == 0) + return; + + CGRect glyphRect = + [layoutManager boundingRectForGlyphRange:intersect + inTextContainer:textContainer]; + + CGRect chipRect = CGRectMake(glyphRect.origin.x + origin.x - paddingH, + glyphRect.origin.y + origin.y - paddingV, + glyphRect.size.width + paddingH * 2, + glyphRect.size.height + paddingV * 2); + + CGFloat radius = MIN(chipRect.size.width, chipRect.size.height) / 2.0; + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:chipRect + cornerRadius:radius]; + + [bgColor setFill]; + [path fill]; + }]; + }]; +} + +@end diff --git a/ios/utils/MarkdownExtractor.m b/ios/utils/MarkdownExtractor.m index fc1dd0df..e29c4616 100644 --- a/ios/utils/MarkdownExtractor.m +++ b/ios/utils/MarkdownExtractor.m @@ -8,6 +8,7 @@ #import "ENRMMathInlineAttachment.h" #endif #import "LastElementUtils.h" +#import "LinkRenderer.h" #import "ListItemRenderer.h" #import "RuntimeKeys.h" #import "ThematicBreakAttachment.h" @@ -290,7 +291,18 @@ static void extractFontTraits(NSDictionary *attrs, BOOL *isBold, BOOL *isItalic, NSNumber *underlineStyle = attrs[NSUnderlineStyleAttributeName]; BOOL isUnderline = (underlineStyle != nil && [underlineStyle integerValue] != 0); + // Mentions / citations are stored as inline text tagged with custom + // attributes. When the range covers a mention we emit + // `[text](mention://)`; citations likewise become + // `[text](citation://)` so copy/paste roundtrips cleanly. NSString *linkURL = attrs[NSLinkAttributeName]; + NSString *mentionURL = attrs[ENRMMentionURLAttributeName]; + NSString *citationURL = attrs[ENRMCitationURLAttributeName]; + if (mentionURL) { + linkURL = [@"mention://" stringByAppendingString:mentionURL]; + } else if (citationURL) { + linkURL = [@"citation://" stringByAppendingString:citationURL]; + } NSString *segment = applyInlineFormatting(text, isBold, isItalic, isMonospace, isStrikethrough, isUnderline, linkURL); diff --git a/ios/utils/MentionBackground.h b/ios/utils/MentionBackground.h new file mode 100644 index 00000000..8ea931e6 --- /dev/null +++ b/ios/utils/MentionBackground.h @@ -0,0 +1,25 @@ +#pragma once +#import "ENRMUIKit.h" +#import "StyleConfig.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * Draws the rounded-pill background + optional border behind any glyph range + * tagged with the `ENRMMentionURLAttributeName` attribute. Runs from inside + * `NSLayoutManager.drawBackgroundForGlyphRange:` so mention pills don't + * require an NSTextAttachment — selection, copy/paste, and long-press all + * behave like normal inline text. + */ +@interface MentionBackground : NSObject + +- (instancetype)initWithConfig:(StyleConfig *)config; + +- (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow + layoutManager:(NSLayoutManager *)layoutManager + textContainer:(NSTextContainer *)textContainer + atPoint:(CGPoint)origin; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ios/utils/MentionBackground.m b/ios/utils/MentionBackground.m new file mode 100644 index 00000000..572bb0c5 --- /dev/null +++ b/ios/utils/MentionBackground.m @@ -0,0 +1,91 @@ +#import "MentionBackground.h" +#import "ENRMUIKit.h" +#import "LinkRenderer.h" + +@implementation MentionBackground { + StyleConfig *_config; +} + +- (instancetype)initWithConfig:(StyleConfig *)config +{ + self = [super init]; + if (self) { + _config = config; + } + return self; +} + +- (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow + layoutManager:(NSLayoutManager *)layoutManager + textContainer:(NSTextContainer *)textContainer + atPoint:(CGPoint)origin +{ + NSTextStorage *textStorage = layoutManager.textStorage; + if (!textStorage || textStorage.length == 0) + return; + + NSRange charRange = [layoutManager characterRangeForGlyphRange:glyphsToShow actualGlyphRange:NULL]; + if (charRange.location == NSNotFound || charRange.length == 0) + return; + + RCTUIColor *bgColor = [_config mentionBackgroundColor]; + RCTUIColor *borderColor = [_config mentionBorderColor]; + CGFloat borderWidth = [_config mentionBorderWidth]; + CGFloat borderRadius = [_config mentionBorderRadius]; + CGFloat paddingH = [_config mentionPaddingHorizontal]; + CGFloat paddingV = [_config mentionPaddingVertical]; + + // Bail early when there is nothing visible to draw. + if (!bgColor && (!borderColor || borderWidth <= 0)) + return; + + [textStorage + enumerateAttribute:ENRMMentionURLAttributeName + inRange:NSMakeRange(0, textStorage.length) + options:0 + usingBlock:^(id value, NSRange range, BOOL *stop) { + if (!value || range.length == 0) + return; + if (NSIntersectionRange(range, charRange).length == 0) + return; + + NSRange glyphRange = [layoutManager glyphRangeForCharacterRange:range actualCharacterRange:NULL]; + if (glyphRange.location == NSNotFound || glyphRange.length == 0) + return; + + [layoutManager + enumerateLineFragmentsForGlyphRange:glyphRange + usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *tc, + NSRange lineRange, BOOL *lineStop) { + NSRange intersect = NSIntersectionRange(lineRange, glyphRange); + if (intersect.length == 0) + return; + + CGRect glyphRect = + [layoutManager boundingRectForGlyphRange:intersect + inTextContainer:textContainer]; + CGRect pillRect = CGRectMake(glyphRect.origin.x + origin.x - paddingH, + glyphRect.origin.y + origin.y - paddingV, + glyphRect.size.width + paddingH * 2, + glyphRect.size.height + paddingV * 2); + + CGFloat radius = MIN( + borderRadius, MIN(pillRect.size.width, pillRect.size.height) / 2.0); + UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:pillRect + cornerRadius:radius]; + + if (bgColor) { + [bgColor setFill]; + [path fill]; + } + + if (borderColor && borderWidth > 0) { + path.lineWidth = borderWidth; + [borderColor setStroke]; + [path stroke]; + } + }]; + }]; +} + +@end diff --git a/ios/utils/RuntimeKeys.h b/ios/utils/RuntimeKeys.h index 5e1bef5e..182b8d46 100644 --- a/ios/utils/RuntimeKeys.h +++ b/ios/utils/RuntimeKeys.h @@ -32,6 +32,14 @@ extern void *kListMarkerDrawerKey; // Used by TextViewLayoutManager for code block background drawing extern void *kCodeBlockBackgroundKey; +// Key for storing MentionBackground instance on NSLayoutManager +// Used by TextViewLayoutManager for inline mention pill drawing +extern void *kMentionBackgroundKey; + +// Key for storing CitationBackground instance on NSLayoutManager +// Used by TextViewLayoutManager for inline citation chip drawing +extern void *kCitationBackgroundKey; + // Custom attribute keys for markdown type tracking (used for Copy Markdown) extern NSString *const MarkdownTypeAttributeName; diff --git a/ios/utils/RuntimeKeys.m b/ios/utils/RuntimeKeys.m index 02ae3e90..e2030f73 100644 --- a/ios/utils/RuntimeKeys.m +++ b/ios/utils/RuntimeKeys.m @@ -6,6 +6,8 @@ void *kBlockquoteBorderKey = &kBlockquoteBorderKey; void *kListMarkerDrawerKey = &kListMarkerDrawerKey; void *kCodeBlockBackgroundKey = &kCodeBlockBackgroundKey; +void *kMentionBackgroundKey = &kMentionBackgroundKey; +void *kCitationBackgroundKey = &kCitationBackgroundKey; // Custom attribute for markdown type tracking NSString *const MarkdownTypeAttributeName = @"MarkdownType"; diff --git a/ios/utils/TextViewLayoutManager.mm b/ios/utils/TextViewLayoutManager.mm index dca3717b..4bf11b4a 100644 --- a/ios/utils/TextViewLayoutManager.mm +++ b/ios/utils/TextViewLayoutManager.mm @@ -1,8 +1,10 @@ #import "TextViewLayoutManager.h" #import "BlockquoteBorder.h" +#import "CitationBackground.h" #import "CodeBackground.h" #import "CodeBlockBackground.h" #import "ListMarkerDrawer.h" +#import "MentionBackground.h" #import "RuntimeKeys.h" #import "StyleConfig.h" #import @@ -42,6 +44,12 @@ - (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origi ListMarkerDrawer *markerDrawer = [self getListMarkerDrawerWithConfig:config]; [markerDrawer drawMarkersForGlyphRange:glyphsToShow layoutManager:self textContainer:textContainer atPoint:origin]; + + MentionBackground *mentionBg = [self getMentionBackgroundWithConfig:config]; + [mentionBg drawBackgroundsForGlyphRange:glyphsToShow layoutManager:self textContainer:textContainer atPoint:origin]; + + CitationBackground *citationBg = [self getCitationBackgroundWithConfig:config]; + [citationBg drawBackgroundsForGlyphRange:glyphsToShow layoutManager:self textContainer:textContainer atPoint:origin]; } #pragma mark - Safe Property Accessors @@ -89,6 +97,26 @@ - (CodeBlockBackground *)getCodeBlockBackgroundWithConfig:(StyleConfig *)config return obj; } +- (MentionBackground *)getMentionBackgroundWithConfig:(StyleConfig *)config +{ + MentionBackground *obj = objc_getAssociatedObject(self, kMentionBackgroundKey); + if (!obj) { + obj = [[MentionBackground alloc] initWithConfig:config]; + objc_setAssociatedObject(self, kMentionBackgroundKey, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return obj; +} + +- (CitationBackground *)getCitationBackgroundWithConfig:(StyleConfig *)config +{ + CitationBackground *obj = objc_getAssociatedObject(self, kCitationBackgroundKey); + if (!obj) { + obj = [[CitationBackground alloc] initWithConfig:config]; + objc_setAssociatedObject(self, kCitationBackgroundKey, obj, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + } + return obj; +} + #pragma mark - Configuration - (StyleConfig *)config @@ -103,6 +131,8 @@ - (void)setConfig:(StyleConfig *)config objc_setAssociatedObject(self, kCodeBlockBackgroundKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, kBlockquoteBorderKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, kListMarkerDrawerKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, kMentionBackgroundKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); + objc_setAssociatedObject(self, kCitationBackgroundKey, nil, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, kStyleConfigKey, config, OBJC_ASSOCIATION_RETAIN_NONATOMIC); From 5ca23e358eda7fd783461930ac3cd5c945a4f3fe Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 12:18:50 -0700 Subject: [PATCH 04/23] android: allow copy/paste mention and citations as normal text --- .../markdown/renderer/LinkRenderer.kt | 14 +- .../enriched/markdown/spans/MentionSpan.kt | 174 ++++++++++-------- apps/example/src/App.tsx | 1 + apps/example/src/markdownStyles.ts | 2 +- apps/web-example/src/App.tsx | 20 +- 5 files changed, 121 insertions(+), 90 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt b/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt index d44eba0f..b7163ab9 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt @@ -79,18 +79,18 @@ class LinkRenderer( onLinkLongPress: ((String) -> Unit)?, factory: RendererFactory, ) { - // Render children into a throwaway buffer to derive the display label. - // Any inline formatting (bold/italic) inside the label collapses to plain - // text because ReplacementSpan paints a single atomic glyph. + // Render children into a throwaway buffer to derive the plain display + // label (any inline formatting inside the mention collapses to text). val labelBuffer = SpannableStringBuilder() factory.renderChildren(node, labelBuffer, onLinkPress, onLinkLongPress) val displayText = labelBuffer.toString() + if (displayText.isEmpty()) return - // Insert a single placeholder character that ReplacementSpan will paint - // over; keeping it as a real character preserves cursor metrics, selection - // handles, and accessibility traversal. + // Append the displayText as real characters so copy/paste, selection, and + // accessibility traversal all see the mention as normal text. The pill + // background is painted by the MentionSpan's LineBackgroundSpan pass. val start = builder.length - builder.append(' ') + builder.append(displayText) val end = builder.length val span = diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt index f69aa015..c841bcb2 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt @@ -4,23 +4,32 @@ import android.graphics.Canvas import android.graphics.Paint import android.graphics.RectF import android.graphics.Typeface -import android.text.style.ReplacementSpan +import android.text.Spanned +import android.text.StaticLayout +import android.text.TextPaint +import android.text.style.LineBackgroundSpan +import android.text.style.MetricAffectingSpan import com.swmansion.enriched.markdown.styles.MentionStyle +import kotlin.math.max +import kotlin.math.min /** - * Replaces a range of text with a rounded "pill" containing the display name. - * Rendering is atomic — the span reports its full width (including padding and - * border) via getSize so layout reserves enough room and the text never clips. + * Styles and paints the pill "chip" behind an inline mention. The mention + * text itself lives in the underlying Spannable as real characters, so copy, + * paste, selection, and accessibility all behave like ordinary text — the + * pill appearance is produced by this span's [LineBackgroundSpan.drawBackground] + * pass. * - * The span exposes [url] for tap dispatching and an [isPressed] flag the - * tap handler can toggle to drive the pressedOpacity tap-feedback animation. + * The span exposes [url] for tap dispatching and an [isPressed] flag the tap + * handler can toggle to drive the pressedOpacity feedback. */ class MentionSpan( val url: String, val displayText: String, private val mentionStyle: MentionStyle, private val mentionTypeface: Typeface?, -) : ReplacementSpan() { +) : MetricAffectingSpan(), + LineBackgroundSpan { @Volatile var isPressed: Boolean = false @@ -28,87 +37,84 @@ class MentionSpan( private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE - strokeWidth = mentionStyle.borderWidth } - private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG) + private val rect = RectF() - private fun configureTextPaint(basePaint: Paint) { - textPaint.set(basePaint) - if (mentionStyle.fontSize > 0) { - textPaint.textSize = mentionStyle.fontSize - } - mentionTypeface?.let { textPaint.typeface = it } - textPaint.color = mentionStyle.color + override fun updateMeasureState(textPaint: TextPaint) { + applyTextStyling(textPaint) } - private fun contentWidth(): Float = textPaint.measureText(displayText) + override fun updateDrawState(tp: TextPaint) { + applyTextStyling(tp) + tp.color = mentionStyle.color + } - override fun getSize( - paint: Paint, - text: CharSequence?, - start: Int, - end: Int, - fm: Paint.FontMetricsInt?, - ): Int { - configureTextPaint(paint) - - val textWidth = contentWidth() - val totalWidth = textWidth + mentionStyle.paddingHorizontal * 2f + mentionStyle.borderWidth * 2f - - if (fm != null) { - val metrics = textPaint.fontMetricsInt - val verticalInset = (mentionStyle.paddingVertical + mentionStyle.borderWidth).toInt() - fm.ascent = metrics.ascent - verticalInset - fm.top = metrics.top - verticalInset - fm.descent = metrics.descent + verticalInset - fm.bottom = metrics.bottom + verticalInset - fm.leading = metrics.leading + private fun applyTextStyling(paint: TextPaint) { + if (mentionStyle.fontSize > 0f) { + paint.textSize = mentionStyle.fontSize + } + if (mentionTypeface != null) { + paint.typeface = mentionTypeface + } else if (mentionStyle.fontWeight.isNotEmpty()) { + val base = paint.typeface ?: Typeface.DEFAULT + val weightStyle = + when (mentionStyle.fontWeight.lowercase()) { + "bold", "700", "800", "900" -> Typeface.BOLD + else -> Typeface.NORMAL + } + paint.typeface = Typeface.create(base, weightStyle) } - - return totalWidth.toInt() + 1 } - override fun draw( + override fun drawBackground( canvas: Canvas, + paint: Paint, + left: Int, + right: Int, + top: Int, + baseline: Int, + bottom: Int, text: CharSequence, start: Int, end: Int, - x: Float, - top: Int, - y: Int, - bottom: Int, - paint: Paint, + lineNum: Int, ) { - configureTextPaint(paint) - - val opacity = - if (isPressed) mentionStyle.pressedOpacity.coerceIn(0f, 1f) else 1f - val globalAlpha = (opacity * 255f).toInt().coerceIn(0, 255) - - val textWidth = contentWidth() - val pillWidth = textWidth + mentionStyle.paddingHorizontal * 2f + mentionStyle.borderWidth * 2f - val metrics = textPaint.fontMetricsInt - val textHeight = metrics.descent - metrics.ascent - val pillHeight = textHeight + mentionStyle.paddingVertical * 2f + mentionStyle.borderWidth * 2f - - // Vertically center the pill on the surrounding text line. - val lineTop = top.toFloat() - val lineBottom = bottom.toFloat() - val pillTop = lineTop + ((lineBottom - lineTop) - pillHeight) / 2f - val pillBottom = pillTop + pillHeight - - val halfStroke = mentionStyle.borderWidth / 2f - val pillRect = - RectF( - x + halfStroke, - pillTop + halfStroke, - x + pillWidth - halfStroke, - pillBottom - halfStroke, - ) + if (text !is Spanned) return + val spanStart = text.getSpanStart(this) + val spanEnd = text.getSpanEnd(this) + if (spanStart < 0 || spanEnd <= spanStart) return + + // Only paint on the line segment(s) the span intersects with. + val drawStart = max(spanStart, start) + val drawEnd = min(spanEnd, end) + if (drawStart >= drawEnd) return + + val opacity = if (isPressed) mentionStyle.pressedOpacity.coerceIn(0f, 1f) else 1f + + val textPaint = (paint as? TextPaint) ?: TextPaint(paint).apply { set(paint) } + // LineBackgroundSpan is invoked before the glyphs are drawn, so the paint + // hasn't been run through updateDrawState yet; apply mention-specific + // styling locally so measurements here match the rendered text exactly. + val localPaint = TextPaint(textPaint) + applyTextStyling(localPaint) + + val startOffset = horizontalOffset(text, start, end, drawStart, localPaint) + val endOffset = horizontalOffset(text, start, end, drawEnd, localPaint) + val paddingH = mentionStyle.paddingHorizontal + val paddingV = mentionStyle.paddingVertical + + val pillLeft = left + min(startOffset, endOffset) - paddingH + val pillRight = left + max(startOffset, endOffset) + paddingH + val pillTop = top - paddingV + val pillBottom = bottom + paddingV + if (pillRight <= pillLeft || pillBottom <= pillTop) return + + rect.set(pillLeft, pillTop, pillRight, pillBottom) + val radius = - minOf( + min( mentionStyle.borderRadius, - minOf(pillRect.width(), pillRect.height()) / 2f, + min(rect.width(), rect.height()) / 2f, ) fillPaint.color = mentionStyle.backgroundColor @@ -116,7 +122,7 @@ class MentionSpan( ((fillPaint.color ushr 24) and 0xFF).let { baseAlpha -> (baseAlpha * opacity).toInt().coerceIn(0, 255) } - canvas.drawRoundRect(pillRect, radius, radius, fillPaint) + canvas.drawRoundRect(rect, radius, radius, fillPaint) if (mentionStyle.borderWidth > 0f) { strokePaint.strokeWidth = mentionStyle.borderWidth @@ -125,14 +131,20 @@ class MentionSpan( ((strokePaint.color ushr 24) and 0xFF).let { baseAlpha -> (baseAlpha * opacity).toInt().coerceIn(0, 255) } - canvas.drawRoundRect(pillRect, radius, radius, strokePaint) + canvas.drawRoundRect(rect, radius, radius, strokePaint) } + } - textPaint.alpha = globalAlpha - - val textX = x + mentionStyle.paddingHorizontal + mentionStyle.borderWidth - // Baseline-align the label inside the pill. - val textY = pillTop + mentionStyle.paddingVertical + mentionStyle.borderWidth - metrics.ascent - canvas.drawText(displayText, textX, textY, textPaint) + private fun horizontalOffset( + text: CharSequence, + lineStart: Int, + lineEnd: Int, + index: Int, + paint: TextPaint, + ): Float { + if (index <= lineStart) return 0f + val lineText = text.subSequence(lineStart, lineEnd) + val layout = StaticLayout.Builder.obtain(lineText, 0, lineText.length, paint, Int.MAX_VALUE / 2).build() + return layout.getPrimaryHorizontal(index - lineStart) } } diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index 459ab89c..77ef0eb6 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -96,6 +96,7 @@ export default function App() { }, ]); }; + return ( diff --git a/apps/example/src/markdownStyles.ts b/apps/example/src/markdownStyles.ts index b8fb363a..7100744c 100644 --- a/apps/example/src/markdownStyles.ts +++ b/apps/example/src/markdownStyles.ts @@ -156,7 +156,7 @@ export const customMarkdownStyle: MarkdownStyle = { citation: { backgroundColor: '#EBEBFF', color: '#9B9BFD', - fontSizeMultiplier: 0.7, + fontSizeMultiplier: 0.5, baselineOffsetPx: 1.5, fontWeight: '', underline: false, diff --git a/apps/web-example/src/App.tsx b/apps/web-example/src/App.tsx index 7547f280..ee242748 100644 --- a/apps/web-example/src/App.tsx +++ b/apps/web-example/src/App.tsx @@ -5,6 +5,8 @@ import type { LinkPressEvent, LinkLongPressEvent, TaskListItemPressEvent, + CitationPressEvent, + MentionPressEvent, } from 'react-native-enriched-markdown'; import { sampleMarkdown } from './sampleMarkdown'; @@ -96,7 +98,7 @@ console.log(greet("العالم")); `.trim(); interface EventLog { - kind: 'link' | 'linkLong' | 'task'; + kind: 'link' | 'linkLong' | 'task' | 'citation' | 'mention'; label: string; detail: string; } @@ -105,6 +107,8 @@ const KIND_COLOR: Record = { link: '#2563EB', linkLong: '#7C3AED', task: '#059669', + citation: '#9B9BFD', + mention: '#2563EB', }; export default function App() { @@ -130,6 +134,18 @@ export default function App() { [] ); + const handleCitationPress = (event: CitationPressEvent) => { + const { url } = event; + setLastEvent({ kind: 'citation', label: 'onCitationPress', detail: url }); + Linking.openURL(url); + }; + + const handleMentionPress = (event: MentionPressEvent) => { + const { url } = event; + setLastEvent({ kind: 'mention', label: 'onMentionPress', detail: url }); + Linking.openURL(url); + }; + return ( @@ -153,6 +169,8 @@ export default function App() { onLinkPress={onLinkPress} onLinkLongPress={onLinkLongPress} onTaskListItemPress={onTaskListItemPress} + onCitationPress={handleCitationPress} + onMentionPress={handleMentionPress} /> From dbf4d5054ceeafdb0b7a2198946e5a07252f9fad Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 12:57:34 -0700 Subject: [PATCH 05/23] exclude citation from pasted content --- .../utils/text/view/SelectionActionMode.kt | 38 ++++++++- ios/utils/PasteboardUtils.m | 85 +++++++++++++++++-- src/web/EnrichedMarkdownText.tsx | 68 ++++++++++++++- src/web/renderers/InlineRenderers.tsx | 2 + 4 files changed, 185 insertions(+), 8 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt index e358b1e2..e5e30a66 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/SelectionActionMode.kt @@ -11,6 +11,7 @@ import android.view.ViewParent import android.widget.TextView import com.swmansion.enriched.markdown.EnrichedMarkdown import com.swmansion.enriched.markdown.EnrichedMarkdownText +import com.swmansion.enriched.markdown.spans.CitationSpan import com.swmansion.enriched.markdown.spans.ImageSpan import com.swmansion.enriched.markdown.styles.StyleConfig import com.swmansion.enriched.markdown.utils.common.layout.isLayoutRTL @@ -123,7 +124,10 @@ private fun TextView.copyWithHTML() { val spannable = text as? Spannable ?: return val selectedText = spannable.subSequence(start, end) - val plainText = selectedText.toString() + // Citation text (e.g. superscript markers) is reference metadata, not prose; + // strip it from the plain-text flavor so pasting into a plain text target + // yields clean copy. Rich flavors (HTML below) still keep the marker. + val plainText = buildPlainTextWithoutCitations(selectedText) val styleConfig = (this as? EnrichedMarkdownText)?.markdownStyle @@ -148,6 +152,38 @@ private fun TextView.copyWithHTML() { } } +/** + * Rebuilds a plain-text string from a selected CharSequence with any ranges + * tagged by a [CitationSpan] elided. The selection is indexed from 0, so span + * positions need to be translated against the passed-in CharSequence. + */ +private fun buildPlainTextWithoutCitations(selection: CharSequence): String { + if (selection !is Spannable) return selection.toString() + + val spans = selection.getSpans(0, selection.length, CitationSpan::class.java) + if (spans.isEmpty()) return selection.toString() + + // Collect non-overlapping, non-zero-length ranges sorted by start index. + val ranges = + spans + .map { selection.getSpanStart(it) to selection.getSpanEnd(it) } + .filter { it.second > it.first } + .sortedBy { it.first } + + val result = StringBuilder(selection.length) + var cursor = 0 + for ((spanStart, spanEnd) in ranges) { + if (spanStart > cursor) { + result.append(selection.subSequence(cursor, spanStart)) + } + cursor = maxOf(cursor, spanEnd) + } + if (cursor < selection.length) { + result.append(selection.subSequence(cursor, selection.length)) + } + return result.toString() +} + private fun TextView.copyMarkdownToClipboard() { val markdown = MarkdownExtractor.getMarkdownForSelection(this) ?: return val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager diff --git a/ios/utils/PasteboardUtils.m b/ios/utils/PasteboardUtils.m index c89ce549..7a674c51 100644 --- a/ios/utils/PasteboardUtils.m +++ b/ios/utils/PasteboardUtils.m @@ -1,6 +1,7 @@ #import "PasteboardUtils.h" #import "ENRMImageAttachment.h" #import "HTMLGenerator.h" +#import "LinkRenderer.h" #import "MarkdownExtractor.h" #import "RTFExportUtils.h" #import "StyleConfig.h" @@ -53,6 +54,71 @@ static void addHTMLData(NSMutableDictionary *items, NSAttributedString *attribut } } +/** + * Returns a copy of the attributed string with any character ranges tagged by + * `ENRMCitationURLAttributeName` removed entirely. Citations are reference + * metadata, not prose — stripping them here keeps every export flavor + * (plain, HTML, RTF, RTFD, markdown) consistently citation-free, so pasting + * into a rich-text destination like Notes or Mail no longer surfaces the + * citation marker. + */ +static NSAttributedString *attributedStringWithoutCitations(NSAttributedString *attributedString) +{ + NSRange fullRange = NSMakeRange(0, attributedString.length); + NSMutableArray *rangesToRemove = [NSMutableArray array]; + + [attributedString enumerateAttribute:ENRMCitationURLAttributeName + inRange:fullRange + options:0 + usingBlock:^(id value, NSRange range, BOOL *stop) { + if (!value || range.length == 0) + return; + [rangesToRemove addObject:[NSValue valueWithRange:range]]; + }]; + + if (rangesToRemove.count == 0) + return attributedString; + + NSMutableAttributedString *mutable = [attributedString mutableCopy]; + // Delete in reverse so earlier ranges remain valid after the edits. + for (NSInteger i = (NSInteger)rangesToRemove.count - 1; i >= 0; i--) { + NSRange range = [rangesToRemove[i] rangeValue]; + if (NSMaxRange(range) <= mutable.length) { + [mutable deleteCharactersInRange:range]; + } + } + return mutable; +} + +/** + * Strips `[text](citation://...)` occurrences from a pre-extracted markdown + * string so the markdown pasteboard flavor stays consistent with the other + * flavors in the default Copy action. + */ +static NSString *markdownWithoutCitations(NSString *markdown) +{ + if (markdown.length == 0) + return markdown; + + static NSRegularExpression *regex = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + // Matches a standard CommonMark link where the URL begins with `citation://`. + // The label body uses `[^\]]*` so it stops at the first `]` — nested + // brackets in citation labels aren't a supported case in our emitter. + regex = [NSRegularExpression regularExpressionWithPattern:@"\\[[^\\]]*\\]\\(citation://[^\\)]*\\)" + options:0 + error:NULL]; + }); + + if (!regex) + return markdown; + return [regex stringByReplacingMatchesInString:markdown + options:0 + range:NSMakeRange(0, markdown.length) + withTemplate:@""]; +} + #pragma mark - Public API void copyStringToPasteboard(NSString *string) @@ -90,20 +156,29 @@ void copyAttributedStringToPasteboard(NSAttributedString *attributedString, NSSt if (!attributedString || attributedString.length == 0) return; + // Elide citations before deriving any export flavor so rich-text + // destinations (Notes, Mail, Pages, contenteditable, etc.) don't end up + // with the reference marker when the user pastes. The dedicated + // "Copy as Markdown" action still preserves citations for round-tripping. + NSAttributedString *cleaned = attributedStringWithoutCitations(attributedString); + if (cleaned.length == 0) + return; + NSMutableDictionary *items = [NSMutableDictionary dictionary]; - items[kUTIPlainText] = attributedString.string; + items[kUTIPlainText] = cleaned.string; - if (markdown.length > 0) { - items[kUTIMarkdown] = markdown; + NSString *cleanedMarkdown = markdownWithoutCitations(markdown); + if (cleanedMarkdown.length > 0) { + items[kUTIMarkdown] = cleanedMarkdown; } if (styleConfig) { - addHTMLData(items, attributedString, styleConfig); + addHTMLData(items, cleaned, styleConfig); } // RTF export requires preprocessing (backgrounds, markers, normalized spacing) - NSAttributedString *rtfPrepared = prepareAttributedStringForRTFExport(attributedString, styleConfig); + NSAttributedString *rtfPrepared = prepareAttributedStringForRTFExport(cleaned, styleConfig); NSRange rtfRange = NSMakeRange(0, rtfPrepared.length); addRTFDData(items, rtfPrepared, rtfRange); diff --git a/src/web/EnrichedMarkdownText.tsx b/src/web/EnrichedMarkdownText.tsx index 80fc2e14..f7d60e8e 100644 --- a/src/web/EnrichedMarkdownText.tsx +++ b/src/web/EnrichedMarkdownText.tsx @@ -1,4 +1,11 @@ -import { useState, useEffect, useMemo, type CSSProperties } from 'react'; +import { + useState, + useEffect, + useMemo, + useCallback, + type CSSProperties, + type ClipboardEvent, +} from 'react'; import type { EnrichedMarkdownTextProps } from '../types/MarkdownTextProps.web'; import { normalizeMarkdownStyle } from '../normalizeMarkdownStyle.web'; import { @@ -8,6 +15,7 @@ import { } from './styles'; import { parseMarkdown } from './parseMarkdown'; import { RenderNode } from './renderers'; +import { CITATION_CLASS } from './renderers/InlineRenderers'; import type { ASTNode, RendererCallbacks, RenderCapabilities } from './types'; import { indexTaskItems, markInlineImages } from './utils'; import { loadKaTeX } from './katex'; @@ -119,6 +127,62 @@ export const EnrichedMarkdownText = ({ [containerStyle, selectable] ); + // The browser's default copy picks up the text content of the selected + // DOM, which would include citation markers. Citations are reference + // metadata, not prose, so we rewrite the plain-text flavor to elide them + // while keeping the HTML flavor intact for rich-text destinations. + // + // DOM types aren't in the tsconfig lib list, so we narrow through + // locally-scoped interfaces to access only the few APIs we need. + const handleCopy = useCallback((event: ClipboardEvent) => { + const globals = globalThis as unknown as { + window?: { + getSelection?: () => { + rangeCount: number; + getRangeAt: (i: number) => { + collapsed: boolean; + cloneContents: () => unknown; + }; + } | null; + }; + document?: { + createElement: (tag: string) => { + appendChild: (node: unknown) => void; + querySelectorAll: ( + selector: string + ) => Iterable<{ remove: () => void }>; + textContent: string | null; + innerHTML: string; + }; + }; + }; + + const win = globals.window; + const doc = globals.document; + if (!win || !doc) return; + + const selection = win.getSelection?.(); + if (!selection || selection.rangeCount === 0) return; + const range = selection.getRangeAt(0); + if (range.collapsed) return; + + const container = doc.createElement('div'); + container.appendChild(range.cloneContents()); + + for (const node of container.querySelectorAll(`.${CITATION_CLASS}`)) { + node.remove(); + } + + const clipboardData = ( + event as unknown as { + clipboardData: { setData: (type: string, data: string) => void }; + } + ).clipboardData; + clipboardData.setData('text/plain', container.textContent ?? ''); + clipboardData.setData('text/html', container.innerHTML); + event.preventDefault(); + }, []); + if (parseError) { return (
@@ -133,7 +197,7 @@ export const EnrichedMarkdownText = ({ const lastIdx = children.length - 1; return ( -
+
{children.map((child, index) => ( ` element so we @@ -156,6 +157,7 @@ function CitationRenderer({ return ( Date: Fri, 17 Apr 2026 12:59:17 -0700 Subject: [PATCH 06/23] web: allow select and highlight mentions --- src/web/styles.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/web/styles.ts b/src/web/styles.ts index 2ad3113f..b430c7af 100644 --- a/src/web/styles.ts +++ b/src/web/styles.ts @@ -262,7 +262,6 @@ function mentionStyle(style: MarkdownStyleInternal): CSSProperties { fontWeight: normalizeFontWeight(mention.fontWeight), fontSize: mention.fontSize || undefined, cursor: 'pointer', - userSelect: 'none', transition: 'opacity 0.12s ease-in-out', lineHeight: 1, }; From 30a50467b8b40569803c8485b17bb5013280ecb7 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 14:23:10 -0700 Subject: [PATCH 07/23] add citation border props --- ...mention_citation_tooltips_18cf1b1d.plan.md | 268 ++++++++++++++++++ .../enriched/markdown/spans/CitationSpan.kt | 30 +- .../enriched/markdown/styles/CitationStyle.kt | 9 + apps/example/src/markdownStyles.ts | 13 +- apps/web-example/src/App.tsx | 25 ++ ios/styles/StyleConfig.h | 6 + ios/styles/StyleConfig.mm | 36 +++ ios/utils/CitationBackground.m | 56 +++- ios/utils/MentionBackground.m | 35 ++- ios/utils/StylePropsUtils.h | 19 ++ src/EnrichedMarkdownNativeComponent.ts | 3 + src/EnrichedMarkdownTextNativeComponent.ts | 3 + src/normalizeMarkdownStyle.ts | 3 + src/normalizeMarkdownStyle.web.ts | 3 + src/types/MarkdownStyle.ts | 15 + src/types/MarkdownStyleInternal.ts | 3 + src/web/styles.ts | 11 +- 17 files changed, 509 insertions(+), 29 deletions(-) create mode 100644 .cursor/plans/mention_citation_tooltips_18cf1b1d.plan.md diff --git a/.cursor/plans/mention_citation_tooltips_18cf1b1d.plan.md b/.cursor/plans/mention_citation_tooltips_18cf1b1d.plan.md new file mode 100644 index 00000000..9349a20b --- /dev/null +++ b/.cursor/plans/mention_citation_tooltips_18cf1b1d.plan.md @@ -0,0 +1,268 @@ +--- +name: mention citation tooltips +overview: Emit hover events with pill coordinates on web so consumers can render their own mention/citation tooltips, and add optional rect data to the existing onMentionPress / onCitationPress events on all platforms. +todos: + - id: event_types + content: Add InlineElementRect and extend MentionPressEvent / CitationPressEvent with optional rect in src/types/events.ts; add new MentionHoverEvent / CitationHoverEvent + status: pending + - id: types_web + content: Extend src/types/MarkdownTextProps.web.ts with onMentionHoverChange and onCitationHoverChange + status: pending + - id: renderer_types + content: Extend RendererCallbacks in src/web/types.ts with the two new hover callbacks + status: pending + - id: wire_inline_renderers + content: In src/web/renderers/InlineRenderers.tsx, fire onMentionHoverChange / onCitationHoverChange on pointerenter / pointerleave / focus / blur, and include rect in onMentionPress / onCitationPress on click + status: pending + - id: wire_web_root + content: Thread the two new hover props through src/web/EnrichedMarkdownText.tsx with stable memoization + status: pending + - id: types_shared + content: Mirror the event-type additions in src/types/MarkdownTextProps.ts so rect shows up on the native-facing press events too (documented per-platform as container-relative) + status: pending +isProject: false +--- + +## Goal + +- Web: emit hover-in / hover-out events with the hovered pill's viewport coordinates so consumers can render their own tooltip. +- All platforms: add an optional `rect` field to the existing `onMentionPress` / `onCitationPress` events. No new long-press events, no gesture changes on mobile. + +## Public API + +### Event types — [src/types/events.ts](src/types/events.ts) + +```ts +export interface InlineElementRect { + /** + * Web: viewport-relative (from getBoundingClientRect()). + * iOS / Android: container-relative (origin is the EnrichedMarkdownText view). + */ + x: number; + y: number; + width: number; + height: number; +} + +export interface MentionPressEvent { + url: string; + text: string; + /** Pill bounding rect at press time. Present on all platforms. */ + rect?: InlineElementRect; +} + +export interface CitationPressEvent { + url: string; + text: string; + rect?: InlineElementRect; +} + +export interface MentionHoverEvent { + url: string; + text: string; + hovered: boolean; + /** Present when hovered === true; omitted on hover-out. */ + rect?: InlineElementRect; +} + +export interface CitationHoverEvent { + url: string; + text: string; + hovered: boolean; + rect?: InlineElementRect; +} +``` + +`rect` is optional to preserve back-compat (future platforms / edge cases where we can't compute it can omit it). + +### New props — [src/types/MarkdownTextProps.web.ts](src/types/MarkdownTextProps.web.ts) + +```ts +onMentionHoverChange?: (event: MentionHoverEvent) => void; +onCitationHoverChange?: (event: CitationHoverEvent) => void; +``` + +Single event with `hovered: boolean` rather than two separate in/out events so consumers can drive a `useState` with one handler and avoid stale-state races. + +## Web implementation (this pass) + +Files touched: + +- [src/types/events.ts](src/types/events.ts): add `InlineElementRect`, extend `MentionPressEvent` / `CitationPressEvent` with `rect`, add `MentionHoverEvent` / `CitationHoverEvent`. +- [src/types/MarkdownTextProps.web.ts](src/types/MarkdownTextProps.web.ts): add `onMentionHoverChange`, `onCitationHoverChange`. +- [src/types/MarkdownTextProps.ts](src/types/MarkdownTextProps.ts): no new props (native keeps same two press callbacks), but imports updated event types automatically. +- [src/web/types.ts](src/web/types.ts): add `onMentionHoverChange` / `onCitationHoverChange` to `RendererCallbacks`. +- [src/web/EnrichedMarkdownText.tsx](src/web/EnrichedMarkdownText.tsx): accept + memoize the two new props into `callbacks`. +- [src/web/renderers/InlineRenderers.tsx](src/web/renderers/InlineRenderers.tsx): wire pointer / focus handlers on mention `` and citation ``; include `rect` on click handlers. + +### Renderer change (sketch) + +```tsx +function MentionRenderer({ url, callbacks, node, styles }: SchemeRendererProps) { + const mentionUrl = url.slice(MENTION_SCHEME.length); + const displayText = extractNodeText(node); + + const rectFromEvent = ( + event: { currentTarget: { getBoundingClientRect: () => InlineElementRect } } + ): InlineElementRect => { + const r = event.currentTarget.getBoundingClientRect(); + return { x: r.x, y: r.y, width: r.width, height: r.height }; + }; + + const handleClick = (event: MouseEvent) => { + event.preventDefault(); + callbacks.onMentionPress?.({ + url: mentionUrl, + text: displayText, + rect: rectFromEvent(event), + }); + }; + + const handleHoverIn = (event: PointerEvent | FocusEvent) => { + callbacks.onMentionHoverChange?.({ + url: mentionUrl, + text: displayText, + hovered: true, + rect: rectFromEvent(event), + }); + }; + + const handleHoverOut = () => { + callbacks.onMentionHoverChange?.({ + url: mentionUrl, + text: displayText, + hovered: false, + }); + }; + + // ... existing pressed-opacity style unchanged ... + + return ( + <> + + + {displayText} + + + ); +} +``` + +Citations get the same rect-on-click + hover handlers on ``. + +### Semantics + +- `rect` on web is viewport-coordinates (`getBoundingClientRect()`), captured once at event emission time. +- Hover-in fires on `pointerenter` and `focus` (keyboard a11y). Hover-out fires on `pointerleave` and `blur`. +- No debounce / delay in the library — consumers add `setTimeout` if they want a hover delay. +- Existing click → `onMentionPress` / `onCitationPress` behavior is unchanged except the payload now carries `rect`. + +### Scroll behavior (documented, not handled) + +The library emits `rect` once per event — it does not re-emit on scroll or resize. If the page scrolls (or an ancestor scroll container scrolls) while a tooltip is open, the stored viewport rect goes stale and the tooltip will visually detach from the pill. + +Recommended consumer patterns, listed from simplest to most robust, go into the JSDoc for `onMentionHoverChange` / `onCitationHoverChange`: + +1. **Dismiss on scroll.** Listen to `scroll` on `window` (capture) while hovered and clear tooltip state. Simplest and most common tooltip UX. +2. **Re-measure on scroll.** Keep the mention id in state on hover-in, and re-query the DOM (`document.querySelector(` `` `[data-mention-url="${url}"]` `` `)`) on scroll to refresh position. Works because `InlineRenderers` already stamps `data-mention-url` / `data-citation-url` on the element. +3. **Upgrade to a ref-based lib.** If live repositioning matters, a future iteration of this plan can add an optional `target: HTMLElement` field to the web hover event without breaking back-compat. Out of scope here. + +Same story on native: `rect` is emitted once, and if the user scrolls the surrounding RN `ScrollView` the consumer is responsible for dismissing or re-emitting (typically wired to the `ScrollView`'s `onScroll`). This library does not own the scroll container and has no API for it. + +## Native plan (documented, not implemented) + +Only one change needed on each platform: include `rect` in the existing press event. No new gestures, no new callbacks. + +iOS ([ios/utils/LinkTapUtils.m](ios/utils/LinkTapUtils.m), [ios/EnrichedMarkdownText.mm](ios/EnrichedMarkdownText.mm)): + +- At the point `inlineElementAtTapLocation` resolves a mention or citation, compute the full attribute run's character range (`effectiveRange:` out-param from `attributesAtIndex:`), then call `NSLayoutManager boundingRectForGlyphRange:inTextContainer:` for that glyph range. +- Combine with `lineFragmentRectForGlyphAtIndex:` for the hit glyph when the run is multi-line, so line-height and padding are accounted for correctly (not just the glyph ink extent). +- Convert to the `EnrichedMarkdownText` view's coordinate space by adding the text container inset / the host view's `bounds.origin` offset as used elsewhere in this file. +- Include the rect on the `MentionPressEvent` / `CitationPressEvent` payload emitted from `EnrichedMarkdownText.mm`. + +Android ([android/.../spans/MentionSpan.kt](android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt), [android/.../spans/CitationSpan.kt](android/src/main/java/com/swmansion/enriched/markdown/spans/CitationSpan.kt), [android/.../utils/text/view/LinkLongPressMovementMethod.kt](android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/LinkLongPressMovementMethod.kt)): + +- In `LinkLongPressMovementMethod.onTouchEvent` where `onMentionTap` / `onCitationTap` currently fire, compute the span's rect from `Layout.getPrimaryHorizontal(spanStart..spanEnd)` + `Layout.getLineTop` / `getLineBottom` for the hit line. +- `Layout` coordinates are text-relative, not view-relative. Before emitting, add `textView.totalPaddingLeft` to x and `textView.totalPaddingTop` to y so the rect is aligned with the `EnrichedMarkdownText` View's origin. +- Thread the rect through `MentionPressEvent.kt` / `CitationPressEvent.kt` to the JS event. + +Both platforms report **container-relative** rects (relative to the `EnrichedMarkdownText` view), documented on `InlineElementRect`. + +Since this only extends an existing optional field, it is fully backward-compatible: consumers already reading `url` / `text` see no changes. + +Hover events on native are explicitly out of scope — mobile has no hover. + +### Consumer guidance on native (to include in JSDoc for `rect`) + +Most RN positioning libraries (e.g. `@floating-ui/react-native`) expect window / global coordinates, not container-relative ones. To convert: + +```tsx +const ref = useRef(null); +const [containerOrigin, setContainerOrigin] = useState({ x: 0, y: 0 }); + + { + ref.current?.measureInWindow((x, y) => setContainerOrigin({ x, y })); + }} + onMentionPress={({ url, text, rect }) => { + const windowRect = rect && { + x: rect.x + containerOrigin.x, + y: rect.y + containerOrigin.y, + width: rect.width, + height: rect.height, + }; + // feed windowRect to Floating UI / custom overlay + }} +/> +``` + +Rationale for keeping the emitted rect container-relative rather than global: the component itself may move (animated transitions, keyboard avoidance, parent re-layout), and a container-relative rect remains valid as long as the consumer re-measures the container on layout changes. + +## Flow diagram + +```mermaid +sequenceDiagram + participant User + participant Renderer as InlineRenderers + participant Consumer as App + + Note over User,Consumer: Hover (web only) + User->>Renderer: pointerenter on mention + Renderer->>Consumer: onMentionHoverChange({hovered:true, url, text, rect}) + Consumer->>Consumer: setState -> render custom tooltip overlay + User->>Renderer: pointerleave + Renderer->>Consumer: onMentionHoverChange({hovered:false, url, text}) + + Note over User,Consumer: Press (all platforms) + User->>Renderer: click / tap on mention + Renderer->>Consumer: onMentionPress({url, text, rect}) +``` + +## Out of scope + +- No long-press events for mention / citation on mobile. +- No tooltip UI shipped with the library — consumers own all rendering / positioning / a11y. +- No markdown syntax changes; no new `MarkdownStyle` slots. + +## Risks / notes + +- Multi-line mention wraps: `getBoundingClientRect()` returns the bounding box across all line fragments on web (one tall rect spanning both lines); native equivalents collapse to the hit line. Documented platform asymmetry. If this becomes visually jarring, a future iteration can switch the web rect to `event.currentTarget.getClientRects()[hitLineIndex]` to anchor to the specific hovered line — non-breaking since `InlineElementRect` shape stays the same. +- Rect is optional on both press and hover event types. Consumers must null-check before using it. +- Rect is a point-in-time snapshot. The library does not track the pill through scrolls / resizes; handling that is the consumer's responsibility (see "Scroll behavior" above). Deliberate choice to keep the API thin and uniform across platforms. + +## Future considerations (not in this pass) + +- **`target: HTMLElement` field on web hover event.** Enables live re-positioning with Floating UI / Popper on scroll/resize without consumer re-querying DOM. Additive, non-breaking. +- **`padding` or `inset` in `InlineElementRect`.** If consumers want the rect slightly expanded (e.g. to allow a fade/slide tooltip entry animation without visual jitter at the edges), a numeric padding can be added to the event payload later. Out of scope for v1 — consumers can expand the rect themselves. +- **Per-line-fragment rect on web** for long multi-line mention wraps (see Risks above). diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/CitationSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/CitationSpan.kt index b92a4381..01e0b3d4 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/spans/CitationSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/CitationSpan.kt @@ -23,6 +23,7 @@ class CitationSpan( private val citationStyle: CitationStyle, ) : ReplacementSpan() { private val fillPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.FILL } + private val strokePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { style = Paint.Style.STROKE } private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.SUBPIXEL_TEXT_FLAG) private fun configureTextPaint(basePaint: Paint) { @@ -104,15 +105,30 @@ class CitationSpan( val bgTop = glyphBaseline + textAscent - paddingV val bgBottom = glyphBaseline + textDescent + paddingV + val maxRadius = minOf((bgBottom - bgTop) / 2f, chipWidth / 2f) + val radius = minOf(citationStyle.borderRadius, maxRadius) + val chipRect = RectF(x, bgTop, x + chipWidth, bgBottom) + if (citationStyle.backgroundColor != null && citationStyle.backgroundColor != 0) { fillPaint.color = citationStyle.backgroundColor - val radius = minOf((bgBottom - bgTop) / 2f, chipWidth / 2f) - canvas.drawRoundRect( - RectF(x, bgTop, x + chipWidth, bgBottom), - radius, - radius, - fillPaint, - ) + canvas.drawRoundRect(chipRect, radius, radius, fillPaint) + } + + if (citationStyle.borderColor != null && citationStyle.borderColor != 0 && citationStyle.borderWidth > 0f) { + strokePaint.color = citationStyle.borderColor + strokePaint.strokeWidth = citationStyle.borderWidth + // Inset the stroke by half its width so the border stays inside the chip + // rect (matches the iOS UIBezierPath stroke). + val halfStroke = citationStyle.borderWidth / 2f + val borderRect = + RectF( + chipRect.left + halfStroke, + chipRect.top + halfStroke, + chipRect.right - halfStroke, + chipRect.bottom - halfStroke, + ) + val borderRadius = minOf(radius, minOf(borderRect.width(), borderRect.height()) / 2f) + canvas.drawRoundRect(borderRect, borderRadius, borderRadius, strokePaint) } canvas.drawText(displayText, x + paddingH, glyphBaseline, textPaint) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/styles/CitationStyle.kt b/android/src/main/java/com/swmansion/enriched/markdown/styles/CitationStyle.kt index c07ef43e..8d8f3325 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/styles/CitationStyle.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/styles/CitationStyle.kt @@ -11,6 +11,9 @@ data class CitationStyle( val backgroundColor: Int?, val paddingHorizontal: Float, val paddingVertical: Float, + val borderColor: Int?, + val borderWidth: Float, + val borderRadius: Float, ) { companion object { fun fromReadableMap( @@ -25,6 +28,9 @@ data class CitationStyle( val backgroundColor = parser.parseOptionalColor(map, "backgroundColor") val paddingHorizontal = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "paddingHorizontal").toFloat()) val paddingVertical = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "paddingVertical").toFloat()) + val borderColor = parser.parseOptionalColor(map, "borderColor") + val borderWidth = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "borderWidth").toFloat()) + val borderRadius = parser.toPixelFromDIP(parser.parseOptionalDouble(map, "borderRadius", 999.0).toFloat()) return CitationStyle( color = color, @@ -35,6 +41,9 @@ data class CitationStyle( backgroundColor = backgroundColor, paddingHorizontal = paddingHorizontal, paddingVertical = paddingVertical, + borderColor = borderColor, + borderWidth = borderWidth, + borderRadius = borderRadius, ) } } diff --git a/apps/example/src/markdownStyles.ts b/apps/example/src/markdownStyles.ts index 7100744c..7ba72e34 100644 --- a/apps/example/src/markdownStyles.ts +++ b/apps/example/src/markdownStyles.ts @@ -146,9 +146,9 @@ export const customMarkdownStyle: MarkdownStyle = { backgroundColor: '#EBEBFF', borderColor: '#ddd6fe', borderWidth: 1, - borderRadius: 4, + borderRadius: 99, paddingHorizontal: 4, - paddingVertical: 2, + paddingVertical: 0, fontFamily: 'Montserrat-Regular', fontSize: 14, color: '#2563fb', @@ -157,10 +157,13 @@ export const customMarkdownStyle: MarkdownStyle = { backgroundColor: '#EBEBFF', color: '#9B9BFD', fontSizeMultiplier: 0.5, - baselineOffsetPx: 1.5, + baselineOffsetPx: 7, fontWeight: '', underline: false, - paddingHorizontal: 0, - paddingVertical: 0, + paddingHorizontal: 4, + paddingVertical: 2, + borderColor: '#ddd6fe', + borderWidth: 1, + borderRadius: 99, }, }; diff --git a/apps/web-example/src/App.tsx b/apps/web-example/src/App.tsx index ee242748..493615a0 100644 --- a/apps/web-example/src/App.tsx +++ b/apps/web-example/src/App.tsx @@ -171,6 +171,31 @@ export default function App() { onTaskListItemPress={onTaskListItemPress} onCitationPress={handleCitationPress} onMentionPress={handleMentionPress} + markdownStyle={{ + mention: { + backgroundColor: '#EBEBFF', + borderColor: '#ddd6fe', + borderWidth: 1, + borderRadius: 99, + paddingHorizontal: 4, + paddingVertical: 0, + fontSize: 14, + color: '#2563fb', + }, + citation: { + backgroundColor: '#EBEBFF', + color: '#9B9BFD', + fontSizeMultiplier: 0.5, + baselineOffsetPx: 7, + fontWeight: '', + underline: false, + paddingHorizontal: 4, + paddingVertical: 2, + borderColor: '#ddd6fe', + borderWidth: 1, + borderRadius: 99, + }, + }} /> diff --git a/ios/styles/StyleConfig.h b/ios/styles/StyleConfig.h index 6b87e93c..ecdedff1 100644 --- a/ios/styles/StyleConfig.h +++ b/ios/styles/StyleConfig.h @@ -405,5 +405,11 @@ - (void)setCitationPaddingHorizontal:(CGFloat)newValue; - (CGFloat)citationPaddingVertical; - (void)setCitationPaddingVertical:(CGFloat)newValue; +- (RCTUIColor *)citationBorderColor; +- (void)setCitationBorderColor:(RCTUIColor *)newValue; +- (CGFloat)citationBorderWidth; +- (void)setCitationBorderWidth:(CGFloat)newValue; +- (CGFloat)citationBorderRadius; +- (void)setCitationBorderRadius:(CGFloat)newValue; @end diff --git a/ios/styles/StyleConfig.mm b/ios/styles/StyleConfig.mm index a6c76b85..a8422df7 100644 --- a/ios/styles/StyleConfig.mm +++ b/ios/styles/StyleConfig.mm @@ -249,6 +249,9 @@ @implementation StyleConfig { RCTUIColor *_citationBackgroundColor; CGFloat _citationPaddingHorizontal; CGFloat _citationPaddingVertical; + RCTUIColor *_citationBorderColor; + CGFloat _citationBorderWidth; + CGFloat _citationBorderRadius; } - (instancetype)init @@ -544,6 +547,9 @@ - (id)copyWithZone:(NSZone *)zone copy->_citationBackgroundColor = [_citationBackgroundColor copy]; copy->_citationPaddingHorizontal = _citationPaddingHorizontal; copy->_citationPaddingVertical = _citationPaddingVertical; + copy->_citationBorderColor = [_citationBorderColor copy]; + copy->_citationBorderWidth = _citationBorderWidth; + copy->_citationBorderRadius = _citationBorderRadius; return copy; } @@ -2656,4 +2662,34 @@ - (void)setCitationPaddingVertical:(CGFloat)newValue _citationPaddingVertical = newValue; } +- (RCTUIColor *)citationBorderColor +{ + return _citationBorderColor; +} + +- (void)setCitationBorderColor:(RCTUIColor *)newValue +{ + _citationBorderColor = newValue; +} + +- (CGFloat)citationBorderWidth +{ + return _citationBorderWidth; +} + +- (void)setCitationBorderWidth:(CGFloat)newValue +{ + _citationBorderWidth = newValue; +} + +- (CGFloat)citationBorderRadius +{ + return _citationBorderRadius; +} + +- (void)setCitationBorderRadius:(CGFloat)newValue +{ + _citationBorderRadius = newValue; +} + @end diff --git a/ios/utils/CitationBackground.m b/ios/utils/CitationBackground.m index ce0c623e..5405b255 100644 --- a/ios/utils/CitationBackground.m +++ b/ios/utils/CitationBackground.m @@ -31,8 +31,12 @@ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow RCTUIColor *bgColor = [_config citationBackgroundColor]; CGFloat paddingH = [_config citationPaddingHorizontal]; CGFloat paddingV = [_config citationPaddingVertical]; + RCTUIColor *borderColor = [_config citationBorderColor]; + CGFloat borderWidth = [_config citationBorderWidth]; + CGFloat borderRadius = [_config citationBorderRadius]; - if (!bgColor) + // Nothing to paint when neither a fill nor a stroke would be visible. + if (!bgColor && (!borderColor || borderWidth <= 0)) return; [textStorage @@ -49,29 +53,63 @@ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow if (glyphRange.location == NSNotFound || glyphRange.length == 0) return; + // Pick up the font actually applied to the citation glyphs so the + // chip can be sized to the (smaller) citation font, not the full + // line height. + UIFont *citationFont = [textStorage attribute:NSFontAttributeName + atIndex:range.location + effectiveRange:NULL]; + [layoutManager enumerateLineFragmentsForGlyphRange:glyphRange - usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *tc, + usingBlock:^(CGRect lineRect, CGRect usedRect, NSTextContainer *tc, NSRange lineRange, BOOL *lineStop) { NSRange intersect = NSIntersectionRange(lineRange, glyphRange); if (intersect.length == 0) return; + // Horizontal extent: tight to the glyph run. CGRect glyphRect = [layoutManager boundingRectForGlyphRange:intersect inTextContainer:textContainer]; - CGRect chipRect = CGRectMake(glyphRect.origin.x + origin.x - paddingH, - glyphRect.origin.y + origin.y - paddingV, - glyphRect.size.width + paddingH * 2, - glyphRect.size.height + paddingV * 2); + // Vertical extent: derive from the citation glyphs' own + // baseline + font metrics so the chip hugs the smaller + // superscript text rather than stretching to the line + // height. `locationForGlyphAtIndex:` returns the baseline + // in the line fragment's coordinate system and already + // accounts for NSBaselineOffsetAttributeName. + CGPoint glyphLocation = + [layoutManager locationForGlyphAtIndex:intersect.location]; + CGFloat baselineY = lineRect.origin.y + glyphLocation.y; + + CGFloat ascent = citationFont ? citationFont.ascender : 0; + // UIFont.descender is negative (points below baseline); + // subtract it to move downward from the baseline. + CGFloat descent = citationFont ? citationFont.descender : 0; + + CGFloat chipTop = baselineY - ascent - paddingV; + CGFloat chipBottom = baselineY - descent + paddingV; - CGFloat radius = MIN(chipRect.size.width, chipRect.size.height) / 2.0; + CGRect chipRect = CGRectMake( + glyphRect.origin.x + origin.x - paddingH, chipTop + origin.y, + glyphRect.size.width + paddingH * 2, MAX(0, chipBottom - chipTop)); + + CGFloat maxRadius = MIN(chipRect.size.width, chipRect.size.height) / 2.0; + CGFloat radius = MIN(borderRadius, maxRadius); UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:chipRect cornerRadius:radius]; - [bgColor setFill]; - [path fill]; + if (bgColor) { + [bgColor setFill]; + [path fill]; + } + + if (borderColor && borderWidth > 0) { + path.lineWidth = borderWidth; + [borderColor setStroke]; + [path stroke]; + } }]; }]; } diff --git a/ios/utils/MentionBackground.m b/ios/utils/MentionBackground.m index 572bb0c5..8f765575 100644 --- a/ios/utils/MentionBackground.m +++ b/ios/utils/MentionBackground.m @@ -53,21 +53,46 @@ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow if (glyphRange.location == NSNotFound || glyphRange.length == 0) return; + // Pick up the font actually applied to the mention glyphs so the + // pill can be sized to the mention font, not to the (possibly + // taller) line height. + UIFont *mentionFont = [textStorage attribute:NSFontAttributeName + atIndex:range.location + effectiveRange:NULL]; + [layoutManager enumerateLineFragmentsForGlyphRange:glyphRange - usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer *tc, + usingBlock:^(CGRect lineRect, CGRect usedRect, NSTextContainer *tc, NSRange lineRange, BOOL *lineStop) { NSRange intersect = NSIntersectionRange(lineRange, glyphRange); if (intersect.length == 0) return; + // Horizontal extent: tight to the mention glyph run. CGRect glyphRect = [layoutManager boundingRectForGlyphRange:intersect inTextContainer:textContainer]; - CGRect pillRect = CGRectMake(glyphRect.origin.x + origin.x - paddingH, - glyphRect.origin.y + origin.y - paddingV, - glyphRect.size.width + paddingH * 2, - glyphRect.size.height + paddingV * 2); + + // Vertical extent: derive from the mention glyphs' own + // baseline + font metrics so the pill hugs the mention + // text rather than stretching to the full line height + // (which can be taller when other inline elements on the + // line have larger metrics). + CGPoint glyphLocation = + [layoutManager locationForGlyphAtIndex:intersect.location]; + CGFloat baselineY = lineRect.origin.y + glyphLocation.y; + + CGFloat ascent = mentionFont ? mentionFont.ascender : 0; + // UIFont.descender is negative (points below baseline); + // subtract it to move downward from the baseline. + CGFloat descent = mentionFont ? mentionFont.descender : 0; + + CGFloat pillTop = baselineY - ascent - paddingV; + CGFloat pillBottom = baselineY - descent + paddingV; + + CGRect pillRect = CGRectMake( + glyphRect.origin.x + origin.x - paddingH, pillTop + origin.y, + glyphRect.size.width + paddingH * 2, MAX(0, pillBottom - pillTop)); CGFloat radius = MIN( borderRadius, MIN(pillRect.size.width, pillRect.size.height) / 2.0); diff --git a/ios/utils/StylePropsUtils.h b/ios/utils/StylePropsUtils.h index f32b9fd5..99ca21f5 100644 --- a/ios/utils/StylePropsUtils.h +++ b/ios/utils/StylePropsUtils.h @@ -1201,5 +1201,24 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty changed = YES; } + if (newStyle.citation.borderColor != oldStyle.citation.borderColor) { + if (newStyle.citation.borderColor) { + [config setCitationBorderColor:RCTUIColorFromSharedColor(newStyle.citation.borderColor)]; + } else { + [config setCitationBorderColor:nullptr]; + } + changed = YES; + } + + if (newStyle.citation.borderWidth != oldStyle.citation.borderWidth) { + [config setCitationBorderWidth:newStyle.citation.borderWidth]; + changed = YES; + } + + if (newStyle.citation.borderRadius != oldStyle.citation.borderRadius) { + [config setCitationBorderRadius:newStyle.citation.borderRadius]; + changed = YES; + } + return changed; } diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index b53e1357..915bd6fc 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -175,6 +175,9 @@ interface CitationStyleInternal { backgroundColor: ColorValue; paddingHorizontal: CodegenTypes.Float; paddingVertical: CodegenTypes.Float; + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; } export interface MarkdownStyleInternal { diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index 28821d21..9ec7abef 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -175,6 +175,9 @@ interface CitationStyleInternal { backgroundColor: ColorValue; paddingHorizontal: CodegenTypes.Float; paddingVertical: CodegenTypes.Float; + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; } export interface MarkdownStyleInternal { diff --git a/src/normalizeMarkdownStyle.ts b/src/normalizeMarkdownStyle.ts index c904cb67..2946a70a 100644 --- a/src/normalizeMarkdownStyle.ts +++ b/src/normalizeMarkdownStyle.ts @@ -226,6 +226,9 @@ const DEFAULT_NORMALIZED_STYLE = Object.freeze({ backgroundColor: 'transparent', paddingHorizontal: 0, paddingVertical: 0, + borderColor: 'transparent', + borderWidth: 0, + borderRadius: 999, }, }) as MarkdownStyleInternal; diff --git a/src/normalizeMarkdownStyle.web.ts b/src/normalizeMarkdownStyle.web.ts index bf0d5801..34c7bcdd 100644 --- a/src/normalizeMarkdownStyle.web.ts +++ b/src/normalizeMarkdownStyle.web.ts @@ -207,6 +207,9 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({ backgroundColor: 'transparent', paddingHorizontal: 0, paddingVertical: 0, + borderColor: 'transparent', + borderWidth: 0, + borderRadius: 999, }, }); diff --git a/src/types/MarkdownStyle.ts b/src/types/MarkdownStyle.ts index e2b8a194..9c282ae9 100644 --- a/src/types/MarkdownStyle.ts +++ b/src/types/MarkdownStyle.ts @@ -226,6 +226,21 @@ interface CitationStyle { * @default 0 */ paddingVertical?: number; + /** + * Border color rendered around the citation marker. Only visible when + * `borderWidth` is > 0. + */ + borderColor?: string; + /** + * Border width (in px) rendered around the citation marker. + * @default 0 + */ + borderWidth?: number; + /** + * Corner radius (in px) of the citation marker's background/border. + * Defaults to a fully-rounded pill. + */ + borderRadius?: number; } export interface MarkdownStyle { diff --git a/src/types/MarkdownStyleInternal.ts b/src/types/MarkdownStyleInternal.ts index 7befe862..101e53c3 100644 --- a/src/types/MarkdownStyleInternal.ts +++ b/src/types/MarkdownStyleInternal.ts @@ -175,6 +175,9 @@ interface CitationStyleInternal { backgroundColor: string; paddingHorizontal: number; paddingVertical: number; + borderColor: string; + borderWidth: number; + borderRadius: number; } export interface MarkdownStyleInternal { diff --git a/src/web/styles.ts b/src/web/styles.ts index b430c7af..dd333e2b 100644 --- a/src/web/styles.ts +++ b/src/web/styles.ts @@ -271,6 +271,10 @@ function citationStyle(style: MarkdownStyleInternal): CSSProperties { const citation = style.citation; const hasBackground = !!citation.backgroundColor && citation.backgroundColor !== 'transparent'; + const hasBorder = + !!citation.borderColor && + citation.borderColor !== 'transparent' && + citation.borderWidth > 0; return { color: citation.color, fontSize: `calc(1em * ${citation.fontSizeMultiplier})`, @@ -282,10 +286,11 @@ function citationStyle(style: MarkdownStyleInternal): CSSProperties { textDecoration: citation.underline ? 'underline' : undefined, paddingInline: citation.paddingHorizontal || undefined, paddingBlock: citation.paddingVertical || undefined, - // Round the chip when a background is set; a small radius keeps inline - // citation markers looking like pills rather than square boxes. + borderStyle: hasBorder ? 'solid' : undefined, + borderColor: hasBorder ? citation.borderColor : undefined, + borderWidth: hasBorder ? citation.borderWidth : undefined, borderRadius: - hasBackground && citation.paddingHorizontal > 0 ? 999 : undefined, + hasBackground || hasBorder ? citation.borderRadius : undefined, cursor: 'pointer', }; } From 93577dad0882d6fb3c561904eee70a6685b7bb7e Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 14:38:07 -0700 Subject: [PATCH 08/23] ios: citation should have margin around it --- apps/example/src/markdownStyles.ts | 2 +- apps/example/src/sampleMarkdown.ts | 2 +- apps/web-example/src/App.tsx | 2 +- ios/renderer/LinkRenderer.m | 12 ++++++ ios/utils/CitationBackground.m | 61 +++++++++++++++++++++++++----- 5 files changed, 66 insertions(+), 13 deletions(-) diff --git a/apps/example/src/markdownStyles.ts b/apps/example/src/markdownStyles.ts index 7ba72e34..5ba474c8 100644 --- a/apps/example/src/markdownStyles.ts +++ b/apps/example/src/markdownStyles.ts @@ -160,7 +160,7 @@ export const customMarkdownStyle: MarkdownStyle = { baselineOffsetPx: 7, fontWeight: '', underline: false, - paddingHorizontal: 4, + paddingHorizontal: 2, paddingVertical: 2, borderColor: '#ddd6fe', borderWidth: 1, diff --git a/apps/example/src/sampleMarkdown.ts b/apps/example/src/sampleMarkdown.ts index 1c1919dc..3b3fdc10 100644 --- a/apps/example/src/sampleMarkdown.ts +++ b/apps/example/src/sampleMarkdown.ts @@ -17,7 +17,7 @@ Forests are often called the *lungs of the Earth*. They absorb **carbon dioxide* ### Key Benefits -- **Climate regulation** through carbon sequestration [@Casper](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) +- **Climate regulation** through carbon sequestration [@Casper](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) [@Arby](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) - *Biodiversity* hotspots supporting millions of species [+resume software engineer](mention://Uploads/twilio-script.py?type=file) - Natural water filtration and ***flood prevention*** [1](citation://https://www.google.com) [2](citation://https://www.google.com?q=123) [3](citation://https://www.google.com?q=123&abc=123) [4](citation://https://www.google.com?q=123) [5](citation://https://www.google.com?q=123) [6](citation://https://www.google.com?q=123) [7](citation://https://www.google.com?q=123) [8](citation://https://www.google.com?q=123) [9](citation://https://www.google.com?q=123) [10](citation://https://www.google.com?q=123) - Source of medicine, food, and raw materials diff --git a/apps/web-example/src/App.tsx b/apps/web-example/src/App.tsx index 493615a0..418fd0fd 100644 --- a/apps/web-example/src/App.tsx +++ b/apps/web-example/src/App.tsx @@ -189,7 +189,7 @@ export default function App() { baselineOffsetPx: 7, fontWeight: '', underline: false, - paddingHorizontal: 4, + paddingHorizontal: 2, paddingVertical: 2, borderColor: '#ddd6fe', borderWidth: 1, diff --git a/ios/renderer/LinkRenderer.m b/ios/renderer/LinkRenderer.m index c2f4efb3..a10accea 100644 --- a/ios/renderer/LinkRenderer.m +++ b/ios/renderer/LinkRenderer.m @@ -189,6 +189,7 @@ - (void)renderCitationNode:(MarkdownASTNode *)node RCTUIColor *citationColor = [_config citationColor]; NSString *fontWeight = [_config citationFontWeight]; BOOL underline = [_config citationUnderline]; + CGFloat paddingHorizontal = [_config citationPaddingHorizontal]; [output enumerateAttributesInRange:range options:NSAttributedStringEnumerationLongestEffectiveRangeNotRequired @@ -237,6 +238,17 @@ - (void)renderCitationNode:(MarkdownASTNode *)node [output addAttribute:ENRMCitationURLAttributeName value:targetURL range:range]; [output addAttribute:ENRMCitationTextAttributeName value:labelText range:range]; + // The drawn chip background extends `paddingHorizontal` beyond the glyph run + // on each side. Inline text doesn't reserve any advance for that visual + // padding, so adjacent citations (and following text) would sit right up + // against our glyphs, causing the drawn chips to overlap. Applying NSKern + // on the last character adds the missing trailing advance so consecutive + // chips have the same natural spacing they'd get on web via CSS padding. + if (paddingHorizontal > 0 && range.length > 0) { + NSRange lastCharRange = NSMakeRange(NSMaxRange(range) - 1, 1); + [output addAttribute:NSKernAttributeName value:@(paddingHorizontal * 2) range:lastCharRange]; + } + [context registerCitationRange:range url:targetURL text:labelText]; } diff --git a/ios/utils/CitationBackground.m b/ios/utils/CitationBackground.m index 5405b255..c04c1574 100644 --- a/ios/utils/CitationBackground.m +++ b/ios/utils/CitationBackground.m @@ -39,6 +39,8 @@ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow if (!bgColor && (!borderColor || borderWidth <= 0)) return; + NSUInteger totalGlyphs = [layoutManager numberOfGlyphs]; + [textStorage enumerateAttribute:ENRMCitationURLAttributeName inRange:NSMakeRange(0, textStorage.length) @@ -68,10 +70,50 @@ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow if (intersect.length == 0) return; - // Horizontal extent: tight to the glyph run. - CGRect glyphRect = - [layoutManager boundingRectForGlyphRange:intersect - inTextContainer:textContainer]; + // Horizontal extent: compute from glyph ADVANCE positions + // (not ink bounds) so the chip hugs each digit the same + // way proportional text naturally lays out. Using + // boundingRectForGlyphRange: here would include any + // trailing kerning we added to space consecutive chips + // apart, causing them to visually overlap. + NSUInteger firstGlyph = intersect.location; + NSUInteger lastGlyph = NSMaxRange(intersect) - 1; + + CGPoint firstLoc = [layoutManager locationForGlyphAtIndex:firstGlyph]; + CGFloat chipLeftX = lineRect.origin.x + firstLoc.x; + + CGFloat chipRightX; + NSUInteger afterLastGlyph = NSMaxRange(intersect); + BOOL canQueryNext = (afterLastGlyph < totalGlyphs) && + (afterLastGlyph < NSMaxRange(lineRange)); + if (canQueryNext) { + CGPoint nextLoc = + [layoutManager locationForGlyphAtIndex:afterLastGlyph]; + chipRightX = lineRect.origin.x + nextLoc.x; + + // Subtract any trailing kern we stamped on the last + // character of the citation so the chip doesn't include + // that spacing gap. + NSUInteger lastCharIndex = + [layoutManager characterIndexForGlyphAtIndex:lastGlyph]; + if (lastCharIndex < textStorage.length) { + NSNumber *kern = [textStorage attribute:NSKernAttributeName + atIndex:lastCharIndex + effectiveRange:NULL]; + if (kern) { + chipRightX -= [kern doubleValue]; + } + } + } else { + // Last glyph on the line or last in the buffer — fall + // back to the last glyph's ink bounding rect (trailing + // kerning is irrelevant here since there's nothing + // after it). + CGRect lastRect = + [layoutManager boundingRectForGlyphRange:NSMakeRange(lastGlyph, 1) + inTextContainer:textContainer]; + chipRightX = lastRect.origin.x + lastRect.size.width; + } // Vertical extent: derive from the citation glyphs' own // baseline + font metrics so the chip hugs the smaller @@ -79,9 +121,7 @@ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow // height. `locationForGlyphAtIndex:` returns the baseline // in the line fragment's coordinate system and already // accounts for NSBaselineOffsetAttributeName. - CGPoint glyphLocation = - [layoutManager locationForGlyphAtIndex:intersect.location]; - CGFloat baselineY = lineRect.origin.y + glyphLocation.y; + CGFloat baselineY = lineRect.origin.y + firstLoc.y; CGFloat ascent = citationFont ? citationFont.ascender : 0; // UIFont.descender is negative (points below baseline); @@ -91,9 +131,10 @@ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow CGFloat chipTop = baselineY - ascent - paddingV; CGFloat chipBottom = baselineY - descent + paddingV; - CGRect chipRect = CGRectMake( - glyphRect.origin.x + origin.x - paddingH, chipTop + origin.y, - glyphRect.size.width + paddingH * 2, MAX(0, chipBottom - chipTop)); + CGRect chipRect = + CGRectMake(chipLeftX + origin.x - paddingH, chipTop + origin.y, + MAX(0, (chipRightX - chipLeftX)) + paddingH * 2, + MAX(0, chipBottom - chipTop)); CGFloat maxRadius = MIN(chipRect.size.width, chipRect.size.height) / 2.0; CGFloat radius = MIN(borderRadius, maxRadius); From 7bd117ca05aaa08036cd79852d7f9860f881cadc Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 14:51:53 -0700 Subject: [PATCH 09/23] mention should have margin around too --- .../markdown/renderer/LinkRenderer.kt | 20 ++++++ .../markdown/spans/MentionSpacerSpan.kt | 55 +++++++++++++++ ios/renderer/LinkRenderer.m | 12 ++++ ios/utils/MentionBackground.m | 67 ++++++++++++++----- 4 files changed, 139 insertions(+), 15 deletions(-) create mode 100644 android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpacerSpan.kt diff --git a/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt b/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt index b7163ab9..81c2179e 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/renderer/LinkRenderer.kt @@ -4,6 +4,7 @@ import android.text.SpannableStringBuilder import com.swmansion.enriched.markdown.parser.MarkdownASTNode import com.swmansion.enriched.markdown.spans.CitationSpan import com.swmansion.enriched.markdown.spans.LinkSpan +import com.swmansion.enriched.markdown.spans.MentionSpacerSpan import com.swmansion.enriched.markdown.spans.MentionSpan import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE @@ -101,6 +102,25 @@ class LinkRenderer( mentionTypeface = factory.styleCache.mentionTypeface, ) builder.setSpan(span, start, end, SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE) + + // The pill background extends `paddingHorizontal` past the glyph run on + // each side, but the underlying inline text doesn't reserve any advance + // for that visual overhang. Without extra spacing, two adjacent mention + // pills (separated only by a space in the source markdown) visually + // overlap. Appending a zero-width sentinel char with a MentionSpacerSpan + // reserves `paddingHorizontal * 2` of advance after each mention — the + // Android-side equivalent of the NSKern we apply on iOS. + val mentionStyle = factory.styleCache.mentionStyle + if (mentionStyle.paddingHorizontal > 0f) { + val spacerStart = builder.length + builder.append("\u200B") // zero-width space + builder.setSpan( + MentionSpacerSpan(mentionStyle.paddingHorizontal * 2f), + spacerStart, + builder.length, + SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE, + ) + } } private fun renderCitation( diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpacerSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpacerSpan.kt new file mode 100644 index 00000000..83b6c1b3 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpacerSpan.kt @@ -0,0 +1,55 @@ +package com.swmansion.enriched.markdown.spans + +import android.graphics.Canvas +import android.graphics.Paint +import android.text.style.ReplacementSpan + +/** + * Reserves a fixed horizontal advance on a single sentinel character + * (typically ZWSP) appended after an inline mention. This mirrors the trailing + * NSKern iOS uses to space consecutive mention pills apart — the mention's + * own `LineBackgroundSpan` draws its pill extending `paddingHorizontal` past + * the glyph run on both sides, and without reserved advance here the pills of + * two adjacent mentions would visually overlap. + * + * The span draws nothing, so the sentinel character is invisible; it only + * affects layout. + */ +class MentionSpacerSpan( + private val widthPx: Float, +) : ReplacementSpan() { + override fun getSize( + paint: Paint, + text: CharSequence?, + start: Int, + end: Int, + fm: Paint.FontMetricsInt?, + ): Int { + if (fm != null) { + // Match the surrounding line metrics so the sentinel doesn't affect + // line height. + val metrics = paint.fontMetricsInt + fm.ascent = metrics.ascent + fm.top = metrics.top + fm.descent = metrics.descent + fm.bottom = metrics.bottom + fm.leading = metrics.leading + } + return widthPx.toInt().coerceAtLeast(0) + } + + override fun draw( + canvas: Canvas, + text: CharSequence, + start: Int, + end: Int, + x: Float, + top: Int, + y: Int, + bottom: Int, + paint: Paint, + ) { + // Intentionally no-op — the sentinel is invisible and only reserves + // advance width so adjacent mention pills don't visually overlap. + } +} diff --git a/ios/renderer/LinkRenderer.m b/ios/renderer/LinkRenderer.m index a10accea..45a93f42 100644 --- a/ios/renderer/LinkRenderer.m +++ b/ios/renderer/LinkRenderer.m @@ -165,6 +165,18 @@ - (void)renderMentionNode:(MarkdownASTNode *)node [output appendAttributedString:[[NSAttributedString alloc] initWithString:displayText attributes:attrs]]; NSRange outputRange = NSMakeRange(start, output.length - start); + // The drawn pill extends `paddingHorizontal` beyond the glyph run on each + // side. Inline text doesn't reserve any advance for that visual padding, so + // two adjacent mentions (separated only by a space) would have their pills + // visually overlap. Stamping NSKern on the last glyph pushes the following + // character away by the same amount the pill extends, matching what CSS + // `paddingInline` does on web. + CGFloat mentionPaddingH = [_config mentionPaddingHorizontal]; + if (mentionPaddingH > 0 && outputRange.length > 0) { + NSRange lastCharRange = NSMakeRange(NSMaxRange(outputRange) - 1, 1); + [output addAttribute:NSKernAttributeName value:@(mentionPaddingH * 2) range:lastCharRange]; + } + [context registerMentionRange:outputRange url:mentionURL text:displayText]; } diff --git a/ios/utils/MentionBackground.m b/ios/utils/MentionBackground.m index 8f765575..a4bb7074 100644 --- a/ios/utils/MentionBackground.m +++ b/ios/utils/MentionBackground.m @@ -39,6 +39,8 @@ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow if (!bgColor && (!borderColor || borderWidth <= 0)) return; + NSUInteger totalGlyphs = [layoutManager numberOfGlyphs]; + [textStorage enumerateAttribute:ENRMMentionURLAttributeName inRange:NSMakeRange(0, textStorage.length) @@ -68,31 +70,66 @@ - (void)drawBackgroundsForGlyphRange:(NSRange)glyphsToShow if (intersect.length == 0) return; - // Horizontal extent: tight to the mention glyph run. - CGRect glyphRect = - [layoutManager boundingRectForGlyphRange:intersect - inTextContainer:textContainer]; + // Horizontal extent: compute from glyph ADVANCE positions + // (not ink bounds) so the pill hugs the glyph run exactly, + // and subtract any trailing kerning we stamped on the + // last character. Using boundingRectForGlyphRange: here + // would include that kerning and let adjacent mention + // pills visually overlap. + NSUInteger firstGlyph = intersect.location; + NSUInteger lastGlyph = NSMaxRange(intersect) - 1; + + CGPoint firstLoc = [layoutManager locationForGlyphAtIndex:firstGlyph]; + CGFloat pillLeftX = lineRect.origin.x + firstLoc.x; + + CGFloat pillRightX; + NSUInteger afterLastGlyph = NSMaxRange(intersect); + BOOL canQueryNext = (afterLastGlyph < totalGlyphs) && + (afterLastGlyph < NSMaxRange(lineRange)); + if (canQueryNext) { + CGPoint nextLoc = + [layoutManager locationForGlyphAtIndex:afterLastGlyph]; + pillRightX = lineRect.origin.x + nextLoc.x; + + // Subtract the trailing NSKern (if any) so the pill + // ends exactly at the last glyph's natural advance, + // not inside the kerning gap used to space chips. + NSUInteger lastCharIndex = + [layoutManager characterIndexForGlyphAtIndex:lastGlyph]; + if (lastCharIndex < textStorage.length) { + NSNumber *kern = [textStorage attribute:NSKernAttributeName + atIndex:lastCharIndex + effectiveRange:NULL]; + if (kern) { + pillRightX -= [kern doubleValue]; + } + } + } else { + // End of line or end of buffer: fall back to the last + // glyph's ink bounding rect (trailing kerning is + // irrelevant when nothing follows it on the line). + CGRect lastRect = + [layoutManager boundingRectForGlyphRange:NSMakeRange(lastGlyph, 1) + inTextContainer:textContainer]; + pillRightX = lastRect.origin.x + lastRect.size.width; + } // Vertical extent: derive from the mention glyphs' own // baseline + font metrics so the pill hugs the mention - // text rather than stretching to the full line height - // (which can be taller when other inline elements on the - // line have larger metrics). - CGPoint glyphLocation = - [layoutManager locationForGlyphAtIndex:intersect.location]; - CGFloat baselineY = lineRect.origin.y + glyphLocation.y; + // text rather than stretching to the full line height. + CGFloat baselineY = lineRect.origin.y + firstLoc.y; CGFloat ascent = mentionFont ? mentionFont.ascender : 0; - // UIFont.descender is negative (points below baseline); - // subtract it to move downward from the baseline. + // UIFont.descender is negative; subtract to move down. CGFloat descent = mentionFont ? mentionFont.descender : 0; CGFloat pillTop = baselineY - ascent - paddingV; CGFloat pillBottom = baselineY - descent + paddingV; - CGRect pillRect = CGRectMake( - glyphRect.origin.x + origin.x - paddingH, pillTop + origin.y, - glyphRect.size.width + paddingH * 2, MAX(0, pillBottom - pillTop)); + CGRect pillRect = + CGRectMake(pillLeftX + origin.x - paddingH, pillTop + origin.y, + MAX(0, (pillRightX - pillLeftX)) + paddingH * 2, + MAX(0, pillBottom - pillTop)); CGFloat radius = MIN( borderRadius, MIN(pillRect.size.width, pillRect.size.height) / 2.0); From 65a6494e7ab46a1d5435d60d7de2112677e84d3b Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 15:14:08 -0700 Subject: [PATCH 10/23] android: show mention background color inside blockquote --- .../markdown/renderer/BlockquoteRenderer.kt | 9 +++-- .../markdown/renderer/SpanStyleCache.kt | 13 +++++++ .../enriched/markdown/spans/BlockquoteSpan.kt | 35 ++++++++++++++++--- .../markdown/utils/text/span/SpanFlags.kt | 12 +++++++ apps/example/src/markdownStyles.ts | 2 +- apps/example/src/sampleMarkdown.ts | 2 +- 6 files changed, 64 insertions(+), 9 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt b/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt index 5389284f..a2429769 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/renderer/BlockquoteRenderer.kt @@ -3,6 +3,7 @@ package com.swmansion.enriched.markdown.renderer import android.text.SpannableStringBuilder import com.swmansion.enriched.markdown.parser.MarkdownASTNode import com.swmansion.enriched.markdown.spans.BlockquoteSpan +import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_CONTAINER_BACKGROUND import com.swmansion.enriched.markdown.utils.text.span.SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE import com.swmansion.enriched.markdown.utils.text.span.applyMarginBottom import com.swmansion.enriched.markdown.utils.text.span.applyMarginTop @@ -45,12 +46,16 @@ class BlockquoteRenderer( .map { builder.getSpanStart(it) to builder.getSpanEnd(it) } .sortedBy { it.first } - // The Accent Bar Span covers the full range for visual continuity + // The Accent Bar Span covers the full range for visual continuity. + // Use high-priority flags so BlockquoteSpan's LineBackgroundSpan pass + // runs FIRST on each line — the blockquote fill is painted first, then + // inline chip/pill backgrounds (mention pills etc.) draw on top of it + // instead of being covered by it. builder.setSpan( BlockquoteSpan(style, depth, factory.context, factory.styleCache), start, end, - SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE, + SPAN_FLAGS_CONTAINER_BACKGROUND, ) // Apply styling only to segments that are NOT nested quotes diff --git a/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt b/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt index 53095922..7cc6e2aa 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/renderer/SpanStyleCache.kt @@ -53,6 +53,19 @@ class SpanStyleCache( style.taskListStyle.checkedTextColor .takeIf { it != 0 } ?.let { add(it) } + // Inline chip colors (mention / citation). Container spans like + // BaseListSpan and BlockquoteSpan overwrite text color via + // `applyColorPreserving(blockColor, colorsToPreserve)`. Including the + // chip colors here ensures the mention/citation foreground set by + // MentionSpan / CitationSpan survives that overwrite — otherwise the + // chip text falls back to the surrounding block color inside lists or + // blockquotes. + style.mentionStyle.color + .takeIf { it != 0 && it != paragraphColor } + ?.let { add(it) } + style.citationStyle.color + .takeIf { it != 0 && it != paragraphColor } + ?.let { add(it) } }.toIntArray() } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt index f8230f6e..a0889524 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/BlockquoteSpan.kt @@ -10,6 +10,7 @@ import android.text.Layout import android.text.Spanned import android.text.TextPaint import android.text.style.LeadingMarginSpan +import android.text.style.LineBackgroundSpan import android.text.style.MetricAffectingSpan import com.swmansion.enriched.markdown.renderer.BlockStyle import com.swmansion.enriched.markdown.renderer.SpanStyleCache @@ -23,7 +24,8 @@ class BlockquoteSpan( private val context: Context, private val styleCache: SpanStyleCache, ) : MetricAffectingSpan(), - LeadingMarginSpan { + LeadingMarginSpan, + LineBackgroundSpan { private val levelSpacing: Float = blockquoteStyle.borderWidth + blockquoteStyle.gapWidth private val blockStyle = BlockStyle( @@ -60,8 +62,6 @@ class BlockquoteSpan( // Essential check from original: only the deepest span draws to prevent over-rendering background if (shouldSkipDrawing(text, start)) return - drawBackground(c, top, bottom, layout) - val borderPaint = configureBorderPaint() val borderTop = top.toFloat() val borderBottom = bottom.toFloat() @@ -73,6 +73,31 @@ class BlockquoteSpan( } } + /** + * Drawn BEFORE glyphs and before other [LineBackgroundSpan]s attached to the + * same line, so inline backgrounds painted by mention / code spans render on + * top of the blockquote fill instead of being covered by it (the previous + * implementation painted the blockquote fill from [drawLeadingMargin], which + * runs AFTER [LineBackgroundSpan.drawBackground] and erased the mention + * pill). + */ + override fun drawBackground( + canvas: Canvas, + paint: Paint, + left: Int, + right: Int, + top: Int, + baseline: Int, + bottom: Int, + text: CharSequence, + start: Int, + end: Int, + lineNum: Int, + ) { + if (shouldSkipDrawing(text, start)) return + drawBackground(canvas, top, bottom, right) + } + @SuppressLint("WrongConstant") // Result of mask is always valid: 0, 1, 2, or 3 private fun applyTextStyle(tp: TextPaint) { tp.textSize = blockStyle.fontSize @@ -125,10 +150,10 @@ class BlockquoteSpan( c: Canvas, top: Int, bottom: Int, - layout: Layout?, + right: Int, ) { val bgColor = blockquoteStyle.backgroundColor?.takeIf { it != Color.TRANSPARENT } ?: return val backgroundPaint = configureBackgroundPaint(bgColor) - c.drawRect(0f, top.toFloat(), layout?.width?.toFloat() ?: 0f, bottom.toFloat(), backgroundPaint) + c.drawRect(0f, top.toFloat(), right.toFloat(), bottom.toFloat(), backgroundPaint) } } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/span/SpanFlags.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/span/SpanFlags.kt index b8e9a348..1bd80786 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/span/SpanFlags.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/span/SpanFlags.kt @@ -1,5 +1,17 @@ package com.swmansion.enriched.markdown.utils.text.span import android.text.SpannableString +import android.text.Spanned const val SPAN_FLAGS_EXCLUSIVE_EXCLUSIVE = SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE + +/** + * EXCLUSIVE_EXCLUSIVE flags with the maximum span priority bit set. Higher + * priority means the span is iterated FIRST during the text view's draw + * passes (e.g. `Layout.drawBackground`), which means it's painted FIRST — so + * lower-priority spans drawn afterwards end up on top visually. Use this for + * full-width container backgrounds (like blockquote) that must sit UNDER any + * inline chip / pill backgrounds on the same line. + */ +const val SPAN_FLAGS_CONTAINER_BACKGROUND = + SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE or ((0xFF) shl Spanned.SPAN_PRIORITY_SHIFT) diff --git a/apps/example/src/markdownStyles.ts b/apps/example/src/markdownStyles.ts index 5ba474c8..16046118 100644 --- a/apps/example/src/markdownStyles.ts +++ b/apps/example/src/markdownStyles.ts @@ -151,7 +151,7 @@ export const customMarkdownStyle: MarkdownStyle = { paddingVertical: 0, fontFamily: 'Montserrat-Regular', fontSize: 14, - color: '#2563fb', + color: '#2561fB', }, citation: { backgroundColor: '#EBEBFF', diff --git a/apps/example/src/sampleMarkdown.ts b/apps/example/src/sampleMarkdown.ts index 3b3fdc10..e3dfa2cf 100644 --- a/apps/example/src/sampleMarkdown.ts +++ b/apps/example/src/sampleMarkdown.ts @@ -11,7 +11,7 @@ Forests cover approximately **31% of the Earth's land surface**, providing habit Forests are often called the *lungs of the Earth*. They absorb **carbon dioxide** and release oxygen through photosynthesis — a process essential for all life on our planet. A single mature tree can absorb up to \`48 pounds\` of CO₂ per year. -> [@John Muir](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) In every walk with nature, one receives far more than he seeks. +> In every walk with nature, [@John Muir](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) one receives far more than he seeks. [4](citation://https://www.google.com) > > — John Muir From c258b4d5b03518a661af135c9a25b03c0bff6958 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 15:24:22 -0700 Subject: [PATCH 11/23] android: mention background size to the mention text itself --- .../swmansion/enriched/markdown/spans/MentionSpan.kt | 11 +++++++++-- apps/example/src/sampleMarkdown.ts | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt b/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt index c841bcb2..a0efb4fb 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/spans/MentionSpan.kt @@ -105,8 +105,15 @@ class MentionSpan( val pillLeft = left + min(startOffset, endOffset) - paddingH val pillRight = left + max(startOffset, endOffset) + paddingH - val pillTop = top - paddingV - val pillBottom = bottom + paddingV + // Derive vertical extent from the mention's own font metrics (not the + // line's `top`/`bottom`) so the pill hugs the mention text. Using the + // line bounds would stretch the pill to the paragraph's lineHeight, + // which is visibly taller than the glyph when lineHeight > natural + // font height (or when anything else on the line has bigger metrics). + val fm = localPaint.fontMetrics + // ascent is negative (above baseline), descent is positive (below). + val pillTop = baseline + fm.ascent - paddingV + val pillBottom = baseline + fm.descent + paddingV if (pillRight <= pillLeft || pillBottom <= pillTop) return rect.set(pillLeft, pillTop, pillRight, pillBottom) diff --git a/apps/example/src/sampleMarkdown.ts b/apps/example/src/sampleMarkdown.ts index e3dfa2cf..355e48a3 100644 --- a/apps/example/src/sampleMarkdown.ts +++ b/apps/example/src/sampleMarkdown.ts @@ -17,8 +17,8 @@ Forests are often called the *lungs of the Earth*. They absorb **carbon dioxide* ### Key Benefits -- **Climate regulation** through carbon sequestration [@Casper](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) [@Arby](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) -- *Biodiversity* hotspots supporting millions of species [+resume software engineer](mention://Uploads/twilio-script.py?type=file) +- **Climate regulation** through carbon sequestration [@Casper](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) [@Arby](mention://d81546aa-5f91-408a-b6dd-628e324750bf?type=user) +- *Biodiversity* hotspots supporting millions of species [+resume software engineer](mention://Uploads/twilio-script.py?type=file) - Natural water filtration and ***flood prevention*** [1](citation://https://www.google.com) [2](citation://https://www.google.com?q=123) [3](citation://https://www.google.com?q=123&abc=123) [4](citation://https://www.google.com?q=123) [5](citation://https://www.google.com?q=123) [6](citation://https://www.google.com?q=123) [7](citation://https://www.google.com?q=123) [8](citation://https://www.google.com?q=123) [9](citation://https://www.google.com?q=123) [10](citation://https://www.google.com?q=123) - Source of medicine, food, and raw materials - Soil erosion prevention and **nutrient cycling** From 4c3433d912fdf8af820ce054fed0d7e23c195edf Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 15:31:42 -0700 Subject: [PATCH 12/23] android: include citation links when copy as markdown --- .../text/conversion/MarkdownExtractor.kt | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt index 8a79d7f6..9ae5d368 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/conversion/MarkdownExtractor.kt @@ -5,12 +5,14 @@ import android.text.style.UnderlineSpan import android.widget.TextView import com.swmansion.enriched.markdown.EnrichedMarkdownText import com.swmansion.enriched.markdown.spans.BlockquoteSpan +import com.swmansion.enriched.markdown.spans.CitationSpan import com.swmansion.enriched.markdown.spans.CodeBlockSpan import com.swmansion.enriched.markdown.spans.CodeSpan import com.swmansion.enriched.markdown.spans.EmphasisSpan import com.swmansion.enriched.markdown.spans.HeadingSpan import com.swmansion.enriched.markdown.spans.ImageSpan import com.swmansion.enriched.markdown.spans.LinkSpan +import com.swmansion.enriched.markdown.spans.MentionSpan import com.swmansion.enriched.markdown.spans.OrderedListSpan import com.swmansion.enriched.markdown.spans.StrikethroughSpan import com.swmansion.enriched.markdown.spans.StrongSpan @@ -301,17 +303,21 @@ object MarkdownExtractor { val hasStrikethrough = spannable.getSpans(start, end, StrikethroughSpan::class.java).isNotEmpty() val hasUnderline = spannable.getSpans(start, end, UnderlineSpan::class.java).isNotEmpty() val linkSpans = spannable.getSpans(start, end, LinkSpan::class.java) + val mentionSpans = spannable.getSpans(start, end, MentionSpan::class.java) + val citationSpans = spannable.getSpans(start, end, CitationSpan::class.java) + + val hasAnyLinkShape = linkSpans.isNotEmpty() || mentionSpans.isNotEmpty() || citationSpans.isNotEmpty() var result = text // Innermost first - if (hasCode && linkSpans.isEmpty()) { + if (hasCode && !hasAnyLinkShape) { result = "`$result`" } if (hasStrikethrough) { result = "~~$result~~" } - if (hasUnderline && linkSpans.isEmpty()) { + if (hasUnderline && !hasAnyLinkShape) { result = "$result" } if (hasEmphasis) { @@ -320,9 +326,16 @@ object MarkdownExtractor { if (hasStrong) { result = "**$result**" } - if (linkSpans.isNotEmpty()) { - result = "[$text](${linkSpans[0].url})" - } + // Mentions and citations share the `[text](scheme://url)` shape as regular + // links — just with distinct URL schemes — so a selected range that + // covers one emits the full markdown link, preserving the target URL. + result = + when { + mentionSpans.isNotEmpty() -> "[$text](mention://${mentionSpans[0].url})" + citationSpans.isNotEmpty() -> "[$text](citation://${citationSpans[0].url})" + linkSpans.isNotEmpty() -> "[$text](${linkSpans[0].url})" + else -> result + } return result } From b233abbbe1478fe2fa843656b610dc7464c58535 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 20:56:44 -0700 Subject: [PATCH 13/23] ios: citation padding should not cover adjacent content --- apps/example/src/markdownStyles.ts | 8 +-- apps/example/src/sampleMarkdown.ts | 8 +++ apps/macos-example/src/sampleMarkdown.ts | 6 +-- ios/renderer/LinkRenderer.m | 66 +++++++++++++++++------- 4 files changed, 63 insertions(+), 25 deletions(-) diff --git a/apps/example/src/markdownStyles.ts b/apps/example/src/markdownStyles.ts index 16046118..057f6d29 100644 --- a/apps/example/src/markdownStyles.ts +++ b/apps/example/src/markdownStyles.ts @@ -156,12 +156,12 @@ export const customMarkdownStyle: MarkdownStyle = { citation: { backgroundColor: '#EBEBFF', color: '#9B9BFD', - fontSizeMultiplier: 0.5, - baselineOffsetPx: 7, + fontSizeMultiplier: 0.6, + baselineOffsetPx: 8, fontWeight: '', underline: false, - paddingHorizontal: 2, - paddingVertical: 2, + paddingHorizontal: 4, + paddingVertical: 4, borderColor: '#ddd6fe', borderWidth: 1, borderRadius: 99, diff --git a/apps/example/src/sampleMarkdown.ts b/apps/example/src/sampleMarkdown.ts index 355e48a3..b2916445 100644 --- a/apps/example/src/sampleMarkdown.ts +++ b/apps/example/src/sampleMarkdown.ts @@ -309,6 +309,14 @@ Conservation efforts are making a difference: | ***The Nature Conservancy*** | Land protection | Protected 125M+ acres | | One Tree Planted | Reforestation | Planted 100M+ trees | + +| Organization | Focus Area | +|--------------|------------| +| **WFF** | Global conservation | +| *RA* | Sustainable agriculture | +| ***TNC*** | Land protection | +| ***OTP*** | Reforestation | + --- ## The Future of Forests diff --git a/apps/macos-example/src/sampleMarkdown.ts b/apps/macos-example/src/sampleMarkdown.ts index 1fd851b9..f4deab3d 100644 --- a/apps/macos-example/src/sampleMarkdown.ts +++ b/apps/macos-example/src/sampleMarkdown.ts @@ -122,7 +122,7 @@ class TreeNetwork { this.trees = []; this.fungalConnections = new Map(); } - + connectTrees(tree1, tree2) { // Trees share nutrients through mycorrhizal networks this.fungalConnections.set(\`\${tree1.id}-\${tree2.id}\`, { @@ -316,11 +316,11 @@ def detect_deforestation(region): """Monitor forest cover changes using satellite imagery""" current_cover = satellite_imagery.get_forest_cover(region) previous_cover = satellite_imagery.get_historical_cover(region, years_ago=1) - + deforestation_rate = (previous_cover - current_cover) / previous_cover if deforestation_rate > 0.05: # 5% threshold alert_conservation_team(region, deforestation_rate) - + return deforestation_rate \`\`\` diff --git a/ios/renderer/LinkRenderer.m b/ios/renderer/LinkRenderer.m index 45a93f42..f7353a21 100644 --- a/ios/renderer/LinkRenderer.m +++ b/ios/renderer/LinkRenderer.m @@ -48,6 +48,39 @@ static BOOL isCitationURL(NSString *url) return url; } +// Stamps NSKern on the character before and the last character of an inline +// chip range so its drawn background never overlaps surrounding text. +// +// Kern value is `paddingHorizontal`: exactly enough to shift the chip's left +// overhang off the previous glyph (leading kern) and the following glyph off +// the chip's right overhang (trailing kern). With this amount, a chip just +// touches its neighbors rather than gapping — adjacent chips separated by a +// source-markdown space show the natural `space_width` between them, which +// matches the gap CSS `paddingInline` produces on web. Using `paddingHorizontal +// * 2` would double up across a shared boundary character and push consecutive +// chips visibly far apart. +// +// Adjacent chips writing the same value on the shared boundary character is +// idempotent. +static void applyChipKern(NSMutableAttributedString *output, NSRange chipRange, CGFloat paddingHorizontal) +{ + if (chipRange.length == 0 || paddingHorizontal <= 0) + return; + + NSNumber *kernValue = @(paddingHorizontal); + + // Trailing kern on the last glyph of the chip. + NSRange trailing = NSMakeRange(NSMaxRange(chipRange) - 1, 1); + [output addAttribute:NSKernAttributeName value:kernValue range:trailing]; + + // Leading kern on the character immediately before the chip, if any. This + // pushes the chip right so its left overhang doesn't cover preceding text. + if (chipRange.location > 0) { + NSRange leading = NSMakeRange(chipRange.location - 1, 1); + [output addAttribute:NSKernAttributeName value:kernValue range:leading]; + } +} + #pragma mark - Rendering - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)output context:(RenderContext *)context @@ -165,16 +198,19 @@ - (void)renderMentionNode:(MarkdownASTNode *)node [output appendAttributedString:[[NSAttributedString alloc] initWithString:displayText attributes:attrs]]; NSRange outputRange = NSMakeRange(start, output.length - start); - // The drawn pill extends `paddingHorizontal` beyond the glyph run on each - // side. Inline text doesn't reserve any advance for that visual padding, so - // two adjacent mentions (separated only by a space) would have their pills - // visually overlap. Stamping NSKern on the last glyph pushes the following - // character away by the same amount the pill extends, matching what CSS - // `paddingInline` does on web. + // The drawn pill extends `paddingHorizontal` beyond the glyph run on both + // sides. Inline text doesn't reserve any advance for that visual padding, so + // without extra spacing the pill would cover the character immediately + // before the mention (and likewise any adjacent mention/citation following + // it). Stamping NSKern on: + // - the character BEFORE the mention (adds leading advance), and + // - the LAST character of the mention (adds trailing advance) + // pushes the surrounding text outside the pill edges, matching what CSS + // `paddingInline` produces on web. Adjacent chips' kerns land on the same + // boundary character and are idempotent. CGFloat mentionPaddingH = [_config mentionPaddingHorizontal]; if (mentionPaddingH > 0 && outputRange.length > 0) { - NSRange lastCharRange = NSMakeRange(NSMaxRange(outputRange) - 1, 1); - [output addAttribute:NSKernAttributeName value:@(mentionPaddingH * 2) range:lastCharRange]; + applyChipKern(output, outputRange, mentionPaddingH); } [context registerMentionRange:outputRange url:mentionURL text:displayText]; @@ -250,16 +286,10 @@ - (void)renderCitationNode:(MarkdownASTNode *)node [output addAttribute:ENRMCitationURLAttributeName value:targetURL range:range]; [output addAttribute:ENRMCitationTextAttributeName value:labelText range:range]; - // The drawn chip background extends `paddingHorizontal` beyond the glyph run - // on each side. Inline text doesn't reserve any advance for that visual - // padding, so adjacent citations (and following text) would sit right up - // against our glyphs, causing the drawn chips to overlap. Applying NSKern - // on the last character adds the missing trailing advance so consecutive - // chips have the same natural spacing they'd get on web via CSS padding. - if (paddingHorizontal > 0 && range.length > 0) { - NSRange lastCharRange = NSMakeRange(NSMaxRange(range) - 1, 1); - [output addAttribute:NSKernAttributeName value:@(paddingHorizontal * 2) range:lastCharRange]; - } + // Stamp NSKern on the characters flanking the chip so the drawn background + // has symmetric clearance from surrounding text (previous glyph on the + // left, following glyph on the right). + applyChipKern(output, range, paddingHorizontal); [context registerCitationRange:range url:targetURL text:labelText]; } From 5b4b76fe83c39433ad1959e85686244b8f460c9a Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 20:41:17 -0700 Subject: [PATCH 14/23] make table take full width of parent --- .../markdown/views/TableContainerView.kt | 120 +++++++++++++++--- ios/views/TableContainerView.m | 77 ++++++++++- src/web/styles.ts | 2 + 3 files changed, 178 insertions(+), 21 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt b/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt index 6606f5fe..07cc8d82 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/views/TableContainerView.kt @@ -61,12 +61,19 @@ class TableContainerView( private val gridContainer get() = scrollView.getChildAt(0) as GridContainerView private var rows: List> = emptyList() + private var cellTextsForMeasure: List> = emptyList() private var columnCount = 0 + + /** Natural widths from text measurement, before stretching to the parent width. */ + private var naturalColumnWidths = emptyList() + private var naturalTotalTableWidth = 0f + private var naturalTotalTableHeight = 0f private var columnWidths = emptyList() private var rowHeights = emptyList() private var totalTableWidth = 0f private var totalTableHeight = 0f private var tableMarkdown = "" + private var lastStretchLayoutWidth = -1 init { addView(scrollView, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)) @@ -94,11 +101,16 @@ class TableContainerView( columnCount = rows.maxOfOrNull { it.size } ?: 0 tableMarkdown = buildMarkdownFromRows() - val (widths, heights) = computeTableDimensions(rows.map { row -> row.map { it.attributedText } }, styleConfig, context) - columnWidths = widths + cellTextsForMeasure = rows.map { row -> row.map { it.attributedText } } + val (widths, heights) = computeTableDimensions(cellTextsForMeasure, styleConfig, context) + naturalColumnWidths = widths + naturalTotalTableWidth = naturalColumnWidths.sum() + tableStyle.borderWidth + naturalTotalTableHeight = heights.sum() + tableStyle.borderWidth + columnWidths = naturalColumnWidths rowHeights = heights - totalTableWidth = columnWidths.sum() + tableStyle.borderWidth - totalTableHeight = rowHeights.sum() + tableStyle.borderWidth + totalTableWidth = naturalTotalTableWidth + totalTableHeight = naturalTotalTableHeight + lastStretchLayoutWidth = -1 renderGrid() } @@ -249,6 +261,10 @@ class TableContainerView( ) { scrollView.layout(0, 0, right - left, bottom - top) val viewWidth = right - left + if (naturalColumnWidths.isNotEmpty() && viewWidth != lastStretchLayoutWidth) { + lastStretchLayoutWidth = viewWidth + applyStretchForParentWidth(viewWidth) + } scrollView.isHorizontalScrollBarEnabled = totalTableWidth > viewWidth if (isRtl && totalTableWidth > viewWidth) { @@ -256,6 +272,58 @@ class TableContainerView( } } + /** + * When the table's intrinsic width is smaller than the parent, grow columns proportionally so + * the grid fills the available width (same idea as CSS `width: 100%` on a table). + */ + private fun applyStretchForParentWidth(viewWidth: Int) { + if (naturalColumnWidths.isEmpty()) return + val targetTotal = max(naturalTotalTableWidth, viewWidth.toFloat()) + val naturalContentWidth = naturalColumnWidths.sum() + if (naturalContentWidth <= 0f) return + val targetContentWidth = targetTotal - tableStyle.borderWidth + val extra = targetContentWidth - naturalContentWidth + val newWidths = + if (extra <= 0.5f) { + naturalColumnWidths + } else { + val stretched = + naturalColumnWidths + .map { w -> + w + extra * (w / naturalContentWidth) + }.toMutableList() + val drift = targetContentWidth - stretched.sum() + if (kotlin.math.abs(drift) > 0.01f && stretched.isNotEmpty()) { + val last = stretched.lastIndex + stretched[last] = stretched[last] + drift + } + stretched + } + if (newWidths == columnWidths) { + return + } + columnWidths = newWidths + totalTableWidth = columnWidths.sum() + tableStyle.borderWidth + val (horizontalPadding, verticalPadding) = + tableStyle.cellPaddingHorizontal * 2 to tableStyle.cellPaddingVertical * 2 + val measurePaint = + TextPaint(Paint.ANTI_ALIAS_FLAG).apply { + textSize = tableStyle.fontSize + typeface = styleConfig.tableTypeface + } + rowHeights = + computeRowHeightsForColumnWidths( + cellTextsForMeasure, + columnWidths, + measurePaint, + horizontalPadding, + verticalPadding, + ) + totalTableHeight = rowHeights.sum() + tableStyle.borderWidth + renderGrid() + requestLayout() + } + private fun showContextMenu(anchor: View) { val clipboard = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager ContextMenuPopup.show(anchor, this) { @@ -337,22 +405,13 @@ class TableContainerView( } val rowHeights = - texts.map { row -> - row - .mapIndexed { colIndex, cellText -> - val layout = - StaticLayout.Builder - .obtain( - cellText, - 0, - cellText.length, - paint, - (columnWidths[colIndex] - horizontalPadding).toInt().coerceAtLeast(1), - ).setIncludePad(false) - .build() - ceil(layout.height.toFloat()) + verticalPadding - }.maxOfOrNull { it } ?: 0f - } + computeRowHeightsForColumnWidths( + texts, + columnWidths.toList(), + paint, + horizontalPadding, + verticalPadding, + ) return columnWidths.toList() to rowHeights } @@ -479,3 +538,24 @@ class TableContainerView( val alignment: Layout.Alignment, ) } + +private fun computeRowHeightsForColumnWidths( + texts: List>, + columnWidths: List, + paint: TextPaint, + horizontalPadding: Float, + verticalPadding: Float, +): List = + texts.map { row -> + row + .mapIndexed { colIndex, cellText -> + val cellWidth = + (columnWidths.getOrElse(colIndex) { 0f } - horizontalPadding).toInt().coerceAtLeast(1) + val layout = + StaticLayout.Builder + .obtain(cellText, 0, cellText.length, paint, cellWidth) + .setIncludePad(false) + .build() + ceil(layout.height.toFloat()) + verticalPadding + }.maxOfOrNull { it } ?: 0f + } diff --git a/ios/views/TableContainerView.m b/ios/views/TableContainerView.m index 47091ced..b6db2a4b 100644 --- a/ios/views/TableContainerView.m +++ b/ios/views/TableContainerView.m @@ -46,6 +46,10 @@ @implementation TableContainerView { CGFloat _borderWidth; NSString *_cachedMarkdown; + + NSMutableArray *_naturalColWidths; + CGFloat _naturalTotalTableWidth; + CGFloat _lastStretchLayoutWidth; } - (instancetype)initWithConfig:(StyleConfig *)config @@ -57,6 +61,7 @@ - (instancetype)initWithConfig:(StyleConfig *)config _allowFontScaling = YES; _maxFontSizeMultiplier = 0; _enableLinkPreview = YES; + _lastStretchLayoutWidth = -1; [self setupScrollView]; } return self; @@ -244,11 +249,26 @@ - (void)computeLayout } } + [self recomputeRowHeightsForColumnWidths:_colWidths]; + + _totalTableWidth = [[_colWidths valueForKeyPath:@"@sum.self"] doubleValue] + _borderWidth; + _totalTableHeight = [[_rowHeights valueForKeyPath:@"@sum.self"] doubleValue] + _borderWidth; + + _naturalColWidths = [_colWidths mutableCopy]; + _naturalTotalTableWidth = _totalTableWidth; + _lastStretchLayoutWidth = -1; +} + +- (void)recomputeRowHeightsForColumnWidths:(NSArray *)columnWidths +{ + const CGFloat horizontalPadding = self.config.tableCellPaddingHorizontal * 2; + const CGFloat verticalPadding = self.config.tableCellPaddingVertical * 2; + _rowHeights = [NSMutableArray arrayWithCapacity:_rows.count]; for (NSArray *row in _rows) { CGFloat maxHeight = 0; for (NSUInteger column = 0; column < row.count; column++) { - CGFloat availableWidth = [_colWidths[column] doubleValue] - horizontalPadding; + CGFloat availableWidth = [columnWidths[column] doubleValue] - horizontalPadding; CGRect boundingRect = [row[column].attributedText boundingRectWithSize:CGSizeMake(availableWidth, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading @@ -257,9 +277,59 @@ - (void)computeLayout } [_rowHeights addObject:@(maxHeight)]; } +} + +/// When the table's intrinsic width is smaller than the parent, grow columns proportionally (CSS `width: 100%` behavior). +- (void)applyStretchIfNeededForParentWidth:(CGFloat)viewWidth +{ + if (_naturalColWidths.count == 0) + return; + + CGFloat targetTotal = MAX(_naturalTotalTableWidth, viewWidth); + CGFloat naturalContent = [[_naturalColWidths valueForKeyPath:@"@sum.self"] doubleValue]; + if (naturalContent <= 0) + return; + + CGFloat targetContent = targetTotal - _borderWidth; + CGFloat extra = targetContent - naturalContent; + + NSMutableArray *newWidths; + if (extra <= 0.5) { + newWidths = [_naturalColWidths mutableCopy]; + } else { + newWidths = [NSMutableArray arrayWithCapacity:_naturalColWidths.count]; + for (NSNumber *n in _naturalColWidths) { + CGFloat w = [n doubleValue]; + CGFloat nw = w + extra * (w / naturalContent); + [newWidths addObject:@(nw)]; + } + CGFloat sum = [[newWidths valueForKeyPath:@"@sum.self"] doubleValue]; + CGFloat drift = targetContent - sum; + if (fabs(drift) > 0.01 && newWidths.count > 0) { + NSUInteger last = newWidths.count - 1; + newWidths[last] = @([newWidths[last] doubleValue] + drift); + } + } + BOOL equal = (_colWidths.count == newWidths.count); + if (equal) { + for (NSUInteger i = 0; i < newWidths.count; i++) { + if (fabs([newWidths[i] doubleValue] - [_colWidths[i] doubleValue]) > 0.5) { + equal = NO; + break; + } + } + } else { + equal = NO; + } + if (equal) + return; + + _colWidths = newWidths; + [self recomputeRowHeightsForColumnWidths:_colWidths]; _totalTableWidth = [[_colWidths valueForKeyPath:@"@sum.self"] doubleValue] + _borderWidth; _totalTableHeight = [[_rowHeights valueForKeyPath:@"@sum.self"] doubleValue] + _borderWidth; + [self renderGrid]; } - (void)renderGrid @@ -597,6 +667,11 @@ - (void)layoutSubviews { [super layoutSubviews]; _scrollView.frame = self.bounds; + CGFloat layoutWidth = self.bounds.size.width; + if (_naturalColWidths.count > 0 && layoutWidth != _lastStretchLayoutWidth) { + _lastStretchLayoutWidth = layoutWidth; + [self applyStretchIfNeededForParentWidth:layoutWidth]; + } #if !TARGET_OS_OSX _scrollView.contentSize = CGSizeMake(MAX(_totalTableWidth, self.bounds.size.width), _totalTableHeight); _scrollView.scrollEnabled = (_totalTableWidth > self.bounds.size.width); diff --git a/src/web/styles.ts b/src/web/styles.ts index dd333e2b..2eecd4c5 100644 --- a/src/web/styles.ts +++ b/src/web/styles.ts @@ -385,6 +385,8 @@ export function tableBodyRowStyle( function tableWrapperStyle(style: MarkdownStyleInternal): CSSProperties { const table = style.table; return { + width: '100%', + minWidth: 0, overflowX: 'auto', overflowY: 'hidden', marginTop: table.marginTop, From 3787679030aa7ba84d609d9fe0e0450d9dae2064 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 21:10:29 -0700 Subject: [PATCH 15/23] custom selection color --- .../enriched/markdown/EnrichedMarkdown.kt | 21 +++++ .../markdown/EnrichedMarkdownManager.kt | 14 ++++ .../enriched/markdown/EnrichedMarkdownText.kt | 16 ++++ .../markdown/EnrichedMarkdownTextManager.kt | 14 ++++ .../utils/text/view/TextSelectionColors.kt | 47 +++++++++++ apps/example/src/App.tsx | 2 + ios/EnrichedMarkdown.mm | 28 +++++++ ios/EnrichedMarkdownText.mm | 12 +++ src/EnrichedMarkdownNativeComponent.ts | 24 ++++++ src/EnrichedMarkdownTextNativeComponent.ts | 24 ++++++ src/native/EnrichedMarkdownText.tsx | 4 + src/types/MarkdownTextProps.ts | 26 ++++++- src/types/MarkdownTextProps.web.ts | 12 +++ src/web/EnrichedMarkdownText.tsx | 77 ++++++++++++++----- 14 files changed, 300 insertions(+), 21 deletions(-) create mode 100644 android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt index 0fece653..2352e07c 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt @@ -18,6 +18,7 @@ import com.swmansion.enriched.markdown.utils.common.FeatureFlags import com.swmansion.enriched.markdown.utils.common.MarkdownSegmentRenderer import com.swmansion.enriched.markdown.utils.common.RenderedSegment import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments +import com.swmansion.enriched.markdown.utils.text.view.applyMarkdownSelectionColors import com.swmansion.enriched.markdown.utils.text.view.emitLinkLongPressEvent import com.swmansion.enriched.markdown.utils.text.view.emitLinkPressEvent import com.swmansion.enriched.markdown.views.BlockSegmentView @@ -54,6 +55,8 @@ class EnrichedMarkdown private var maxFontSizeMultiplier: Float = 0f private var allowTrailingMargin: Boolean = false private var selectable: Boolean = true + private var propSelectionColor: Int? = null + private var propSelectionHandleColor: Int? = null private var onLinkPressCallback: ((String) -> Unit)? = null private var onLinkLongPressCallback: ((String) -> Unit)? = null @@ -130,6 +133,22 @@ class EnrichedMarkdown } } + fun setSelectionColorFromProps(color: Int?) { + propSelectionColor = color + applySelectionColorsToSegments() + } + + fun setSelectionHandleColorFromProps(color: Int?) { + propSelectionHandleColor = color + applySelectionColorsToSegments() + } + + private fun applySelectionColorsToSegments() { + segmentViews.filterIsInstance().forEach { + it.applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + } + } + fun setOnLinkPressCallback(callback: (String) -> Unit) { onLinkPressCallback = callback } @@ -246,6 +265,8 @@ class EnrichedMarkdown if (contextMenuItemTexts.isNotEmpty()) { setContextMenuItems(contextMenuItemTexts, ::forwardContextMenuItemPress) } + + applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) } private fun createTableView( diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt index f391ae25..87a6c184 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt @@ -90,6 +90,20 @@ class EnrichedMarkdownManager : view?.setIsSelectable(selectable) } + override fun setSelectionColor( + view: EnrichedMarkdown?, + value: Int?, + ) { + view?.setSelectionColorFromProps(value) + } + + override fun setSelectionHandleColor( + view: EnrichedMarkdown?, + value: Int?, + ) { + view?.setSelectionHandleColorFromProps(value) + } + @ReactProp(name = "md4cFlags") override fun setMd4cFlags( view: EnrichedMarkdown?, diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt index 8bc7931e..0cf55601 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt @@ -22,6 +22,7 @@ import com.swmansion.enriched.markdown.styles.StyleConfig import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod +import com.swmansion.enriched.markdown.utils.text.view.applyMarkdownSelectionColors import com.swmansion.enriched.markdown.utils.text.view.applySelectableState import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap @@ -83,6 +84,9 @@ class EnrichedMarkdownText private set var spoilerOverlay: SpoilerOverlay = SpoilerOverlay.PARTICLES + private var propSelectionColor: Int? = null + private var propSelectionHandleColor: Int? = null + init { setupAsMarkdownTextView() customSelectionActionModeCallback = @@ -252,6 +256,8 @@ class EnrichedMarkdownText fadeAnimator?.animate(tailStart, styledText.length) previousTextLength = styledText.length } + + applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) } fun setContextMenuItems(items: List) { @@ -262,6 +268,16 @@ class EnrichedMarkdownText applySelectableState(selectable) } + fun setSelectionColorFromProps(color: Int?) { + propSelectionColor = color + applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + } + + fun setSelectionHandleColorFromProps(color: Int?) { + propSelectionHandleColor = color + applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + } + fun emitOnLinkPress(url: String) { emitLinkPressEvent(url) } diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt index 4cce5323..0f2993f5 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt @@ -100,6 +100,20 @@ class EnrichedMarkdownTextManager : view?.setIsSelectable(selectable) } + override fun setSelectionColor( + view: EnrichedMarkdownText?, + value: Int?, + ) { + view?.setSelectionColorFromProps(value) + } + + override fun setSelectionHandleColor( + view: EnrichedMarkdownText?, + value: Int?, + ) { + view?.setSelectionHandleColorFromProps(value) + } + @ReactProp(name = "md4cFlags") override fun setMd4cFlags( view: EnrichedMarkdownText?, diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt new file mode 100644 index 00000000..260a81c2 --- /dev/null +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt @@ -0,0 +1,47 @@ +package com.swmansion.enriched.markdown.utils.text.view + +import android.os.Build +import android.widget.TextView +import androidx.annotation.ColorInt +import androidx.core.graphics.drawable.DrawableCompat + +/** + * Applies selection highlight and (where supported) handle tinting to a [TextView]. + * + * Handle drawables are only tinted on API 29+ where the framework exposes getters; + * on older versions the handle theme defaults remain unchanged. + */ +fun TextView.applyMarkdownSelectionColors( + selectionColor: Int?, + selectionHandleColor: Int?, +) { + selectionColor?.let { highlightColor = it } + selectionHandleColor?.let { applySelectionHandleTint(it) } +} + +private fun TextView.applySelectionHandleTint( + @ColorInt color: Int, +) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return + } + try { + tintHandle(textSelectHandleLeft, color)?.let { setTextSelectHandleLeft(it) } + tintHandle(textSelectHandle, color)?.let { setTextSelectHandle(it) } + tintHandle(textSelectHandleRight, color)?.let { setTextSelectHandleRight(it) } + } catch (_: Exception) { + // Defensive: OEM TextView variants may not support all handle accessors. + } +} + +private fun tintHandle( + drawable: android.graphics.drawable.Drawable?, + @ColorInt color: Int, +): android.graphics.drawable.Drawable? { + if (drawable == null) { + return null + } + val mutated = drawable.mutate() + DrawableCompat.setTint(mutated, color) + return mutated +} diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index 77ef0eb6..45725414 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -129,6 +129,8 @@ export default function App() { onMentionPress={handleMentionPress} markdownStyle={markdownStyle} contextMenuItems={contextMenuItems} + selectionColor={Platform.OS === 'ios' ? "#5A52FA" : "#DCDDFE"} + selectionHandleColor="#5A52FA" /> )} diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index 1580c6d2..827290cf 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -7,6 +7,7 @@ #import "EditMenuUtils.h" #import "ENRMFeatureFlags.h" +#import "ENRMUIKit.h" #if ENRICHED_MARKDOWN_MATH #import "ENRMMathContainerView.h" @@ -90,6 +91,7 @@ + (instancetype)segmentWithLatex:(NSString *)latex #endif @interface EnrichedMarkdown () +- (void)applySelectionTintFromProps:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView; @end @implementation EnrichedMarkdown { @@ -493,6 +495,9 @@ - (EnrichedMarkdownInternalText *)createTextViewForRenderedSegment:(ENRMRenderRe view.textView.selectable = _selectable; [view applyAttributedText:segment.attributedText context:segment.context]; + const auto &selectionProps = *std::static_pointer_cast(self->_props); + [self applySelectionTintFromProps:selectionProps toTextView:view.textView]; + ENRMTapRecognizer *tapRecognizer = [[ENRMTapRecognizer alloc] initWithTarget:self action:@selector(textTapped:)]; [view.textView addGestureRecognizer:tapRecognizer]; @@ -685,6 +690,18 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & } } + if (newViewProps.selectionHandleColor != oldViewProps.selectionHandleColor || + newViewProps.selectionColor != oldViewProps.selectionColor) { +#if !TARGET_OS_OSX + for (RCTUIView *segment in _segmentViews) { + if ([segment isKindOfClass:[EnrichedMarkdownInternalText class]]) { + ENRMPlatformTextView *tv = ((EnrichedMarkdownInternalText *)segment).textView; + [self applySelectionTintFromProps:newViewProps toTextView:tv]; + } + } +#endif + } + if (markdownChanged || stylePropChanged || md4cFlagsChanged || allowTrailingMarginChanged) { NSString *markdownString = [[NSString alloc] initWithUTF8String:newViewProps.markdown.c_str()]; [self renderMarkdownContent:markdownString]; @@ -925,4 +942,15 @@ - (NSInteger)indexOfAccessibilityElement:(id)element } #endif +- (void)applySelectionTintFromProps:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView +{ +#if !TARGET_OS_OSX + if (isColorMeaningful(props.selectionHandleColor)) { + ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionHandleColor)); + } else if (isColorMeaningful(props.selectionColor)) { + ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionColor)); + } +#endif +} + @end diff --git a/ios/EnrichedMarkdownText.mm b/ios/EnrichedMarkdownText.mm index 1356619a..dc6674f4 100644 --- a/ios/EnrichedMarkdownText.mm +++ b/ios/EnrichedMarkdownText.mm @@ -9,6 +9,7 @@ #import "ENRMTailFadeInAnimator.h" #import "ENRMTextRenderer.h" #import "ENRMTextViewSetup.h" +#import "ENRMUIKit.h" #import "EditMenuUtils.h" #import "FontScaleObserver.h" #import "FontUtils.h" @@ -411,6 +412,17 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & _textView.selectable = newViewProps.selectable; } + if (newViewProps.selectionHandleColor != oldViewProps.selectionHandleColor || + newViewProps.selectionColor != oldViewProps.selectionColor) { +#if !TARGET_OS_OSX + if (isColorMeaningful(newViewProps.selectionHandleColor)) { + ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionHandleColor)); + } else if (isColorMeaningful(newViewProps.selectionColor)) { + ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor)); + } +#endif + } + if (newViewProps.allowFontScaling != oldViewProps.allowFontScaling) { _fontScaleObserver.allowFontScaling = newViewProps.allowFontScaling; diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index 915bd6fc..d499c314 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -320,6 +320,30 @@ export interface NativeProps extends ViewProps { * @default true */ selectable?: boolean; + /** + * Color of the text selection highlight (selected text background). + * + * - **Android**: maps to `TextView.highlightColor`. + * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and + * selection handles together. When `selectionHandleColor` is also set, it + * takes precedence for `tintColor` (see that prop). + * - **Web**: maps to `::selection` background via a CSS variable on the root. + * + * @platform ios, android, web + */ + selectionColor?: ColorValue; + /** + * Color of the selection handles (drag anchors). + * + * - **Android**: tints the left, mid, and right handle drawables (API 29+; + * older API levels leave handles at the default theme color). + * - **iOS**: merged with selection via `tintColor` — when set, it overrides + * `selectionColor` for the shared `tintColor` (highlight + handles + caret). + * - **Web**: best-effort via `accent-color` on the root; browser support varies. + * + * @platform ios, android, web + */ + selectionHandleColor?: ColorValue; /** * MD4C parser flags configuration. * Controls how the markdown parser interprets certain syntax. diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index 9ec7abef..8e88473f 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -320,6 +320,30 @@ export interface NativeProps extends ViewProps { * @default true */ selectable?: boolean; + /** + * Color of the text selection highlight (selected text background). + * + * - **Android**: maps to `TextView.highlightColor`. + * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and + * selection handles together. When `selectionHandleColor` is also set, it + * takes precedence for `tintColor` (see that prop). + * - **Web**: maps to `::selection` background via a CSS variable on the root. + * + * @platform ios, android, web + */ + selectionColor?: ColorValue; + /** + * Color of the selection handles (drag anchors). + * + * - **Android**: tints the left, mid, and right handle drawables (API 29+; + * older API levels leave handles at the default theme color). + * - **iOS**: merged with selection via `tintColor` — when set, it overrides + * `selectionColor` for the shared `tintColor` (highlight + handles + caret). + * - **Web**: best-effort via `accent-color` on the root; browser support varies. + * + * @platform ios, android, web + */ + selectionHandleColor?: ColorValue; /** * MD4C parser flags configuration. * Controls how the markdown parser interprets certain syntax. diff --git a/src/native/EnrichedMarkdownText.tsx b/src/native/EnrichedMarkdownText.tsx index efa1bb57..03715f80 100644 --- a/src/native/EnrichedMarkdownText.tsx +++ b/src/native/EnrichedMarkdownText.tsx @@ -52,6 +52,8 @@ export const EnrichedMarkdownText = ({ streamingAnimation = false, spoilerOverlay = 'particles', contextMenuItems, + selectionColor, + selectionHandleColor, ...rest }: EnrichedMarkdownTextProps) => { const normalizedStyleRef = useRef(null); @@ -165,6 +167,8 @@ export const EnrichedMarkdownText = ({ style: containerStyle, contextMenuItems: nativeContextMenuItems, onContextMenuItemPress: handleContextMenuItemPress, + selectionColor, + selectionHandleColor, ...rest, }; diff --git a/src/types/MarkdownTextProps.ts b/src/types/MarkdownTextProps.ts index 75a36993..c650f6c9 100644 --- a/src/types/MarkdownTextProps.ts +++ b/src/types/MarkdownTextProps.ts @@ -1,4 +1,4 @@ -import type { ViewProps, ViewStyle, TextStyle } from 'react-native'; +import type { ColorValue, ViewProps, ViewStyle, TextStyle } from 'react-native'; import type { MarkdownStyle, Md4cFlags } from './MarkdownStyle'; import type { LinkPressEvent, @@ -107,6 +107,30 @@ export interface EnrichedMarkdownTextProps extends Omit { * @platform ios, android, web */ selectable?: boolean; + /** + * Color of the text selection highlight (selected text background). + * + * - **Android**: maps to `TextView.highlightColor`. + * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and + * selection handles together. When `selectionHandleColor` is also set, it + * takes precedence for `tintColor` (see that prop). + * - **Web**: maps to `::selection` background via a CSS variable on the root. + * + * @platform ios, android, web + */ + selectionColor?: ColorValue; + /** + * Color of the selection handles (drag anchors). + * + * - **Android**: tints the left, mid, and right handle drawables (API 29+; + * older API levels leave handles at the default theme color). + * - **iOS**: merged with selection via `tintColor` — when set, it overrides + * `selectionColor` for the shared `tintColor` (highlight + handles + caret). + * - **Web**: best-effort via `accent-color` on the root; browser support varies. + * + * @platform ios, android, web + */ + selectionHandleColor?: ColorValue; /** * Specifies whether fonts should scale to respect Text Size accessibility settings. * When false, text will not scale with the user's accessibility settings. diff --git a/src/types/MarkdownTextProps.web.ts b/src/types/MarkdownTextProps.web.ts index bf117492..f87814b2 100644 --- a/src/types/MarkdownTextProps.web.ts +++ b/src/types/MarkdownTextProps.web.ts @@ -1,3 +1,4 @@ +import type { ColorValue } from 'react-native'; import type { CSSProperties, HTMLAttributes } from 'react'; import type { MarkdownStyle, Md4cFlags } from './MarkdownStyle'; import type { @@ -79,6 +80,17 @@ export interface EnrichedMarkdownTextProps * @platform ios, android, web */ selectable?: boolean; + /** + * Color of the text selection highlight (`::selection` background). + * @platform web + */ + selectionColor?: ColorValue; + /** + * Best-effort tint for selection affordances (`accent-color` on the root). + * Selection handle appearance is largely browser-controlled. + * @platform web + */ + selectionHandleColor?: ColorValue; /** * When false (default), removes trailing margin from the last element to * eliminate bottom spacing. diff --git a/src/web/EnrichedMarkdownText.tsx b/src/web/EnrichedMarkdownText.tsx index f7d60e8e..69ef0e9a 100644 --- a/src/web/EnrichedMarkdownText.tsx +++ b/src/web/EnrichedMarkdownText.tsx @@ -3,6 +3,7 @@ import { useEffect, useMemo, useCallback, + Fragment, type CSSProperties, type ClipboardEvent, } from 'react'; @@ -34,6 +35,8 @@ export const EnrichedMarkdownText = ({ containerStyle, selectable = true, dir, + selectionColor, + selectionHandleColor, ...rest }: EnrichedMarkdownTextProps) => { const normalizedStyle = useMemo( @@ -117,15 +120,25 @@ export const EnrichedMarkdownText = ({ [lastChildStyle] ); - const wrapperStyle = useMemo( - () => ({ + const wrapperStyle = useMemo(() => { + const selectionBgVar = + selectionColor != null && selectionColor !== undefined + ? String(selectionColor) + : undefined; + + return { display: 'flex', flexDirection: 'column', ...(containerStyle as CSSProperties), ...(selectable ? undefined : { userSelect: 'none' }), - }), - [containerStyle, selectable] - ); + ...(selectionBgVar != null + ? ({ ['--enrm-selection-bg']: selectionBgVar } as CSSProperties) + : null), + ...(selectionHandleColor != null && selectionHandleColor !== undefined + ? { accentColor: String(selectionHandleColor) } + : null), + }; + }, [containerStyle, selectable, selectionColor, selectionHandleColor]); // The browser's default copy picks up the text content of the selected // DOM, which would include citation markers. Citations are reference @@ -183,11 +196,26 @@ export const EnrichedMarkdownText = ({ event.preventDefault(); }, []); + const selectionStyle = + selectionColor != null && selectionColor !== undefined ? ( + + ) : null; + if (parseError) { return ( -
-
{markdown}
-
+ + {selectionStyle} +
+
{markdown}
+
+
); } @@ -197,18 +225,27 @@ export const EnrichedMarkdownText = ({ const lastIdx = children.length - 1; return ( -
- {children.map((child, index) => ( - - ))} -
+ + {selectionStyle} +
+ {children.map((child, index) => ( + + ))} +
+
); }; From b6630c6dbb4112129ad2a0ca4249e92c2f868b96 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Fri, 17 Apr 2026 22:07:33 -0700 Subject: [PATCH 16/23] add example color --- apps/web-example/src/App.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/apps/web-example/src/App.tsx b/apps/web-example/src/App.tsx index 418fd0fd..fb496873 100644 --- a/apps/web-example/src/App.tsx +++ b/apps/web-example/src/App.tsx @@ -196,6 +196,8 @@ export default function App() { borderRadius: 99, }, }} + selectionColor="#DCDDFE" + selectionHandleColor="#5A52FA" /> From 2faa042ce6641131d6d6a176761d7155206bc116 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Mon, 20 Apr 2026 10:10:50 -0700 Subject: [PATCH 17/23] feedback: only use selectionHandleColor in android --- ios/EnrichedMarkdown.mm | 7 ++----- ios/EnrichedMarkdownText.mm | 7 ++----- src/EnrichedMarkdownNativeComponent.ts | 9 ++------- src/EnrichedMarkdownTextNativeComponent.ts | 9 ++------- src/types/MarkdownTextProps.ts | 8 ++------ src/types/MarkdownTextProps.web.ts | 6 ------ src/web/EnrichedMarkdownText.tsx | 6 +----- 7 files changed, 11 insertions(+), 41 deletions(-) diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index 827290cf..1d48702a 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -690,8 +690,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & } } - if (newViewProps.selectionHandleColor != oldViewProps.selectionHandleColor || - newViewProps.selectionColor != oldViewProps.selectionColor) { + if (newViewProps.selectionColor != oldViewProps.selectionColor) { #if !TARGET_OS_OSX for (RCTUIView *segment in _segmentViews) { if ([segment isKindOfClass:[EnrichedMarkdownInternalText class]]) { @@ -945,9 +944,7 @@ - (NSInteger)indexOfAccessibilityElement:(id)element - (void)applySelectionTintFromProps:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView { #if !TARGET_OS_OSX - if (isColorMeaningful(props.selectionHandleColor)) { - ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionHandleColor)); - } else if (isColorMeaningful(props.selectionColor)) { + if (isColorMeaningful(props.selectionColor)) { ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionColor)); } #endif diff --git a/ios/EnrichedMarkdownText.mm b/ios/EnrichedMarkdownText.mm index dc6674f4..104a1192 100644 --- a/ios/EnrichedMarkdownText.mm +++ b/ios/EnrichedMarkdownText.mm @@ -412,12 +412,9 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & _textView.selectable = newViewProps.selectable; } - if (newViewProps.selectionHandleColor != oldViewProps.selectionHandleColor || - newViewProps.selectionColor != oldViewProps.selectionColor) { + if (newViewProps.selectionColor != oldViewProps.selectionColor) { #if !TARGET_OS_OSX - if (isColorMeaningful(newViewProps.selectionHandleColor)) { - ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionHandleColor)); - } else if (isColorMeaningful(newViewProps.selectionColor)) { + if (isColorMeaningful(newViewProps.selectionColor)) { ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor)); } #endif diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index d499c314..4bd9fe30 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -325,8 +325,7 @@ export interface NativeProps extends ViewProps { * * - **Android**: maps to `TextView.highlightColor`. * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. When `selectionHandleColor` is also set, it - * takes precedence for `tintColor` (see that prop). + * selection handles together. * - **Web**: maps to `::selection` background via a CSS variable on the root. * * @platform ios, android, web @@ -337,11 +336,7 @@ export interface NativeProps extends ViewProps { * * - **Android**: tints the left, mid, and right handle drawables (API 29+; * older API levels leave handles at the default theme color). - * - **iOS**: merged with selection via `tintColor` — when set, it overrides - * `selectionColor` for the shared `tintColor` (highlight + handles + caret). - * - **Web**: best-effort via `accent-color` on the root; browser support varies. - * - * @platform ios, android, web + * @platform android */ selectionHandleColor?: ColorValue; /** diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index 8e88473f..32babd3b 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -325,8 +325,7 @@ export interface NativeProps extends ViewProps { * * - **Android**: maps to `TextView.highlightColor`. * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. When `selectionHandleColor` is also set, it - * takes precedence for `tintColor` (see that prop). + * selection handles together. * - **Web**: maps to `::selection` background via a CSS variable on the root. * * @platform ios, android, web @@ -337,11 +336,7 @@ export interface NativeProps extends ViewProps { * * - **Android**: tints the left, mid, and right handle drawables (API 29+; * older API levels leave handles at the default theme color). - * - **iOS**: merged with selection via `tintColor` — when set, it overrides - * `selectionColor` for the shared `tintColor` (highlight + handles + caret). - * - **Web**: best-effort via `accent-color` on the root; browser support varies. - * - * @platform ios, android, web + * @platform android */ selectionHandleColor?: ColorValue; /** diff --git a/src/types/MarkdownTextProps.ts b/src/types/MarkdownTextProps.ts index c650f6c9..c519c3ca 100644 --- a/src/types/MarkdownTextProps.ts +++ b/src/types/MarkdownTextProps.ts @@ -112,8 +112,7 @@ export interface EnrichedMarkdownTextProps extends Omit { * * - **Android**: maps to `TextView.highlightColor`. * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. When `selectionHandleColor` is also set, it - * takes precedence for `tintColor` (see that prop). + * selection handles together. * - **Web**: maps to `::selection` background via a CSS variable on the root. * * @platform ios, android, web @@ -124,11 +123,8 @@ export interface EnrichedMarkdownTextProps extends Omit { * * - **Android**: tints the left, mid, and right handle drawables (API 29+; * older API levels leave handles at the default theme color). - * - **iOS**: merged with selection via `tintColor` — when set, it overrides - * `selectionColor` for the shared `tintColor` (highlight + handles + caret). - * - **Web**: best-effort via `accent-color` on the root; browser support varies. * - * @platform ios, android, web + * @platform android */ selectionHandleColor?: ColorValue; /** diff --git a/src/types/MarkdownTextProps.web.ts b/src/types/MarkdownTextProps.web.ts index f87814b2..86a442df 100644 --- a/src/types/MarkdownTextProps.web.ts +++ b/src/types/MarkdownTextProps.web.ts @@ -85,12 +85,6 @@ export interface EnrichedMarkdownTextProps * @platform web */ selectionColor?: ColorValue; - /** - * Best-effort tint for selection affordances (`accent-color` on the root). - * Selection handle appearance is largely browser-controlled. - * @platform web - */ - selectionHandleColor?: ColorValue; /** * When false (default), removes trailing margin from the last element to * eliminate bottom spacing. diff --git a/src/web/EnrichedMarkdownText.tsx b/src/web/EnrichedMarkdownText.tsx index 69ef0e9a..8fab0257 100644 --- a/src/web/EnrichedMarkdownText.tsx +++ b/src/web/EnrichedMarkdownText.tsx @@ -36,7 +36,6 @@ export const EnrichedMarkdownText = ({ selectable = true, dir, selectionColor, - selectionHandleColor, ...rest }: EnrichedMarkdownTextProps) => { const normalizedStyle = useMemo( @@ -134,11 +133,8 @@ export const EnrichedMarkdownText = ({ ...(selectionBgVar != null ? ({ ['--enrm-selection-bg']: selectionBgVar } as CSSProperties) : null), - ...(selectionHandleColor != null && selectionHandleColor !== undefined - ? { accentColor: String(selectionHandleColor) } - : null), }; - }, [containerStyle, selectable, selectionColor, selectionHandleColor]); + }, [containerStyle, selectable, selectionColor]); // The browser's default copy picks up the text content of the selected // DOM, which would include citation markers. Citations are reference From 6a04cf4bc33efdbff8d192bba113ac378e0e49a2 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 10:06:41 -0700 Subject: [PATCH 18/23] rename --- .../enriched/markdown/EnrichedMarkdown.kt | 18 ++++++++--------- .../markdown/EnrichedMarkdownManager.kt | 4 ++-- .../enriched/markdown/EnrichedMarkdownText.kt | 20 +++++++++---------- .../markdown/EnrichedMarkdownTextManager.kt | 4 ++-- .../utils/text/view/TextSelectionColors.kt | 2 +- ios/EnrichedMarkdown.mm | 8 ++++---- src/web/EnrichedMarkdownText.tsx | 15 +++++++------- 7 files changed, 35 insertions(+), 36 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt index 2352e07c..cac7a6d2 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdown.kt @@ -18,7 +18,7 @@ import com.swmansion.enriched.markdown.utils.common.FeatureFlags import com.swmansion.enriched.markdown.utils.common.MarkdownSegmentRenderer import com.swmansion.enriched.markdown.utils.common.RenderedSegment import com.swmansion.enriched.markdown.utils.common.splitASTIntoSegments -import com.swmansion.enriched.markdown.utils.text.view.applyMarkdownSelectionColors +import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors import com.swmansion.enriched.markdown.utils.text.view.emitLinkLongPressEvent import com.swmansion.enriched.markdown.utils.text.view.emitLinkPressEvent import com.swmansion.enriched.markdown.views.BlockSegmentView @@ -55,8 +55,8 @@ class EnrichedMarkdown private var maxFontSizeMultiplier: Float = 0f private var allowTrailingMargin: Boolean = false private var selectable: Boolean = true - private var propSelectionColor: Int? = null - private var propSelectionHandleColor: Int? = null + private var selectionColor: Int? = null + private var selectionHandleColor: Int? = null private var onLinkPressCallback: ((String) -> Unit)? = null private var onLinkLongPressCallback: ((String) -> Unit)? = null @@ -133,19 +133,19 @@ class EnrichedMarkdown } } - fun setSelectionColorFromProps(color: Int?) { - propSelectionColor = color + fun setSelectionColor(color: Int?) { + selectionColor = color applySelectionColorsToSegments() } - fun setSelectionHandleColorFromProps(color: Int?) { - propSelectionHandleColor = color + fun setSelectionHandleColor(color: Int?) { + selectionHandleColor = color applySelectionColorsToSegments() } private fun applySelectionColorsToSegments() { segmentViews.filterIsInstance().forEach { - it.applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + it.applySelectionColors(selectionColor, selectionHandleColor) } } @@ -266,7 +266,7 @@ class EnrichedMarkdown setContextMenuItems(contextMenuItemTexts, ::forwardContextMenuItemPress) } - applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + applySelectionColors(selectionColor, selectionHandleColor) } private fun createTableView( diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt index 87a6c184..34698618 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownManager.kt @@ -94,14 +94,14 @@ class EnrichedMarkdownManager : view: EnrichedMarkdown?, value: Int?, ) { - view?.setSelectionColorFromProps(value) + view?.setSelectionColor(value) } override fun setSelectionHandleColor( view: EnrichedMarkdown?, value: Int?, ) { - view?.setSelectionHandleColorFromProps(value) + view?.setSelectionHandleColor(value) } @ReactProp(name = "md4cFlags") diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt index 0cf55601..e88f9d90 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownText.kt @@ -22,8 +22,8 @@ import com.swmansion.enriched.markdown.styles.StyleConfig import com.swmansion.enriched.markdown.utils.text.TailFadeInAnimator import com.swmansion.enriched.markdown.utils.text.interaction.CheckboxTouchHelper import com.swmansion.enriched.markdown.utils.text.view.LinkLongPressMovementMethod -import com.swmansion.enriched.markdown.utils.text.view.applyMarkdownSelectionColors import com.swmansion.enriched.markdown.utils.text.view.applySelectableState +import com.swmansion.enriched.markdown.utils.text.view.applySelectionColors import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForCheckboxTap import com.swmansion.enriched.markdown.utils.text.view.cancelJSTouchForLinkTap import com.swmansion.enriched.markdown.utils.text.view.createSelectionActionModeCallback @@ -84,8 +84,8 @@ class EnrichedMarkdownText private set var spoilerOverlay: SpoilerOverlay = SpoilerOverlay.PARTICLES - private var propSelectionColor: Int? = null - private var propSelectionHandleColor: Int? = null + private var selectionColor: Int? = null + private var selectionHandleColor: Int? = null init { setupAsMarkdownTextView() @@ -257,7 +257,7 @@ class EnrichedMarkdownText previousTextLength = styledText.length } - applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + applySelectionColors(selectionColor, selectionHandleColor) } fun setContextMenuItems(items: List) { @@ -268,14 +268,14 @@ class EnrichedMarkdownText applySelectableState(selectable) } - fun setSelectionColorFromProps(color: Int?) { - propSelectionColor = color - applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + fun setSelectionColor(color: Int?) { + selectionColor = color + applySelectionColors(selectionColor, selectionHandleColor) } - fun setSelectionHandleColorFromProps(color: Int?) { - propSelectionHandleColor = color - applyMarkdownSelectionColors(propSelectionColor, propSelectionHandleColor) + fun setSelectionHandleColor(color: Int?) { + selectionHandleColor = color + applySelectionColors(selectionColor, selectionHandleColor) } fun emitOnLinkPress(url: String) { diff --git a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt index 0f2993f5..ed860bc8 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/EnrichedMarkdownTextManager.kt @@ -104,14 +104,14 @@ class EnrichedMarkdownTextManager : view: EnrichedMarkdownText?, value: Int?, ) { - view?.setSelectionColorFromProps(value) + view?.setSelectionColor(value) } override fun setSelectionHandleColor( view: EnrichedMarkdownText?, value: Int?, ) { - view?.setSelectionHandleColorFromProps(value) + view?.setSelectionHandleColor(value) } @ReactProp(name = "md4cFlags") diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt index 260a81c2..70dccfe8 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt @@ -11,7 +11,7 @@ import androidx.core.graphics.drawable.DrawableCompat * Handle drawables are only tinted on API 29+ where the framework exposes getters; * on older versions the handle theme defaults remain unchanged. */ -fun TextView.applyMarkdownSelectionColors( +fun TextView.applySelectionColors( selectionColor: Int?, selectionHandleColor: Int?, ) { diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index 1d48702a..cf17a39f 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -91,7 +91,7 @@ + (instancetype)segmentWithLatex:(NSString *)latex #endif @interface EnrichedMarkdown () -- (void)applySelectionTintFromProps:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView; +- (void)applySelectionColor:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView; @end @implementation EnrichedMarkdown { @@ -496,7 +496,7 @@ - (EnrichedMarkdownInternalText *)createTextViewForRenderedSegment:(ENRMRenderRe [view applyAttributedText:segment.attributedText context:segment.context]; const auto &selectionProps = *std::static_pointer_cast(self->_props); - [self applySelectionTintFromProps:selectionProps toTextView:view.textView]; + [self applySelectionColor:selectionProps toTextView:view.textView]; ENRMTapRecognizer *tapRecognizer = [[ENRMTapRecognizer alloc] initWithTarget:self action:@selector(textTapped:)]; [view.textView addGestureRecognizer:tapRecognizer]; @@ -695,7 +695,7 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & for (RCTUIView *segment in _segmentViews) { if ([segment isKindOfClass:[EnrichedMarkdownInternalText class]]) { ENRMPlatformTextView *tv = ((EnrichedMarkdownInternalText *)segment).textView; - [self applySelectionTintFromProps:newViewProps toTextView:tv]; + [self applySelectionColor:newViewProps toTextView:tv]; } } #endif @@ -941,7 +941,7 @@ - (NSInteger)indexOfAccessibilityElement:(id)element } #endif -- (void)applySelectionTintFromProps:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView +- (void)applySelectionColor:(const EnrichedMarkdownProps &)props toTextView:(ENRMPlatformTextView *)textView { #if !TARGET_OS_OSX if (isColorMeaningful(props.selectionColor)) { diff --git a/src/web/EnrichedMarkdownText.tsx b/src/web/EnrichedMarkdownText.tsx index 8fab0257..3c8bb819 100644 --- a/src/web/EnrichedMarkdownText.tsx +++ b/src/web/EnrichedMarkdownText.tsx @@ -21,6 +21,7 @@ import type { ASTNode, RendererCallbacks, RenderCapabilities } from './types'; import { indexTaskItems, markInlineImages } from './utils'; import { loadKaTeX } from './katex'; import type { KaTeXInstance } from './katex'; +import { normalizeColor } from '../styleUtils'; export const EnrichedMarkdownText = ({ markdown, @@ -120,10 +121,9 @@ export const EnrichedMarkdownText = ({ ); const wrapperStyle = useMemo(() => { - const selectionBgVar = - selectionColor != null && selectionColor !== undefined - ? String(selectionColor) - : undefined; + const selectionBgVar = selectionColor + ? normalizeColor(String(selectionColor)) + : undefined; return { display: 'flex', @@ -192,12 +192,11 @@ export const EnrichedMarkdownText = ({ event.preventDefault(); }, []); - const selectionStyle = - selectionColor != null && selectionColor !== undefined ? ( - - ) : null; + ) : null; if (parseError) { return ( From bb0c4d3dec769bb62697d100e73e75ff9140d096 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 10:14:33 -0700 Subject: [PATCH 19/23] clean up jsdoc --- src/EnrichedMarkdownNativeComponent.ts | 11 ++++------- src/EnrichedMarkdownTextNativeComponent.ts | 11 ++++------- src/types/MarkdownTextProps.ts | 12 ++++-------- src/types/MarkdownTextProps.web.ts | 2 +- 4 files changed, 13 insertions(+), 23 deletions(-) diff --git a/src/EnrichedMarkdownNativeComponent.ts b/src/EnrichedMarkdownNativeComponent.ts index 4bd9fe30..2b86296c 100644 --- a/src/EnrichedMarkdownNativeComponent.ts +++ b/src/EnrichedMarkdownNativeComponent.ts @@ -321,21 +321,18 @@ export interface NativeProps extends ViewProps { */ selectable?: boolean; /** - * Color of the text selection highlight (selected text background). + * Color of the text selection highlight. * - * - **Android**: maps to `TextView.highlightColor`. - * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. - * - **Web**: maps to `::selection` background via a CSS variable on the root. + * On iOS, this also affects the caret and selection handle colors + * (they share a single tint). * * @platform ios, android, web */ selectionColor?: ColorValue; /** * Color of the selection handles (drag anchors). + * No-op on API levels below 29. * - * - **Android**: tints the left, mid, and right handle drawables (API 29+; - * older API levels leave handles at the default theme color). * @platform android */ selectionHandleColor?: ColorValue; diff --git a/src/EnrichedMarkdownTextNativeComponent.ts b/src/EnrichedMarkdownTextNativeComponent.ts index 32babd3b..3a6e076d 100644 --- a/src/EnrichedMarkdownTextNativeComponent.ts +++ b/src/EnrichedMarkdownTextNativeComponent.ts @@ -321,21 +321,18 @@ export interface NativeProps extends ViewProps { */ selectable?: boolean; /** - * Color of the text selection highlight (selected text background). + * Color of the text selection highlight. * - * - **Android**: maps to `TextView.highlightColor`. - * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. - * - **Web**: maps to `::selection` background via a CSS variable on the root. + * On iOS, this also affects the caret and selection handle colors + * (they share a single tint). * * @platform ios, android, web */ selectionColor?: ColorValue; /** * Color of the selection handles (drag anchors). + * No-op on API levels below 29. * - * - **Android**: tints the left, mid, and right handle drawables (API 29+; - * older API levels leave handles at the default theme color). * @platform android */ selectionHandleColor?: ColorValue; diff --git a/src/types/MarkdownTextProps.ts b/src/types/MarkdownTextProps.ts index c519c3ca..aaddba60 100644 --- a/src/types/MarkdownTextProps.ts +++ b/src/types/MarkdownTextProps.ts @@ -108,21 +108,17 @@ export interface EnrichedMarkdownTextProps extends Omit { */ selectable?: boolean; /** - * Color of the text selection highlight (selected text background). + * Color of the text selection highlight. * - * - **Android**: maps to `TextView.highlightColor`. - * - **iOS**: `UITextView.tintColor` drives the selection highlight, caret, and - * selection handles together. - * - **Web**: maps to `::selection` background via a CSS variable on the root. + * On iOS, this also affects the caret and selection handle colors + * (they share a single tint). * * @platform ios, android, web */ selectionColor?: ColorValue; /** * Color of the selection handles (drag anchors). - * - * - **Android**: tints the left, mid, and right handle drawables (API 29+; - * older API levels leave handles at the default theme color). + * No-op on API levels below 29. * * @platform android */ diff --git a/src/types/MarkdownTextProps.web.ts b/src/types/MarkdownTextProps.web.ts index 86a442df..9ac34cf7 100644 --- a/src/types/MarkdownTextProps.web.ts +++ b/src/types/MarkdownTextProps.web.ts @@ -81,7 +81,7 @@ export interface EnrichedMarkdownTextProps */ selectable?: boolean; /** - * Color of the text selection highlight (`::selection` background). + * Color of the text selection highlight. * @platform web */ selectionColor?: ColorValue; From d579ffe6ed590aa8f8cf977c33d4c183b0846a63 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 10:26:28 -0700 Subject: [PATCH 20/23] android: wrap each handle in try catch --- .../utils/text/view/TextSelectionColors.kt | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt index 70dccfe8..ed7423f5 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt @@ -1,10 +1,13 @@ package com.swmansion.enriched.markdown.utils.text.view import android.os.Build +import android.util.Log import android.widget.TextView import androidx.annotation.ColorInt import androidx.core.graphics.drawable.DrawableCompat +private const val TAG = "TextSelectionColors" + /** * Applies selection highlight and (where supported) handle tinting to a [TextView]. * @@ -25,23 +28,20 @@ private fun TextView.applySelectionHandleTint( if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { return } - try { - tintHandle(textSelectHandleLeft, color)?.let { setTextSelectHandleLeft(it) } - tintHandle(textSelectHandle, color)?.let { setTextSelectHandle(it) } - tintHandle(textSelectHandleRight, color)?.let { setTextSelectHandleRight(it) } - } catch (_: Exception) { - // Defensive: OEM TextView variants may not support all handle accessors. - } -} -private fun tintHandle( - drawable: android.graphics.drawable.Drawable?, - @ColorInt color: Int, -): android.graphics.drawable.Drawable? { - if (drawable == null) { - return null + val handles = + listOf( + this::getTextSelectHandleLeft to this::setTextSelectHandleLeft, + this::getTextSelectHandle to this::setTextSelectHandle, + this::getTextSelectHandleRight to this::setTextSelectHandleRight, + ) + + handles.forEach { (getter, setter) -> + try { + getter()?.mutate()?.also { DrawableCompat.setTint(it, color) }?.let(setter) + } catch (e: LinkageError) { + // Defensive: OEM TextView variants may strip individual handle accessors. + Log.w(TAG, "Selection handle tint skipped: ${e.message}") + } } - val mutated = drawable.mutate() - DrawableCompat.setTint(mutated, color) - return mutated } From 0aa16b527366ae60d9491274ad0722fc27ebbe9d Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 10:27:07 -0700 Subject: [PATCH 21/23] ios: handle resetting color --- ios/EnrichedMarkdown.mm | 2 ++ ios/EnrichedMarkdownText.mm | 2 ++ ios/input/EnrichedMarkdownInput.mm | 2 ++ 3 files changed, 6 insertions(+) diff --git a/ios/EnrichedMarkdown.mm b/ios/EnrichedMarkdown.mm index cf17a39f..d967f5d1 100644 --- a/ios/EnrichedMarkdown.mm +++ b/ios/EnrichedMarkdown.mm @@ -946,6 +946,8 @@ - (void)applySelectionColor:(const EnrichedMarkdownProps &)props toTextView:(ENR #if !TARGET_OS_OSX if (isColorMeaningful(props.selectionColor)) { ENRMSetSelectionColor(textView, RCTUIColorFromSharedColor(props.selectionColor)); + } else { + ENRMSetSelectionColor(textView, nil); // resets to inherited / system tint } #endif } diff --git a/ios/EnrichedMarkdownText.mm b/ios/EnrichedMarkdownText.mm index 104a1192..f9666968 100644 --- a/ios/EnrichedMarkdownText.mm +++ b/ios/EnrichedMarkdownText.mm @@ -416,6 +416,8 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & #if !TARGET_OS_OSX if (isColorMeaningful(newViewProps.selectionColor)) { ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor)); + } else { + ENRMSetSelectionColor(_textView, nil); // resets to inherited / system tint } #endif } diff --git a/ios/input/EnrichedMarkdownInput.mm b/ios/input/EnrichedMarkdownInput.mm index b2e187b5..c269130c 100644 --- a/ios/input/EnrichedMarkdownInput.mm +++ b/ios/input/EnrichedMarkdownInput.mm @@ -292,6 +292,8 @@ - (void)updateProps:(Props::Shared const &)props oldProps:(Props::Shared const & if (newViewProps.selectionColor != oldViewProps.selectionColor) { if (isColorMeaningful(newViewProps.selectionColor)) { ENRMSetSelectionColor(_textView, RCTUIColorFromSharedColor(newViewProps.selectionColor)); + } else { + ENRMSetSelectionColor(_textView, nil); // resets to inherited / system tint } } From 8dcd26b596acd210248d3bce4525d7cdbc98d8d8 Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 11:09:41 -0700 Subject: [PATCH 22/23] android: fix --- .../utils/text/view/TextSelectionColors.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt index ed7423f5..4b8e700a 100644 --- a/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt +++ b/android/src/main/java/com/swmansion/enriched/markdown/utils/text/view/TextSelectionColors.kt @@ -1,5 +1,6 @@ package com.swmansion.enriched.markdown.utils.text.view +import android.graphics.drawable.Drawable import android.os.Build import android.util.Log import android.widget.TextView @@ -8,6 +9,9 @@ import androidx.core.graphics.drawable.DrawableCompat private const val TAG = "TextSelectionColors" +private typealias HandleGetter = (TextView) -> Drawable? +private typealias HandleSetter = (TextView, Drawable) -> Unit + /** * Applies selection highlight and (where supported) handle tinting to a [TextView]. * @@ -29,16 +33,16 @@ private fun TextView.applySelectionHandleTint( return } - val handles = + val handles: List> = listOf( - this::getTextSelectHandleLeft to this::setTextSelectHandleLeft, - this::getTextSelectHandle to this::setTextSelectHandle, - this::getTextSelectHandleRight to this::setTextSelectHandleRight, + TextView::getTextSelectHandleLeft to { tv, d -> tv.setTextSelectHandleLeft(d) }, + TextView::getTextSelectHandle to { tv, d -> tv.setTextSelectHandle(d) }, + TextView::getTextSelectHandleRight to { tv, d -> tv.setTextSelectHandleRight(d) }, ) handles.forEach { (getter, setter) -> try { - getter()?.mutate()?.also { DrawableCompat.setTint(it, color) }?.let(setter) + getter(this)?.mutate()?.also { DrawableCompat.setTint(it, color) }?.let { setter(this, it) } } catch (e: LinkageError) { // Defensive: OEM TextView variants may strip individual handle accessors. Log.w(TAG, "Selection handle tint skipped: ${e.message}") From 3aba1b9f5f167a2ca36a0c7587a6510e9640b1bb Mon Sep 17 00:00:00 2001 From: Xindi Xu Date: Tue, 21 Apr 2026 15:04:29 -0700 Subject: [PATCH 23/23] dist --- .gitignore | 11 +- ReactNativeEnrichedMarkdown | 1 + ReactNativeEnrichedMarkdown.podspec | 2 +- .../EnrichedMarkdownInputManagerDelegate.java | 138 + ...EnrichedMarkdownInputManagerInterface.java | 52 + .../EnrichedMarkdownManagerDelegate.java | 72 + .../EnrichedMarkdownManagerInterface.java | 32 + .../EnrichedMarkdownTextManagerDelegate.java | 72 + .../EnrichedMarkdownTextManagerInterface.java | 32 + .../ComponentDescriptors.cpp | 22 + .../ComponentDescriptors.h | 24 + .../EventEmitters.cpp | 314 ++ .../EnrichedMarkdownTextSpec/EventEmitters.h | 255 + .../EnrichedMarkdownTextSpec/Props.cpp | 315 ++ .../EnrichedMarkdownTextSpec/Props.h | 4662 +++++++++++++++++ .../EnrichedMarkdownTextSpec/ShadowNodes.cpp | 17 + .../EnrichedMarkdownTextSpec/ShadowNodes.h | 23 + .../EnrichedMarkdownTextSpec/States.cpp | 16 + .../EnrichedMarkdownTextSpec/States.h | 20 + .../ComponentDescriptors.cpp | 22 + .../ComponentDescriptors.h | 24 + .../EventEmitters.cpp | 314 ++ .../EnrichedMarkdownTextSpec/EventEmitters.h | 255 + .../EnrichedMarkdownTextSpec/Props.cpp | 315 ++ .../EnrichedMarkdownTextSpec/Props.h | 4662 +++++++++++++++++ .../RCTComponentViewHelpers.h | 299 ++ .../EnrichedMarkdownTextSpec/ShadowNodes.cpp | 17 + .../EnrichedMarkdownTextSpec/ShadowNodes.h | 23 + .../EnrichedMarkdownTextSpec/States.cpp | 16 + .../EnrichedMarkdownTextSpec/States.h | 20 + lib/module/EnrichedMarkdownInput.js | 258 + lib/module/EnrichedMarkdownInput.js.map | 1 + .../EnrichedMarkdownInputNativeComponent.ts | 256 + lib/module/EnrichedMarkdownNativeComponent.ts | 392 ++ .../EnrichedMarkdownTextNativeComponent.ts | 391 ++ lib/module/index.js | 5 + lib/module/index.js.map | 1 + lib/module/index.web.js | 4 + lib/module/index.web.js.map | 1 + lib/module/native/EnrichedMarkdownText.js | 158 + lib/module/native/EnrichedMarkdownText.js.map | 1 + lib/module/normalizeMarkdownInputStyle.js | 53 + lib/module/normalizeMarkdownInputStyle.js.map | 1 + lib/module/normalizeMarkdownStyle.js | 324 ++ lib/module/normalizeMarkdownStyle.js.map | 1 + lib/module/normalizeMarkdownStyle.web.js | 258 + lib/module/normalizeMarkdownStyle.web.js.map | 1 + lib/module/package.json | 1 + lib/module/plugin/withAndroidMath.js | 23 + lib/module/plugin/withAndroidMath.js.map | 1 + lib/module/plugin/withIosMath.js | 26 + lib/module/plugin/withIosMath.js.map | 1 + .../plugin/withReactNativeEnrichedMarkdown.js | 16 + .../withReactNativeEnrichedMarkdown.js.map | 1 + lib/module/styleUtils.js | 64 + lib/module/styleUtils.js.map | 1 + lib/module/types/MarkdownStyle.js | 2 + lib/module/types/MarkdownStyle.js.map | 1 + lib/module/types/MarkdownStyleInternal.js | 2 + lib/module/types/MarkdownStyleInternal.js.map | 1 + lib/module/types/MarkdownTextProps.js | 4 + lib/module/types/MarkdownTextProps.js.map | 1 + lib/module/types/MarkdownTextProps.web.js | 4 + lib/module/types/MarkdownTextProps.web.js.map | 1 + lib/module/types/events.js | 2 + lib/module/types/events.js.map | 1 + lib/module/utils/regexParser.js | 43 + lib/module/utils/regexParser.js.map | 1 + lib/module/web/EnrichedMarkdownText.js | 159 + lib/module/web/EnrichedMarkdownText.js.map | 1 + lib/module/web/katex.js | 22 + lib/module/web/katex.js.map | 1 + lib/module/web/parseMarkdown.js | 32 + lib/module/web/parseMarkdown.js.map | 1 + lib/module/web/renderers/BlockRenderers.js | 116 + .../web/renderers/BlockRenderers.js.map | 1 + lib/module/web/renderers/InlineRenderers.js | 214 + .../web/renderers/InlineRenderers.js.map | 1 + lib/module/web/renderers/KaTeXRenderer.js | 40 + lib/module/web/renderers/KaTeXRenderer.js.map | 1 + lib/module/web/renderers/ListRenderers.js | 92 + lib/module/web/renderers/ListRenderers.js.map | 1 + lib/module/web/renderers/TableRenderers.js | 77 + .../web/renderers/TableRenderers.js.map | 1 + lib/module/web/renderers/index.js | 49 + lib/module/web/renderers/index.js.map | 1 + lib/module/web/styles.js | 440 ++ lib/module/web/styles.js.map | 1 + lib/module/web/types.js | 4 + lib/module/web/types.js.map | 1 + lib/module/web/utils.js | 56 + lib/module/web/utils.js.map | 1 + lib/module/web/wasm/md4c.d.js | 4 + lib/module/web/wasm/md4c.d.js.map | 1 + lib/module/web/wasm/md4c.js | Bin 0 -> 124143 bytes lib/module/web/wasm/md4c.js.map | 1 + lib/typescript/package.json | 1 + lib/typescript/src/EnrichedMarkdownInput.d.ts | 109 + .../src/EnrichedMarkdownInput.d.ts.map | 1 + .../EnrichedMarkdownInputNativeComponent.d.ts | 214 + ...ichedMarkdownInputNativeComponent.d.ts.map | 1 + .../src/EnrichedMarkdownNativeComponent.d.ts | 350 ++ .../EnrichedMarkdownNativeComponent.d.ts.map | 1 + .../EnrichedMarkdownTextNativeComponent.d.ts | 350 ++ ...richedMarkdownTextNativeComponent.d.ts.map | 1 + lib/typescript/src/__tests__/index.test.d.ts | 1 + .../src/__tests__/index.test.d.ts.map | 1 + lib/typescript/src/index.d.ts | 6 + lib/typescript/src/index.d.ts.map | 1 + lib/typescript/src/index.web.d.ts | 5 + lib/typescript/src/index.web.d.ts.map | 1 + .../src/native/EnrichedMarkdownText.d.ts | 9 + .../src/native/EnrichedMarkdownText.d.ts.map | 1 + .../src/normalizeMarkdownInputStyle.d.ts | 21 + .../src/normalizeMarkdownInputStyle.d.ts.map | 1 + .../src/normalizeMarkdownStyle.d.ts | 4 + .../src/normalizeMarkdownStyle.d.ts.map | 1 + .../src/normalizeMarkdownStyle.web.d.ts | 4 + .../src/normalizeMarkdownStyle.web.d.ts.map | 1 + .../src/plugin/withAndroidMath.d.ts | 5 + .../src/plugin/withAndroidMath.d.ts.map | 1 + lib/typescript/src/plugin/withIosMath.d.ts | 5 + .../src/plugin/withIosMath.d.ts.map | 1 + .../withReactNativeEnrichedMarkdown.d.ts | 6 + .../withReactNativeEnrichedMarkdown.d.ts.map | 1 + lib/typescript/src/styleUtils.d.ts | 6 + lib/typescript/src/styleUtils.d.ts.map | 1 + lib/typescript/src/types/MarkdownStyle.d.ts | 271 + .../src/types/MarkdownStyle.d.ts.map | 1 + .../src/types/MarkdownStyleInternal.d.ts | 182 + .../src/types/MarkdownStyleInternal.d.ts.map | 1 + .../src/types/MarkdownTextProps.d.ts | 189 + .../src/types/MarkdownTextProps.d.ts.map | 1 + .../src/types/MarkdownTextProps.web.d.ts | 96 + .../src/types/MarkdownTextProps.web.d.ts.map | 1 + lib/typescript/src/types/events.d.ts | 37 + lib/typescript/src/types/events.d.ts.map | 1 + lib/typescript/src/utils/regexParser.d.ts | 3 + lib/typescript/src/utils/regexParser.d.ts.map | 1 + .../src/web/EnrichedMarkdownText.d.ts | 4 + .../src/web/EnrichedMarkdownText.d.ts.map | 1 + lib/typescript/src/web/katex.d.ts | 11 + lib/typescript/src/web/katex.d.ts.map | 1 + lib/typescript/src/web/parseMarkdown.d.ts | 4 + lib/typescript/src/web/parseMarkdown.d.ts.map | 1 + .../src/web/renderers/BlockRenderers.d.ts | 3 + .../src/web/renderers/BlockRenderers.d.ts.map | 1 + .../src/web/renderers/InlineRenderers.d.ts | 4 + .../web/renderers/InlineRenderers.d.ts.map | 1 + .../src/web/renderers/KaTeXRenderer.d.ts | 12 + .../src/web/renderers/KaTeXRenderer.d.ts.map | 1 + .../src/web/renderers/ListRenderers.d.ts | 3 + .../src/web/renderers/ListRenderers.d.ts.map | 1 + .../src/web/renderers/TableRenderers.d.ts | 3 + .../src/web/renderers/TableRenderers.d.ts.map | 1 + lib/typescript/src/web/renderers/index.d.ts | 14 + .../src/web/renderers/index.d.ts.map | 1 + lib/typescript/src/web/styles.d.ts | 48 + lib/typescript/src/web/styles.d.ts.map | 1 + lib/typescript/src/web/types.d.ts | 53 + lib/typescript/src/web/types.d.ts.map | 1 + lib/typescript/src/web/utils.d.ts | 21 + lib/typescript/src/web/utils.d.ts.map | 1 + 163 files changed, 17749 insertions(+), 7 deletions(-) create mode 120000 ReactNativeEnrichedMarkdown create mode 100644 android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownInputManagerDelegate.java create mode 100644 android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownInputManagerInterface.java create mode 100644 android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java create mode 100644 android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java create mode 100644 android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java create mode 100644 android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java create mode 100644 android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp create mode 100644 android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h create mode 100644 android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp create mode 100644 android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h create mode 100644 android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp create mode 100644 android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h create mode 100644 android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp create mode 100644 android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h create mode 100644 android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp create mode 100644 android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ComponentDescriptors.h create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ShadowNodes.cpp create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ShadowNodes.h create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/States.cpp create mode 100644 ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/States.h create mode 100644 lib/module/EnrichedMarkdownInput.js create mode 100644 lib/module/EnrichedMarkdownInput.js.map create mode 100644 lib/module/EnrichedMarkdownInputNativeComponent.ts create mode 100644 lib/module/EnrichedMarkdownNativeComponent.ts create mode 100644 lib/module/EnrichedMarkdownTextNativeComponent.ts create mode 100644 lib/module/index.js create mode 100644 lib/module/index.js.map create mode 100644 lib/module/index.web.js create mode 100644 lib/module/index.web.js.map create mode 100644 lib/module/native/EnrichedMarkdownText.js create mode 100644 lib/module/native/EnrichedMarkdownText.js.map create mode 100644 lib/module/normalizeMarkdownInputStyle.js create mode 100644 lib/module/normalizeMarkdownInputStyle.js.map create mode 100644 lib/module/normalizeMarkdownStyle.js create mode 100644 lib/module/normalizeMarkdownStyle.js.map create mode 100644 lib/module/normalizeMarkdownStyle.web.js create mode 100644 lib/module/normalizeMarkdownStyle.web.js.map create mode 100644 lib/module/package.json create mode 100644 lib/module/plugin/withAndroidMath.js create mode 100644 lib/module/plugin/withAndroidMath.js.map create mode 100644 lib/module/plugin/withIosMath.js create mode 100644 lib/module/plugin/withIosMath.js.map create mode 100644 lib/module/plugin/withReactNativeEnrichedMarkdown.js create mode 100644 lib/module/plugin/withReactNativeEnrichedMarkdown.js.map create mode 100644 lib/module/styleUtils.js create mode 100644 lib/module/styleUtils.js.map create mode 100644 lib/module/types/MarkdownStyle.js create mode 100644 lib/module/types/MarkdownStyle.js.map create mode 100644 lib/module/types/MarkdownStyleInternal.js create mode 100644 lib/module/types/MarkdownStyleInternal.js.map create mode 100644 lib/module/types/MarkdownTextProps.js create mode 100644 lib/module/types/MarkdownTextProps.js.map create mode 100644 lib/module/types/MarkdownTextProps.web.js create mode 100644 lib/module/types/MarkdownTextProps.web.js.map create mode 100644 lib/module/types/events.js create mode 100644 lib/module/types/events.js.map create mode 100644 lib/module/utils/regexParser.js create mode 100644 lib/module/utils/regexParser.js.map create mode 100644 lib/module/web/EnrichedMarkdownText.js create mode 100644 lib/module/web/EnrichedMarkdownText.js.map create mode 100644 lib/module/web/katex.js create mode 100644 lib/module/web/katex.js.map create mode 100644 lib/module/web/parseMarkdown.js create mode 100644 lib/module/web/parseMarkdown.js.map create mode 100644 lib/module/web/renderers/BlockRenderers.js create mode 100644 lib/module/web/renderers/BlockRenderers.js.map create mode 100644 lib/module/web/renderers/InlineRenderers.js create mode 100644 lib/module/web/renderers/InlineRenderers.js.map create mode 100644 lib/module/web/renderers/KaTeXRenderer.js create mode 100644 lib/module/web/renderers/KaTeXRenderer.js.map create mode 100644 lib/module/web/renderers/ListRenderers.js create mode 100644 lib/module/web/renderers/ListRenderers.js.map create mode 100644 lib/module/web/renderers/TableRenderers.js create mode 100644 lib/module/web/renderers/TableRenderers.js.map create mode 100644 lib/module/web/renderers/index.js create mode 100644 lib/module/web/renderers/index.js.map create mode 100644 lib/module/web/styles.js create mode 100644 lib/module/web/styles.js.map create mode 100644 lib/module/web/types.js create mode 100644 lib/module/web/types.js.map create mode 100644 lib/module/web/utils.js create mode 100644 lib/module/web/utils.js.map create mode 100644 lib/module/web/wasm/md4c.d.js create mode 100644 lib/module/web/wasm/md4c.d.js.map create mode 100644 lib/module/web/wasm/md4c.js create mode 100644 lib/module/web/wasm/md4c.js.map create mode 100644 lib/typescript/package.json create mode 100644 lib/typescript/src/EnrichedMarkdownInput.d.ts create mode 100644 lib/typescript/src/EnrichedMarkdownInput.d.ts.map create mode 100644 lib/typescript/src/EnrichedMarkdownInputNativeComponent.d.ts create mode 100644 lib/typescript/src/EnrichedMarkdownInputNativeComponent.d.ts.map create mode 100644 lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts create mode 100644 lib/typescript/src/EnrichedMarkdownNativeComponent.d.ts.map create mode 100644 lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts create mode 100644 lib/typescript/src/EnrichedMarkdownTextNativeComponent.d.ts.map create mode 100644 lib/typescript/src/__tests__/index.test.d.ts create mode 100644 lib/typescript/src/__tests__/index.test.d.ts.map create mode 100644 lib/typescript/src/index.d.ts create mode 100644 lib/typescript/src/index.d.ts.map create mode 100644 lib/typescript/src/index.web.d.ts create mode 100644 lib/typescript/src/index.web.d.ts.map create mode 100644 lib/typescript/src/native/EnrichedMarkdownText.d.ts create mode 100644 lib/typescript/src/native/EnrichedMarkdownText.d.ts.map create mode 100644 lib/typescript/src/normalizeMarkdownInputStyle.d.ts create mode 100644 lib/typescript/src/normalizeMarkdownInputStyle.d.ts.map create mode 100644 lib/typescript/src/normalizeMarkdownStyle.d.ts create mode 100644 lib/typescript/src/normalizeMarkdownStyle.d.ts.map create mode 100644 lib/typescript/src/normalizeMarkdownStyle.web.d.ts create mode 100644 lib/typescript/src/normalizeMarkdownStyle.web.d.ts.map create mode 100644 lib/typescript/src/plugin/withAndroidMath.d.ts create mode 100644 lib/typescript/src/plugin/withAndroidMath.d.ts.map create mode 100644 lib/typescript/src/plugin/withIosMath.d.ts create mode 100644 lib/typescript/src/plugin/withIosMath.d.ts.map create mode 100644 lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts create mode 100644 lib/typescript/src/plugin/withReactNativeEnrichedMarkdown.d.ts.map create mode 100644 lib/typescript/src/styleUtils.d.ts create mode 100644 lib/typescript/src/styleUtils.d.ts.map create mode 100644 lib/typescript/src/types/MarkdownStyle.d.ts create mode 100644 lib/typescript/src/types/MarkdownStyle.d.ts.map create mode 100644 lib/typescript/src/types/MarkdownStyleInternal.d.ts create mode 100644 lib/typescript/src/types/MarkdownStyleInternal.d.ts.map create mode 100644 lib/typescript/src/types/MarkdownTextProps.d.ts create mode 100644 lib/typescript/src/types/MarkdownTextProps.d.ts.map create mode 100644 lib/typescript/src/types/MarkdownTextProps.web.d.ts create mode 100644 lib/typescript/src/types/MarkdownTextProps.web.d.ts.map create mode 100644 lib/typescript/src/types/events.d.ts create mode 100644 lib/typescript/src/types/events.d.ts.map create mode 100644 lib/typescript/src/utils/regexParser.d.ts create mode 100644 lib/typescript/src/utils/regexParser.d.ts.map create mode 100644 lib/typescript/src/web/EnrichedMarkdownText.d.ts create mode 100644 lib/typescript/src/web/EnrichedMarkdownText.d.ts.map create mode 100644 lib/typescript/src/web/katex.d.ts create mode 100644 lib/typescript/src/web/katex.d.ts.map create mode 100644 lib/typescript/src/web/parseMarkdown.d.ts create mode 100644 lib/typescript/src/web/parseMarkdown.d.ts.map create mode 100644 lib/typescript/src/web/renderers/BlockRenderers.d.ts create mode 100644 lib/typescript/src/web/renderers/BlockRenderers.d.ts.map create mode 100644 lib/typescript/src/web/renderers/InlineRenderers.d.ts create mode 100644 lib/typescript/src/web/renderers/InlineRenderers.d.ts.map create mode 100644 lib/typescript/src/web/renderers/KaTeXRenderer.d.ts create mode 100644 lib/typescript/src/web/renderers/KaTeXRenderer.d.ts.map create mode 100644 lib/typescript/src/web/renderers/ListRenderers.d.ts create mode 100644 lib/typescript/src/web/renderers/ListRenderers.d.ts.map create mode 100644 lib/typescript/src/web/renderers/TableRenderers.d.ts create mode 100644 lib/typescript/src/web/renderers/TableRenderers.d.ts.map create mode 100644 lib/typescript/src/web/renderers/index.d.ts create mode 100644 lib/typescript/src/web/renderers/index.d.ts.map create mode 100644 lib/typescript/src/web/styles.d.ts create mode 100644 lib/typescript/src/web/styles.d.ts.map create mode 100644 lib/typescript/src/web/types.d.ts create mode 100644 lib/typescript/src/web/types.d.ts.map create mode 100644 lib/typescript/src/web/utils.d.ts create mode 100644 lib/typescript/src/web/utils.d.ts.map diff --git a/.gitignore b/.gitignore index 16a95637..e290c38f 100644 --- a/.gitignore +++ b/.gitignore @@ -79,12 +79,11 @@ web-build/ # Turborepo .turbo/ -# generated by bob -lib/ - -# React Native Codegen -ios/generated -android/generated +# Bob + RN codegen: leave these lines commented so `lib/`, `ios/generated`, and +# `android/generated` stay in git (consumers with --ignore-scripts need them). +# lib/ +# ios/generated +# android/generated # React Native Nitro Modules nitrogen/ diff --git a/ReactNativeEnrichedMarkdown b/ReactNativeEnrichedMarkdown new file mode 120000 index 00000000..00760609 --- /dev/null +++ b/ReactNativeEnrichedMarkdown @@ -0,0 +1 @@ +ReactCodegen/EnrichedMarkdownTextSpec \ No newline at end of file diff --git a/ReactNativeEnrichedMarkdown.podspec b/ReactNativeEnrichedMarkdown.podspec index 86bcc2af..ac2fc5c6 100644 --- a/ReactNativeEnrichedMarkdown.podspec +++ b/ReactNativeEnrichedMarkdown.podspec @@ -29,7 +29,7 @@ Pod::Spec.new do |s| # ios folder that contains headers so renderer/ utils/ attachments/ etc. cross-imports resolve. ios_header_paths = %w[ ios ios/attachments ios/input ios/input/internals ios/input/styles ios/internals ios/parser - ios/renderer ios/styles ios/utils ios/views + ios/renderer ios/styles ios/utils ios/views ios/generated ].map { |p| "\"$(PODS_TARGET_SRCROOT)/#{p}\"" }.join(' ') s.pod_target_xcconfig = { diff --git a/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownInputManagerDelegate.java b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownInputManagerDelegate.java new file mode 100644 index 00000000..3cce2883 --- /dev/null +++ b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownInputManagerDelegate.java @@ -0,0 +1,138 @@ +/** +* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). +* +* Do not edit this file as changes may cause incorrect behavior and will be lost +* once the code is regenerated. +* +* @generated by codegen project: GeneratePropsJavaDelegate.js +*/ + +package com.facebook.react.viewmanagers; + +import android.view.View; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.ColorPropConverter; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.BaseViewManager; +import com.facebook.react.uimanager.BaseViewManagerDelegate; +import com.facebook.react.uimanager.LayoutShadowNode; + +@SuppressWarnings("deprecation") +public class EnrichedMarkdownInputManagerDelegate & EnrichedMarkdownInputManagerInterface> extends BaseViewManagerDelegate { + public EnrichedMarkdownInputManagerDelegate(U viewManager) { + super(viewManager); + } + @Override + public void setProperty(T view, String propName, @Nullable Object value) { + switch (propName) { + case "defaultValue": + mViewManager.setDefaultValue(view, value == null ? null : (String) value); + break; + case "placeholder": + mViewManager.setPlaceholder(view, value == null ? null : (String) value); + break; + case "placeholderTextColor": + mViewManager.setPlaceholderTextColor(view, ColorPropConverter.getColor(value, view.getContext())); + break; + case "editable": + mViewManager.setEditable(view, value == null ? false : (boolean) value); + break; + case "autoFocus": + mViewManager.setAutoFocus(view, value == null ? false : (boolean) value); + break; + case "scrollEnabled": + mViewManager.setScrollEnabled(view, value == null ? false : (boolean) value); + break; + case "autoCapitalize": + mViewManager.setAutoCapitalize(view, value == null ? null : (String) value); + break; + case "multiline": + mViewManager.setMultiline(view, value == null ? false : (boolean) value); + break; + case "cursorColor": + mViewManager.setCursorColor(view, ColorPropConverter.getColor(value, view.getContext())); + break; + case "selectionColor": + mViewManager.setSelectionColor(view, ColorPropConverter.getColor(value, view.getContext())); + break; + case "markdownStyle": + mViewManager.setMarkdownStyle(view, (ReadableMap) value); + break; + case "color": + mViewManager.setColor(view, ColorPropConverter.getColor(value, view.getContext())); + break; + case "fontSize": + mViewManager.setFontSize(view, value == null ? 0f : ((Double) value).floatValue()); + break; + case "lineHeight": + mViewManager.setLineHeight(view, value == null ? 0f : ((Double) value).floatValue()); + break; + case "fontFamily": + mViewManager.setFontFamily(view, value == null ? null : (String) value); + break; + case "fontWeight": + mViewManager.setFontWeight(view, value == null ? null : (String) value); + break; + case "isOnChangeMarkdownSet": + mViewManager.setIsOnChangeMarkdownSet(view, value == null ? false : (boolean) value); + break; + case "contextMenuItems": + mViewManager.setContextMenuItems(view, (ReadableArray) value); + break; + case "linkRegex": + mViewManager.setLinkRegex(view, (ReadableMap) value); + break; + default: + super.setProperty(view, propName, value); + } + } + + @Override + public void receiveCommand(T view, String commandName, ReadableArray args) { + switch (commandName) { + case "focus": + mViewManager.focus(view); + break; + case "blur": + mViewManager.blur(view); + break; + case "setValue": + mViewManager.setValue(view, args.getString(0)); + break; + case "setSelection": + mViewManager.setSelection(view, args.getInt(0), args.getInt(1)); + break; + case "toggleBold": + mViewManager.toggleBold(view); + break; + case "toggleItalic": + mViewManager.toggleItalic(view); + break; + case "toggleUnderline": + mViewManager.toggleUnderline(view); + break; + case "toggleStrikethrough": + mViewManager.toggleStrikethrough(view); + break; + case "toggleSpoiler": + mViewManager.toggleSpoiler(view); + break; + case "setLink": + mViewManager.setLink(view, args.getString(0)); + break; + case "insertLink": + mViewManager.insertLink(view, args.getString(0), args.getString(1)); + break; + case "removeLink": + mViewManager.removeLink(view); + break; + case "requestMarkdown": + mViewManager.requestMarkdown(view, args.getInt(0)); + break; + case "requestCaretRect": + mViewManager.requestCaretRect(view, args.getInt(0)); + break; + } + } +} diff --git a/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownInputManagerInterface.java b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownInputManagerInterface.java new file mode 100644 index 00000000..c7082202 --- /dev/null +++ b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownInputManagerInterface.java @@ -0,0 +1,52 @@ +/** +* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). +* +* Do not edit this file as changes may cause incorrect behavior and will be lost +* once the code is regenerated. +* +* @generated by codegen project: GeneratePropsJavaInterface.js +*/ + +package com.facebook.react.viewmanagers; + +import android.view.View; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface; + +public interface EnrichedMarkdownInputManagerInterface extends ViewManagerWithGeneratedInterface { + void setDefaultValue(T view, @Nullable String value); + void setPlaceholder(T view, @Nullable String value); + void setPlaceholderTextColor(T view, @Nullable Integer value); + void setEditable(T view, boolean value); + void setAutoFocus(T view, boolean value); + void setScrollEnabled(T view, boolean value); + void setAutoCapitalize(T view, @Nullable String value); + void setMultiline(T view, boolean value); + void setCursorColor(T view, @Nullable Integer value); + void setSelectionColor(T view, @Nullable Integer value); + void setMarkdownStyle(T view, @Nullable ReadableMap value); + void setColor(T view, @Nullable Integer value); + void setFontSize(T view, float value); + void setLineHeight(T view, float value); + void setFontFamily(T view, @Nullable String value); + void setFontWeight(T view, @Nullable String value); + void setIsOnChangeMarkdownSet(T view, boolean value); + void setContextMenuItems(T view, @Nullable ReadableArray value); + void setLinkRegex(T view, @Nullable ReadableMap value); + void focus(T view); + void blur(T view); + void setValue(T view, String markdown); + void setSelection(T view, int start, int end); + void toggleBold(T view); + void toggleItalic(T view); + void toggleUnderline(T view); + void toggleStrikethrough(T view); + void toggleSpoiler(T view); + void setLink(T view, String url); + void insertLink(T view, String text, String url); + void removeLink(T view); + void requestMarkdown(T view, int requestId); + void requestCaretRect(T view, int requestId); +} diff --git a/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java new file mode 100644 index 00000000..86b5312d --- /dev/null +++ b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerDelegate.java @@ -0,0 +1,72 @@ +/** +* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). +* +* Do not edit this file as changes may cause incorrect behavior and will be lost +* once the code is regenerated. +* +* @generated by codegen project: GeneratePropsJavaDelegate.js +*/ + +package com.facebook.react.viewmanagers; + +import android.view.View; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.ColorPropConverter; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.BaseViewManager; +import com.facebook.react.uimanager.BaseViewManagerDelegate; +import com.facebook.react.uimanager.LayoutShadowNode; + +@SuppressWarnings("deprecation") +public class EnrichedMarkdownManagerDelegate & EnrichedMarkdownManagerInterface> extends BaseViewManagerDelegate { + public EnrichedMarkdownManagerDelegate(U viewManager) { + super(viewManager); + } + @Override + public void setProperty(T view, String propName, @Nullable Object value) { + switch (propName) { + case "markdown": + mViewManager.setMarkdown(view, value == null ? null : (String) value); + break; + case "markdownStyle": + mViewManager.setMarkdownStyle(view, (ReadableMap) value); + break; + case "enableLinkPreview": + mViewManager.setEnableLinkPreview(view, value == null ? true : (boolean) value); + break; + case "selectable": + mViewManager.setSelectable(view, value == null ? false : (boolean) value); + break; + case "selectionColor": + mViewManager.setSelectionColor(view, ColorPropConverter.getColor(value, view.getContext())); + break; + case "selectionHandleColor": + mViewManager.setSelectionHandleColor(view, ColorPropConverter.getColor(value, view.getContext())); + break; + case "md4cFlags": + mViewManager.setMd4cFlags(view, (ReadableMap) value); + break; + case "allowFontScaling": + mViewManager.setAllowFontScaling(view, value == null ? true : (boolean) value); + break; + case "maxFontSizeMultiplier": + mViewManager.setMaxFontSizeMultiplier(view, value == null ? 0f : ((Double) value).floatValue()); + break; + case "allowTrailingMargin": + mViewManager.setAllowTrailingMargin(view, value == null ? false : (boolean) value); + break; + case "streamingAnimation": + mViewManager.setStreamingAnimation(view, value == null ? false : (boolean) value); + break; + case "spoilerOverlay": + mViewManager.setSpoilerOverlay(view, value == null ? "particles" : (String) value); + break; + case "contextMenuItems": + mViewManager.setContextMenuItems(view, (ReadableArray) value); + break; + default: + super.setProperty(view, propName, value); + } + } +} diff --git a/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java new file mode 100644 index 00000000..435b5662 --- /dev/null +++ b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownManagerInterface.java @@ -0,0 +1,32 @@ +/** +* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). +* +* Do not edit this file as changes may cause incorrect behavior and will be lost +* once the code is regenerated. +* +* @generated by codegen project: GeneratePropsJavaInterface.js +*/ + +package com.facebook.react.viewmanagers; + +import android.view.View; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface; + +public interface EnrichedMarkdownManagerInterface extends ViewManagerWithGeneratedInterface { + void setMarkdown(T view, @Nullable String value); + void setMarkdownStyle(T view, @Nullable ReadableMap value); + void setEnableLinkPreview(T view, boolean value); + void setSelectable(T view, boolean value); + void setSelectionColor(T view, @Nullable Integer value); + void setSelectionHandleColor(T view, @Nullable Integer value); + void setMd4cFlags(T view, @Nullable ReadableMap value); + void setAllowFontScaling(T view, boolean value); + void setMaxFontSizeMultiplier(T view, float value); + void setAllowTrailingMargin(T view, boolean value); + void setStreamingAnimation(T view, boolean value); + void setSpoilerOverlay(T view, @Nullable String value); + void setContextMenuItems(T view, @Nullable ReadableArray value); +} diff --git a/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java new file mode 100644 index 00000000..3fbf5476 --- /dev/null +++ b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerDelegate.java @@ -0,0 +1,72 @@ +/** +* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). +* +* Do not edit this file as changes may cause incorrect behavior and will be lost +* once the code is regenerated. +* +* @generated by codegen project: GeneratePropsJavaDelegate.js +*/ + +package com.facebook.react.viewmanagers; + +import android.view.View; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.ColorPropConverter; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.BaseViewManager; +import com.facebook.react.uimanager.BaseViewManagerDelegate; +import com.facebook.react.uimanager.LayoutShadowNode; + +@SuppressWarnings("deprecation") +public class EnrichedMarkdownTextManagerDelegate & EnrichedMarkdownTextManagerInterface> extends BaseViewManagerDelegate { + public EnrichedMarkdownTextManagerDelegate(U viewManager) { + super(viewManager); + } + @Override + public void setProperty(T view, String propName, @Nullable Object value) { + switch (propName) { + case "markdown": + mViewManager.setMarkdown(view, value == null ? null : (String) value); + break; + case "markdownStyle": + mViewManager.setMarkdownStyle(view, (ReadableMap) value); + break; + case "enableLinkPreview": + mViewManager.setEnableLinkPreview(view, value == null ? true : (boolean) value); + break; + case "selectable": + mViewManager.setSelectable(view, value == null ? false : (boolean) value); + break; + case "selectionColor": + mViewManager.setSelectionColor(view, ColorPropConverter.getColor(value, view.getContext())); + break; + case "selectionHandleColor": + mViewManager.setSelectionHandleColor(view, ColorPropConverter.getColor(value, view.getContext())); + break; + case "md4cFlags": + mViewManager.setMd4cFlags(view, (ReadableMap) value); + break; + case "allowFontScaling": + mViewManager.setAllowFontScaling(view, value == null ? true : (boolean) value); + break; + case "maxFontSizeMultiplier": + mViewManager.setMaxFontSizeMultiplier(view, value == null ? 0f : ((Double) value).floatValue()); + break; + case "allowTrailingMargin": + mViewManager.setAllowTrailingMargin(view, value == null ? false : (boolean) value); + break; + case "streamingAnimation": + mViewManager.setStreamingAnimation(view, value == null ? false : (boolean) value); + break; + case "spoilerOverlay": + mViewManager.setSpoilerOverlay(view, value == null ? "particles" : (String) value); + break; + case "contextMenuItems": + mViewManager.setContextMenuItems(view, (ReadableArray) value); + break; + default: + super.setProperty(view, propName, value); + } + } +} diff --git a/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java new file mode 100644 index 00000000..93b6ff7b --- /dev/null +++ b/android/generated/java/com/facebook/react/viewmanagers/EnrichedMarkdownTextManagerInterface.java @@ -0,0 +1,32 @@ +/** +* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). +* +* Do not edit this file as changes may cause incorrect behavior and will be lost +* once the code is regenerated. +* +* @generated by codegen project: GeneratePropsJavaInterface.js +*/ + +package com.facebook.react.viewmanagers; + +import android.view.View; +import androidx.annotation.Nullable; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.uimanager.ViewManagerWithGeneratedInterface; + +public interface EnrichedMarkdownTextManagerInterface extends ViewManagerWithGeneratedInterface { + void setMarkdown(T view, @Nullable String value); + void setMarkdownStyle(T view, @Nullable ReadableMap value); + void setEnableLinkPreview(T view, boolean value); + void setSelectable(T view, boolean value); + void setSelectionColor(T view, @Nullable Integer value); + void setSelectionHandleColor(T view, @Nullable Integer value); + void setMd4cFlags(T view, @Nullable ReadableMap value); + void setAllowFontScaling(T view, boolean value); + void setMaxFontSizeMultiplier(T view, float value); + void setAllowTrailingMargin(T view, boolean value); + void setStreamingAnimation(T view, boolean value); + void setSpoilerOverlay(T view, @Nullable String value); + void setContextMenuItems(T view, @Nullable ReadableArray value); +} diff --git a/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp new file mode 100644 index 00000000..4d59d519 --- /dev/null +++ b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp @@ -0,0 +1,22 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateComponentDescriptorCpp.js + */ + +#include "ComponentDescriptors.h" +#include +#include + +namespace facebook::react { + +void EnrichedMarkdownTextSpec_registerComponentDescriptorsFromCodegen( + std::shared_ptr registry) { + +} + +} // namespace facebook::react diff --git a/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h new file mode 100644 index 00000000..06510880 --- /dev/null +++ b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ComponentDescriptors.h @@ -0,0 +1,24 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateComponentDescriptorH.js + */ + +#pragma once + +#include "ShadowNodes.h" +#include +#include + +namespace facebook::react { + + + +void EnrichedMarkdownTextSpec_registerComponentDescriptorsFromCodegen( + std::shared_ptr registry); + +} // namespace facebook::react diff --git a/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp new file mode 100644 index 00000000..5fe6e87f --- /dev/null +++ b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.cpp @@ -0,0 +1,314 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateEventEmitterCpp.js + */ + +#include "EventEmitters.h" + + +namespace facebook::react { + +void EnrichedMarkdownInputEventEmitter::onChangeText(OnChangeText event) const { + dispatchEvent("changeText", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "value", event.value); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onChangeMarkdown(OnChangeMarkdown event) const { + dispatchEvent("changeMarkdown", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "value", event.value); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onChangeSelection(OnChangeSelection event) const { + dispatchEvent("changeSelection", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "start", event.start); +payload.setProperty(runtime, "end", event.end); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onChangeState(OnChangeState event) const { + dispatchEvent("changeState", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + { + auto bold = jsi::Object(runtime); + bold.setProperty(runtime, "isActive", event.bold.isActive); + payload.setProperty(runtime, "bold", bold); +} +{ + auto italic = jsi::Object(runtime); + italic.setProperty(runtime, "isActive", event.italic.isActive); + payload.setProperty(runtime, "italic", italic); +} +{ + auto underline = jsi::Object(runtime); + underline.setProperty(runtime, "isActive", event.underline.isActive); + payload.setProperty(runtime, "underline", underline); +} +{ + auto strikethrough = jsi::Object(runtime); + strikethrough.setProperty(runtime, "isActive", event.strikethrough.isActive); + payload.setProperty(runtime, "strikethrough", strikethrough); +} +{ + auto spoiler = jsi::Object(runtime); + spoiler.setProperty(runtime, "isActive", event.spoiler.isActive); + payload.setProperty(runtime, "spoiler", spoiler); +} +{ + auto link = jsi::Object(runtime); + link.setProperty(runtime, "isActive", event.link.isActive); + payload.setProperty(runtime, "link", link); +} + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onInputFocus(OnInputFocus event) const { + dispatchEvent("inputFocus", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onInputBlur(OnInputBlur event) const { + dispatchEvent("inputBlur", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onRequestMarkdownResult(OnRequestMarkdownResult event) const { + dispatchEvent("requestMarkdownResult", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "requestId", event.requestId); +payload.setProperty(runtime, "markdown", event.markdown); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onRequestCaretRectResult(OnRequestCaretRectResult event) const { + dispatchEvent("requestCaretRectResult", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "requestId", event.requestId); +payload.setProperty(runtime, "x", event.x); +payload.setProperty(runtime, "y", event.y); +payload.setProperty(runtime, "width", event.width); +payload.setProperty(runtime, "height", event.height); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onCaretRectChange(OnCaretRectChange event) const { + dispatchEvent("caretRectChange", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "x", event.x); +payload.setProperty(runtime, "y", event.y); +payload.setProperty(runtime, "width", event.width); +payload.setProperty(runtime, "height", event.height); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onContextMenuItemPress(OnContextMenuItemPress event) const { + dispatchEvent("contextMenuItemPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "itemText", event.itemText); +payload.setProperty(runtime, "selectedText", event.selectedText); +payload.setProperty(runtime, "selectionStart", event.selectionStart); +payload.setProperty(runtime, "selectionEnd", event.selectionEnd); +{ + auto styleState = jsi::Object(runtime); + { + auto bold = jsi::Object(runtime); + bold.setProperty(runtime, "isActive", event.styleState.bold.isActive); + styleState.setProperty(runtime, "bold", bold); + } + { + auto italic = jsi::Object(runtime); + italic.setProperty(runtime, "isActive", event.styleState.italic.isActive); + styleState.setProperty(runtime, "italic", italic); + } + { + auto underline = jsi::Object(runtime); + underline.setProperty(runtime, "isActive", event.styleState.underline.isActive); + styleState.setProperty(runtime, "underline", underline); + } + { + auto strikethrough = jsi::Object(runtime); + strikethrough.setProperty(runtime, "isActive", event.styleState.strikethrough.isActive); + styleState.setProperty(runtime, "strikethrough", strikethrough); + } + { + auto spoiler = jsi::Object(runtime); + spoiler.setProperty(runtime, "isActive", event.styleState.spoiler.isActive); + styleState.setProperty(runtime, "spoiler", spoiler); + } + { + auto link = jsi::Object(runtime); + link.setProperty(runtime, "isActive", event.styleState.link.isActive); + styleState.setProperty(runtime, "link", link); + } + payload.setProperty(runtime, "styleState", styleState); +} + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onLinkDetected(OnLinkDetected event) const { + dispatchEvent("linkDetected", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "text", event.text); +payload.setProperty(runtime, "url", event.url); +payload.setProperty(runtime, "start", event.start); +payload.setProperty(runtime, "end", event.end); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onLinkPress(OnLinkPress event) const { + dispatchEvent("linkPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onLinkLongPress(OnLinkLongPress event) const { + dispatchEvent("linkLongPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onTaskListItemPress(OnTaskListItemPress event) const { + dispatchEvent("taskListItemPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "index", event.index); +payload.setProperty(runtime, "checked", event.checked); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onMentionPress(OnMentionPress event) const { + dispatchEvent("mentionPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onCitationPress(OnCitationPress event) const { + dispatchEvent("citationPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onContextMenuItemPress(OnContextMenuItemPress event) const { + dispatchEvent("contextMenuItemPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "itemText", event.itemText); +payload.setProperty(runtime, "selectedText", event.selectedText); +payload.setProperty(runtime, "selectionStart", event.selectionStart); +payload.setProperty(runtime, "selectionEnd", event.selectionEnd); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onLinkPress(OnLinkPress event) const { + dispatchEvent("linkPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onLinkLongPress(OnLinkLongPress event) const { + dispatchEvent("linkLongPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onTaskListItemPress(OnTaskListItemPress event) const { + dispatchEvent("taskListItemPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "index", event.index); +payload.setProperty(runtime, "checked", event.checked); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onMentionPress(OnMentionPress event) const { + dispatchEvent("mentionPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onCitationPress(OnCitationPress event) const { + dispatchEvent("citationPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onContextMenuItemPress(OnContextMenuItemPress event) const { + dispatchEvent("contextMenuItemPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "itemText", event.itemText); +payload.setProperty(runtime, "selectedText", event.selectedText); +payload.setProperty(runtime, "selectionStart", event.selectionStart); +payload.setProperty(runtime, "selectionEnd", event.selectionEnd); + return payload; + }); +} + +} // namespace facebook::react diff --git a/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h new file mode 100644 index 00000000..8896a9bd --- /dev/null +++ b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/EventEmitters.h @@ -0,0 +1,255 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateEventEmitterH.js + */ +#pragma once + +#include + + +namespace facebook::react { +class EnrichedMarkdownInputEventEmitter : public ViewEventEmitter { + public: + using ViewEventEmitter::ViewEventEmitter; + + struct OnChangeText { + std::string value; + }; + + struct OnChangeMarkdown { + std::string value; + }; + + struct OnChangeSelection { + int start; + int end; + }; + + struct OnChangeStateBold { + bool isActive; + }; + + struct OnChangeStateItalic { + bool isActive; + }; + + struct OnChangeStateUnderline { + bool isActive; + }; + + struct OnChangeStateStrikethrough { + bool isActive; + }; + + struct OnChangeStateSpoiler { + bool isActive; + }; + + struct OnChangeStateLink { + bool isActive; + }; + + struct OnChangeState { + OnChangeStateBold bold; + OnChangeStateItalic italic; + OnChangeStateUnderline underline; + OnChangeStateStrikethrough strikethrough; + OnChangeStateSpoiler spoiler; + OnChangeStateLink link; + }; + + struct OnInputFocus { + int target; + }; + + struct OnInputBlur { + int target; + }; + + struct OnRequestMarkdownResult { + int requestId; + std::string markdown; + }; + + struct OnRequestCaretRectResult { + int requestId; + double x; + double y; + double width; + double height; + }; + + struct OnCaretRectChange { + double x; + double y; + double width; + double height; + }; + + struct OnContextMenuItemPressStyleStateBold { + bool isActive; + }; + + struct OnContextMenuItemPressStyleStateItalic { + bool isActive; + }; + + struct OnContextMenuItemPressStyleStateUnderline { + bool isActive; + }; + + struct OnContextMenuItemPressStyleStateStrikethrough { + bool isActive; + }; + + struct OnContextMenuItemPressStyleStateSpoiler { + bool isActive; + }; + + struct OnContextMenuItemPressStyleStateLink { + bool isActive; + }; + + struct OnContextMenuItemPressStyleState { + OnContextMenuItemPressStyleStateBold bold; + OnContextMenuItemPressStyleStateItalic italic; + OnContextMenuItemPressStyleStateUnderline underline; + OnContextMenuItemPressStyleStateStrikethrough strikethrough; + OnContextMenuItemPressStyleStateSpoiler spoiler; + OnContextMenuItemPressStyleStateLink link; + }; + + struct OnContextMenuItemPress { + std::string itemText; + std::string selectedText; + int selectionStart; + int selectionEnd; + OnContextMenuItemPressStyleState styleState; + }; + + struct OnLinkDetected { + std::string text; + std::string url; + int start; + int end; + }; + void onChangeText(OnChangeText value) const; + + void onChangeMarkdown(OnChangeMarkdown value) const; + + void onChangeSelection(OnChangeSelection value) const; + + void onChangeState(OnChangeState value) const; + + void onInputFocus(OnInputFocus value) const; + + void onInputBlur(OnInputBlur value) const; + + void onRequestMarkdownResult(OnRequestMarkdownResult value) const; + + void onRequestCaretRectResult(OnRequestCaretRectResult value) const; + + void onCaretRectChange(OnCaretRectChange value) const; + + void onContextMenuItemPress(OnContextMenuItemPress value) const; + + void onLinkDetected(OnLinkDetected value) const; +}; +class EnrichedMarkdownEventEmitter : public ViewEventEmitter { + public: + using ViewEventEmitter::ViewEventEmitter; + + struct OnLinkPress { + std::string url; + }; + + struct OnLinkLongPress { + std::string url; + }; + + struct OnTaskListItemPress { + int index; + bool checked; + std::string text; + }; + + struct OnMentionPress { + std::string url; + std::string text; + }; + + struct OnCitationPress { + std::string url; + std::string text; + }; + + struct OnContextMenuItemPress { + std::string itemText; + std::string selectedText; + int selectionStart; + int selectionEnd; + }; + void onLinkPress(OnLinkPress value) const; + + void onLinkLongPress(OnLinkLongPress value) const; + + void onTaskListItemPress(OnTaskListItemPress value) const; + + void onMentionPress(OnMentionPress value) const; + + void onCitationPress(OnCitationPress value) const; + + void onContextMenuItemPress(OnContextMenuItemPress value) const; +}; +class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter { + public: + using ViewEventEmitter::ViewEventEmitter; + + struct OnLinkPress { + std::string url; + }; + + struct OnLinkLongPress { + std::string url; + }; + + struct OnTaskListItemPress { + int index; + bool checked; + std::string text; + }; + + struct OnMentionPress { + std::string url; + std::string text; + }; + + struct OnCitationPress { + std::string url; + std::string text; + }; + + struct OnContextMenuItemPress { + std::string itemText; + std::string selectedText; + int selectionStart; + int selectionEnd; + }; + void onLinkPress(OnLinkPress value) const; + + void onLinkLongPress(OnLinkLongPress value) const; + + void onTaskListItemPress(OnTaskListItemPress value) const; + + void onMentionPress(OnMentionPress value) const; + + void onCitationPress(OnCitationPress value) const; + + void onContextMenuItemPress(OnContextMenuItemPress value) const; +}; +} // namespace facebook::react diff --git a/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp new file mode 100644 index 00000000..bed5a95d --- /dev/null +++ b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.cpp @@ -0,0 +1,315 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GeneratePropsCpp.js + */ + +#include "Props.h" +#include +#include + +namespace facebook::react { + +EnrichedMarkdownInputProps::EnrichedMarkdownInputProps( + const PropsParserContext &context, + const EnrichedMarkdownInputProps &sourceProps, + const RawProps &rawProps): ViewProps(context, sourceProps, rawProps), + + defaultValue(convertRawProp(context, rawProps, "defaultValue", sourceProps.defaultValue, {})), + placeholder(convertRawProp(context, rawProps, "placeholder", sourceProps.placeholder, {})), + placeholderTextColor(convertRawProp(context, rawProps, "placeholderTextColor", sourceProps.placeholderTextColor, {})), + editable(convertRawProp(context, rawProps, "editable", sourceProps.editable, {false})), + autoFocus(convertRawProp(context, rawProps, "autoFocus", sourceProps.autoFocus, {false})), + scrollEnabled(convertRawProp(context, rawProps, "scrollEnabled", sourceProps.scrollEnabled, {false})), + autoCapitalize(convertRawProp(context, rawProps, "autoCapitalize", sourceProps.autoCapitalize, {})), + multiline(convertRawProp(context, rawProps, "multiline", sourceProps.multiline, {false})), + cursorColor(convertRawProp(context, rawProps, "cursorColor", sourceProps.cursorColor, {})), + selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})), + markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})), + color(convertRawProp(context, rawProps, "color", sourceProps.color, {})), + fontSize(convertRawProp(context, rawProps, "fontSize", sourceProps.fontSize, {0.0})), + lineHeight(convertRawProp(context, rawProps, "lineHeight", sourceProps.lineHeight, {0.0})), + fontFamily(convertRawProp(context, rawProps, "fontFamily", sourceProps.fontFamily, {})), + fontWeight(convertRawProp(context, rawProps, "fontWeight", sourceProps.fontWeight, {})), + isOnChangeMarkdownSet(convertRawProp(context, rawProps, "isOnChangeMarkdownSet", sourceProps.isOnChangeMarkdownSet, {false})), + contextMenuItems(convertRawProp(context, rawProps, "contextMenuItems", sourceProps.contextMenuItems, {})), + linkRegex(convertRawProp(context, rawProps, "linkRegex", sourceProps.linkRegex, {})) {} + +#ifdef RN_SERIALIZABLE_STATE +ComponentName EnrichedMarkdownInputProps::getDiffPropsImplementationTarget() const { + return "EnrichedMarkdownInput"; +} + +folly::dynamic EnrichedMarkdownInputProps::getDiffProps( + const Props* prevProps) const { + static const auto defaultProps = EnrichedMarkdownInputProps(); + const EnrichedMarkdownInputProps* oldProps = prevProps == nullptr + ? &defaultProps + : static_cast(prevProps); + if (this == oldProps) { + return folly::dynamic::object(); + } + folly::dynamic result = HostPlatformViewProps::getDiffProps(prevProps); + + if (defaultValue != oldProps->defaultValue) { + result["defaultValue"] = defaultValue; + } + + if (placeholder != oldProps->placeholder) { + result["placeholder"] = placeholder; + } + + if (placeholderTextColor != oldProps->placeholderTextColor) { + result["placeholderTextColor"] = *placeholderTextColor; + } + + if (editable != oldProps->editable) { + result["editable"] = editable; + } + + if (autoFocus != oldProps->autoFocus) { + result["autoFocus"] = autoFocus; + } + + if (scrollEnabled != oldProps->scrollEnabled) { + result["scrollEnabled"] = scrollEnabled; + } + + if (autoCapitalize != oldProps->autoCapitalize) { + result["autoCapitalize"] = autoCapitalize; + } + + if (multiline != oldProps->multiline) { + result["multiline"] = multiline; + } + + if (cursorColor != oldProps->cursorColor) { + result["cursorColor"] = *cursorColor; + } + + if (selectionColor != oldProps->selectionColor) { + result["selectionColor"] = *selectionColor; + } + + if (markdownStyle != oldProps->markdownStyle) { + result["markdownStyle"] = toDynamic(markdownStyle); + } + + if (color != oldProps->color) { + result["color"] = *color; + } + + if ((fontSize != oldProps->fontSize) && !(std::isnan(fontSize) && std::isnan(oldProps->fontSize))) { + result["fontSize"] = fontSize; + } + + if ((lineHeight != oldProps->lineHeight) && !(std::isnan(lineHeight) && std::isnan(oldProps->lineHeight))) { + result["lineHeight"] = lineHeight; + } + + if (fontFamily != oldProps->fontFamily) { + result["fontFamily"] = fontFamily; + } + + if (fontWeight != oldProps->fontWeight) { + result["fontWeight"] = fontWeight; + } + + if (isOnChangeMarkdownSet != oldProps->isOnChangeMarkdownSet) { + result["isOnChangeMarkdownSet"] = isOnChangeMarkdownSet; + } + + if (contextMenuItems != oldProps->contextMenuItems) { + result["contextMenuItems"] = toDynamic(contextMenuItems); + } + + if (linkRegex != oldProps->linkRegex) { + result["linkRegex"] = toDynamic(linkRegex); + } + return result; +} +#endif +EnrichedMarkdownProps::EnrichedMarkdownProps( + const PropsParserContext &context, + const EnrichedMarkdownProps &sourceProps, + const RawProps &rawProps): ViewProps(context, sourceProps, rawProps), + + markdown(convertRawProp(context, rawProps, "markdown", sourceProps.markdown, {})), + markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})), + enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})), + selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})), + selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})), + selectionHandleColor(convertRawProp(context, rawProps, "selectionHandleColor", sourceProps.selectionHandleColor, {})), + md4cFlags(convertRawProp(context, rawProps, "md4cFlags", sourceProps.md4cFlags, {})), + allowFontScaling(convertRawProp(context, rawProps, "allowFontScaling", sourceProps.allowFontScaling, {true})), + maxFontSizeMultiplier(convertRawProp(context, rawProps, "maxFontSizeMultiplier", sourceProps.maxFontSizeMultiplier, {0.0})), + allowTrailingMargin(convertRawProp(context, rawProps, "allowTrailingMargin", sourceProps.allowTrailingMargin, {false})), + streamingAnimation(convertRawProp(context, rawProps, "streamingAnimation", sourceProps.streamingAnimation, {false})), + spoilerOverlay(convertRawProp(context, rawProps, "spoilerOverlay", sourceProps.spoilerOverlay, {std::string{"particles"}})), + contextMenuItems(convertRawProp(context, rawProps, "contextMenuItems", sourceProps.contextMenuItems, {})) {} + +#ifdef RN_SERIALIZABLE_STATE +ComponentName EnrichedMarkdownProps::getDiffPropsImplementationTarget() const { + return "EnrichedMarkdown"; +} + +folly::dynamic EnrichedMarkdownProps::getDiffProps( + const Props* prevProps) const { + static const auto defaultProps = EnrichedMarkdownProps(); + const EnrichedMarkdownProps* oldProps = prevProps == nullptr + ? &defaultProps + : static_cast(prevProps); + if (this == oldProps) { + return folly::dynamic::object(); + } + folly::dynamic result = HostPlatformViewProps::getDiffProps(prevProps); + + if (markdown != oldProps->markdown) { + result["markdown"] = markdown; + } + + if (markdownStyle != oldProps->markdownStyle) { + result["markdownStyle"] = toDynamic(markdownStyle); + } + + if (enableLinkPreview != oldProps->enableLinkPreview) { + result["enableLinkPreview"] = enableLinkPreview; + } + + if (selectable != oldProps->selectable) { + result["selectable"] = selectable; + } + + if (selectionColor != oldProps->selectionColor) { + result["selectionColor"] = *selectionColor; + } + + if (selectionHandleColor != oldProps->selectionHandleColor) { + result["selectionHandleColor"] = *selectionHandleColor; + } + + if (md4cFlags != oldProps->md4cFlags) { + result["md4cFlags"] = toDynamic(md4cFlags); + } + + if (allowFontScaling != oldProps->allowFontScaling) { + result["allowFontScaling"] = allowFontScaling; + } + + if ((maxFontSizeMultiplier != oldProps->maxFontSizeMultiplier) && !(std::isnan(maxFontSizeMultiplier) && std::isnan(oldProps->maxFontSizeMultiplier))) { + result["maxFontSizeMultiplier"] = maxFontSizeMultiplier; + } + + if (allowTrailingMargin != oldProps->allowTrailingMargin) { + result["allowTrailingMargin"] = allowTrailingMargin; + } + + if (streamingAnimation != oldProps->streamingAnimation) { + result["streamingAnimation"] = streamingAnimation; + } + + if (spoilerOverlay != oldProps->spoilerOverlay) { + result["spoilerOverlay"] = spoilerOverlay; + } + + if (contextMenuItems != oldProps->contextMenuItems) { + result["contextMenuItems"] = toDynamic(contextMenuItems); + } + return result; +} +#endif +EnrichedMarkdownTextProps::EnrichedMarkdownTextProps( + const PropsParserContext &context, + const EnrichedMarkdownTextProps &sourceProps, + const RawProps &rawProps): ViewProps(context, sourceProps, rawProps), + + markdown(convertRawProp(context, rawProps, "markdown", sourceProps.markdown, {})), + markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})), + enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})), + selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})), + selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})), + selectionHandleColor(convertRawProp(context, rawProps, "selectionHandleColor", sourceProps.selectionHandleColor, {})), + md4cFlags(convertRawProp(context, rawProps, "md4cFlags", sourceProps.md4cFlags, {})), + allowFontScaling(convertRawProp(context, rawProps, "allowFontScaling", sourceProps.allowFontScaling, {true})), + maxFontSizeMultiplier(convertRawProp(context, rawProps, "maxFontSizeMultiplier", sourceProps.maxFontSizeMultiplier, {0.0})), + allowTrailingMargin(convertRawProp(context, rawProps, "allowTrailingMargin", sourceProps.allowTrailingMargin, {false})), + streamingAnimation(convertRawProp(context, rawProps, "streamingAnimation", sourceProps.streamingAnimation, {false})), + spoilerOverlay(convertRawProp(context, rawProps, "spoilerOverlay", sourceProps.spoilerOverlay, {std::string{"particles"}})), + contextMenuItems(convertRawProp(context, rawProps, "contextMenuItems", sourceProps.contextMenuItems, {})) {} + +#ifdef RN_SERIALIZABLE_STATE +ComponentName EnrichedMarkdownTextProps::getDiffPropsImplementationTarget() const { + return "EnrichedMarkdownText"; +} + +folly::dynamic EnrichedMarkdownTextProps::getDiffProps( + const Props* prevProps) const { + static const auto defaultProps = EnrichedMarkdownTextProps(); + const EnrichedMarkdownTextProps* oldProps = prevProps == nullptr + ? &defaultProps + : static_cast(prevProps); + if (this == oldProps) { + return folly::dynamic::object(); + } + folly::dynamic result = HostPlatformViewProps::getDiffProps(prevProps); + + if (markdown != oldProps->markdown) { + result["markdown"] = markdown; + } + + if (markdownStyle != oldProps->markdownStyle) { + result["markdownStyle"] = toDynamic(markdownStyle); + } + + if (enableLinkPreview != oldProps->enableLinkPreview) { + result["enableLinkPreview"] = enableLinkPreview; + } + + if (selectable != oldProps->selectable) { + result["selectable"] = selectable; + } + + if (selectionColor != oldProps->selectionColor) { + result["selectionColor"] = *selectionColor; + } + + if (selectionHandleColor != oldProps->selectionHandleColor) { + result["selectionHandleColor"] = *selectionHandleColor; + } + + if (md4cFlags != oldProps->md4cFlags) { + result["md4cFlags"] = toDynamic(md4cFlags); + } + + if (allowFontScaling != oldProps->allowFontScaling) { + result["allowFontScaling"] = allowFontScaling; + } + + if ((maxFontSizeMultiplier != oldProps->maxFontSizeMultiplier) && !(std::isnan(maxFontSizeMultiplier) && std::isnan(oldProps->maxFontSizeMultiplier))) { + result["maxFontSizeMultiplier"] = maxFontSizeMultiplier; + } + + if (allowTrailingMargin != oldProps->allowTrailingMargin) { + result["allowTrailingMargin"] = allowTrailingMargin; + } + + if (streamingAnimation != oldProps->streamingAnimation) { + result["streamingAnimation"] = streamingAnimation; + } + + if (spoilerOverlay != oldProps->spoilerOverlay) { + result["spoilerOverlay"] = spoilerOverlay; + } + + if (contextMenuItems != oldProps->contextMenuItems) { + result["contextMenuItems"] = toDynamic(contextMenuItems); + } + return result; +} +#endif + +} // namespace facebook::react diff --git a/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h new file mode 100644 index 00000000..be040887 --- /dev/null +++ b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/Props.h @@ -0,0 +1,4662 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GeneratePropsH.js + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +struct EnrichedMarkdownInputMarkdownStyleStrongStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputMarkdownStyleStrongStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputMarkdownStyleStrongStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownInputMarkdownStyleStrongStruct &value) { + return "[Object EnrichedMarkdownInputMarkdownStyleStrongStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputMarkdownStyleStrongStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownInputMarkdownStyleEmStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputMarkdownStyleEmStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputMarkdownStyleEmStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownInputMarkdownStyleEmStruct &value) { + return "[Object EnrichedMarkdownInputMarkdownStyleEmStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputMarkdownStyleEmStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownInputMarkdownStyleLinkStruct { + SharedColor color{}; + bool underline{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputMarkdownStyleLinkStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["underline"] = underline; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputMarkdownStyleLinkStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } +} + +static inline std::string toString(const EnrichedMarkdownInputMarkdownStyleLinkStruct &value) { + return "[Object EnrichedMarkdownInputMarkdownStyleLinkStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputMarkdownStyleLinkStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownInputMarkdownStyleSpoilerStruct { + SharedColor color{}; + SharedColor backgroundColor{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputMarkdownStyleSpoilerStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputMarkdownStyleSpoilerStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } +} + +static inline std::string toString(const EnrichedMarkdownInputMarkdownStyleSpoilerStruct &value) { + return "[Object EnrichedMarkdownInputMarkdownStyleSpoilerStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputMarkdownStyleSpoilerStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownInputMarkdownStyleStruct { + EnrichedMarkdownInputMarkdownStyleStrongStruct strong{}; + EnrichedMarkdownInputMarkdownStyleEmStruct em{}; + EnrichedMarkdownInputMarkdownStyleLinkStruct link{}; + EnrichedMarkdownInputMarkdownStyleSpoilerStruct spoiler{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputMarkdownStyleStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["strong"] = ::facebook::react::toDynamic(strong); + result["em"] = ::facebook::react::toDynamic(em); + result["link"] = ::facebook::react::toDynamic(link); + result["spoiler"] = ::facebook::react::toDynamic(spoiler); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputMarkdownStyleStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_strong = map.find("strong"); + if (tmp_strong != map.end()) { + fromRawValue(context, tmp_strong->second, result.strong); + } + auto tmp_em = map.find("em"); + if (tmp_em != map.end()) { + fromRawValue(context, tmp_em->second, result.em); + } + auto tmp_link = map.find("link"); + if (tmp_link != map.end()) { + fromRawValue(context, tmp_link->second, result.link); + } + auto tmp_spoiler = map.find("spoiler"); + if (tmp_spoiler != map.end()) { + fromRawValue(context, tmp_spoiler->second, result.spoiler); + } +} + +static inline std::string toString(const EnrichedMarkdownInputMarkdownStyleStruct &value) { + return "[Object EnrichedMarkdownInputMarkdownStyleStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputMarkdownStyleStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownInputContextMenuItemsStruct { + std::string text{}; + std::string icon{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputContextMenuItemsStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["text"] = text; + result["icon"] = icon; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputContextMenuItemsStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_text = map.find("text"); + if (tmp_text != map.end()) { + fromRawValue(context, tmp_text->second, result.text); + } + auto tmp_icon = map.find("icon"); + if (tmp_icon != map.end()) { + fromRawValue(context, tmp_icon->second, result.icon); + } +} + +static inline std::string toString(const EnrichedMarkdownInputContextMenuItemsStruct &value) { + return "[Object EnrichedMarkdownInputContextMenuItemsStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputContextMenuItemsStruct &value) { + return value.toDynamic(); +} +#endif + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector &result) { + auto items = (std::vector)value; + for (const auto &item : items) { + EnrichedMarkdownInputContextMenuItemsStruct newItem; + fromRawValue(context, item, newItem); + result.emplace_back(newItem); + } +} + + +struct EnrichedMarkdownInputLinkRegexStruct { + std::string pattern{}; + bool caseInsensitive{false}; + bool dotAll{false}; + bool isDisabled{false}; + bool isDefault{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputLinkRegexStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["pattern"] = pattern; + result["caseInsensitive"] = caseInsensitive; + result["dotAll"] = dotAll; + result["isDisabled"] = isDisabled; + result["isDefault"] = isDefault; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputLinkRegexStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_pattern = map.find("pattern"); + if (tmp_pattern != map.end()) { + fromRawValue(context, tmp_pattern->second, result.pattern); + } + auto tmp_caseInsensitive = map.find("caseInsensitive"); + if (tmp_caseInsensitive != map.end()) { + fromRawValue(context, tmp_caseInsensitive->second, result.caseInsensitive); + } + auto tmp_dotAll = map.find("dotAll"); + if (tmp_dotAll != map.end()) { + fromRawValue(context, tmp_dotAll->second, result.dotAll); + } + auto tmp_isDisabled = map.find("isDisabled"); + if (tmp_isDisabled != map.end()) { + fromRawValue(context, tmp_isDisabled->second, result.isDisabled); + } + auto tmp_isDefault = map.find("isDefault"); + if (tmp_isDefault != map.end()) { + fromRawValue(context, tmp_isDefault->second, result.isDefault); + } +} + +static inline std::string toString(const EnrichedMarkdownInputLinkRegexStruct &value) { + return "[Object EnrichedMarkdownInputLinkRegexStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputLinkRegexStruct &value) { + return value.toDynamic(); +} +#endif +class EnrichedMarkdownInputProps final : public ViewProps { + public: + EnrichedMarkdownInputProps() = default; + EnrichedMarkdownInputProps(const PropsParserContext& context, const EnrichedMarkdownInputProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + std::string defaultValue{}; + std::string placeholder{}; + SharedColor placeholderTextColor{}; + bool editable{false}; + bool autoFocus{false}; + bool scrollEnabled{false}; + std::string autoCapitalize{}; + bool multiline{false}; + SharedColor cursorColor{}; + SharedColor selectionColor{}; + EnrichedMarkdownInputMarkdownStyleStruct markdownStyle{}; + SharedColor color{}; + Float fontSize{0.0}; + Float lineHeight{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + bool isOnChangeMarkdownSet{false}; + std::vector contextMenuItems{}; + EnrichedMarkdownInputLinkRegexStruct linkRegex{}; + + #ifdef RN_SERIALIZABLE_STATE + ComponentName getDiffPropsImplementationTarget() const override; + + folly::dynamic getDiffProps(const Props* prevProps) const override; + #endif + + +}; + +struct EnrichedMarkdownMarkdownStyleParagraphStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleParagraphStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleParagraphStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleParagraphStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleParagraphStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleParagraphStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH1Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH1Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH1Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH1Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH1Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH1Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH2Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH2Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH2Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH2Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH2Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH2Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH3Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH3Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH3Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH3Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH3Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH3Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH4Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH4Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH4Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH4Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH4Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH4Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH5Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH5Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH5Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH5Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH5Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH5Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH6Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH6Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH6Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH6Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH6Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH6Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleBlockquoteStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float gapWidth{0.0}; + SharedColor backgroundColor{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleBlockquoteStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["gapWidth"] = gapWidth; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleBlockquoteStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_gapWidth = map.find("gapWidth"); + if (tmp_gapWidth != map.end()) { + fromRawValue(context, tmp_gapWidth->second, result.gapWidth); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleBlockquoteStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleBlockquoteStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleBlockquoteStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleListStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor bulletColor{}; + Float bulletSize{0.0}; + SharedColor markerColor{}; + std::string markerFontWeight{}; + Float gapWidth{0.0}; + Float marginLeft{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleListStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["bulletColor"] = ::facebook::react::toDynamic(bulletColor); + result["bulletSize"] = bulletSize; + result["markerColor"] = ::facebook::react::toDynamic(markerColor); + result["markerFontWeight"] = markerFontWeight; + result["gapWidth"] = gapWidth; + result["marginLeft"] = marginLeft; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleListStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_bulletColor = map.find("bulletColor"); + if (tmp_bulletColor != map.end()) { + fromRawValue(context, tmp_bulletColor->second, result.bulletColor); + } + auto tmp_bulletSize = map.find("bulletSize"); + if (tmp_bulletSize != map.end()) { + fromRawValue(context, tmp_bulletSize->second, result.bulletSize); + } + auto tmp_markerColor = map.find("markerColor"); + if (tmp_markerColor != map.end()) { + fromRawValue(context, tmp_markerColor->second, result.markerColor); + } + auto tmp_markerFontWeight = map.find("markerFontWeight"); + if (tmp_markerFontWeight != map.end()) { + fromRawValue(context, tmp_markerFontWeight->second, result.markerFontWeight); + } + auto tmp_gapWidth = map.find("gapWidth"); + if (tmp_gapWidth != map.end()) { + fromRawValue(context, tmp_gapWidth->second, result.gapWidth); + } + auto tmp_marginLeft = map.find("marginLeft"); + if (tmp_marginLeft != map.end()) { + fromRawValue(context, tmp_marginLeft->second, result.marginLeft); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleListStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleListStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleListStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleCodeBlockStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + Float borderRadius{0.0}; + Float borderWidth{0.0}; + Float padding{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleCodeBlockStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderRadius"] = borderRadius; + result["borderWidth"] = borderWidth; + result["padding"] = padding; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleCodeBlockStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_padding = map.find("padding"); + if (tmp_padding != map.end()) { + fromRawValue(context, tmp_padding->second, result.padding); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleCodeBlockStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleCodeBlockStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleCodeBlockStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleLinkStruct { + std::string fontFamily{}; + SharedColor color{}; + bool underline{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleLinkStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["color"] = ::facebook::react::toDynamic(color); + result["underline"] = underline; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleLinkStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleLinkStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleLinkStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleLinkStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleStrongStruct { + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleStrongStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleStrongStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleStrongStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleStrongStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleStrongStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleEmStruct { + std::string fontFamily{}; + std::string fontStyle{}; + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleEmStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontStyle"] = fontStyle; + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleEmStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontStyle = map.find("fontStyle"); + if (tmp_fontStyle != map.end()) { + fromRawValue(context, tmp_fontStyle->second, result.fontStyle); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleEmStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleEmStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleEmStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleStrikethroughStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleStrikethroughStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleStrikethroughStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleStrikethroughStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleStrikethroughStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleStrikethroughStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleUnderlineStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleUnderlineStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleUnderlineStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleUnderlineStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleUnderlineStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleUnderlineStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleCodeStruct { + std::string fontFamily{}; + Float fontSize{0.0}; + SharedColor color{}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleCodeStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontSize"] = fontSize; + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleCodeStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleCodeStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleCodeStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleCodeStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleImageStruct { + Float height{0.0}; + Float borderRadius{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleImageStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["height"] = height; + result["borderRadius"] = borderRadius; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleImageStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_height = map.find("height"); + if (tmp_height != map.end()) { + fromRawValue(context, tmp_height->second, result.height); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleImageStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleImageStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleImageStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleInlineImageStruct { + Float size{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleInlineImageStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["size"] = size; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleInlineImageStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_size = map.find("size"); + if (tmp_size != map.end()) { + fromRawValue(context, tmp_size->second, result.size); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleInlineImageStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleInlineImageStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleInlineImageStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleThematicBreakStruct { + SharedColor color{}; + Float height{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleThematicBreakStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["height"] = height; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleThematicBreakStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_height = map.find("height"); + if (tmp_height != map.end()) { + fromRawValue(context, tmp_height->second, result.height); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleThematicBreakStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleThematicBreakStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleThematicBreakStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleTableStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string headerFontFamily{}; + SharedColor headerBackgroundColor{}; + SharedColor headerTextColor{}; + SharedColor rowEvenBackgroundColor{}; + SharedColor rowOddBackgroundColor{}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + Float cellPaddingHorizontal{0.0}; + Float cellPaddingVertical{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleTableStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["headerFontFamily"] = headerFontFamily; + result["headerBackgroundColor"] = ::facebook::react::toDynamic(headerBackgroundColor); + result["headerTextColor"] = ::facebook::react::toDynamic(headerTextColor); + result["rowEvenBackgroundColor"] = ::facebook::react::toDynamic(rowEvenBackgroundColor); + result["rowOddBackgroundColor"] = ::facebook::react::toDynamic(rowOddBackgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + result["cellPaddingHorizontal"] = cellPaddingHorizontal; + result["cellPaddingVertical"] = cellPaddingVertical; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleTableStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_headerFontFamily = map.find("headerFontFamily"); + if (tmp_headerFontFamily != map.end()) { + fromRawValue(context, tmp_headerFontFamily->second, result.headerFontFamily); + } + auto tmp_headerBackgroundColor = map.find("headerBackgroundColor"); + if (tmp_headerBackgroundColor != map.end()) { + fromRawValue(context, tmp_headerBackgroundColor->second, result.headerBackgroundColor); + } + auto tmp_headerTextColor = map.find("headerTextColor"); + if (tmp_headerTextColor != map.end()) { + fromRawValue(context, tmp_headerTextColor->second, result.headerTextColor); + } + auto tmp_rowEvenBackgroundColor = map.find("rowEvenBackgroundColor"); + if (tmp_rowEvenBackgroundColor != map.end()) { + fromRawValue(context, tmp_rowEvenBackgroundColor->second, result.rowEvenBackgroundColor); + } + auto tmp_rowOddBackgroundColor = map.find("rowOddBackgroundColor"); + if (tmp_rowOddBackgroundColor != map.end()) { + fromRawValue(context, tmp_rowOddBackgroundColor->second, result.rowOddBackgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_cellPaddingHorizontal = map.find("cellPaddingHorizontal"); + if (tmp_cellPaddingHorizontal != map.end()) { + fromRawValue(context, tmp_cellPaddingHorizontal->second, result.cellPaddingHorizontal); + } + auto tmp_cellPaddingVertical = map.find("cellPaddingVertical"); + if (tmp_cellPaddingVertical != map.end()) { + fromRawValue(context, tmp_cellPaddingVertical->second, result.cellPaddingVertical); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleTableStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleTableStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleTableStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleTaskListStruct { + SharedColor checkedColor{}; + SharedColor borderColor{}; + Float checkboxSize{0.0}; + Float checkboxBorderRadius{0.0}; + SharedColor checkmarkColor{}; + SharedColor checkedTextColor{}; + bool checkedStrikethrough{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleTaskListStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["checkedColor"] = ::facebook::react::toDynamic(checkedColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["checkboxSize"] = checkboxSize; + result["checkboxBorderRadius"] = checkboxBorderRadius; + result["checkmarkColor"] = ::facebook::react::toDynamic(checkmarkColor); + result["checkedTextColor"] = ::facebook::react::toDynamic(checkedTextColor); + result["checkedStrikethrough"] = checkedStrikethrough; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleTaskListStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_checkedColor = map.find("checkedColor"); + if (tmp_checkedColor != map.end()) { + fromRawValue(context, tmp_checkedColor->second, result.checkedColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_checkboxSize = map.find("checkboxSize"); + if (tmp_checkboxSize != map.end()) { + fromRawValue(context, tmp_checkboxSize->second, result.checkboxSize); + } + auto tmp_checkboxBorderRadius = map.find("checkboxBorderRadius"); + if (tmp_checkboxBorderRadius != map.end()) { + fromRawValue(context, tmp_checkboxBorderRadius->second, result.checkboxBorderRadius); + } + auto tmp_checkmarkColor = map.find("checkmarkColor"); + if (tmp_checkmarkColor != map.end()) { + fromRawValue(context, tmp_checkmarkColor->second, result.checkmarkColor); + } + auto tmp_checkedTextColor = map.find("checkedTextColor"); + if (tmp_checkedTextColor != map.end()) { + fromRawValue(context, tmp_checkedTextColor->second, result.checkedTextColor); + } + auto tmp_checkedStrikethrough = map.find("checkedStrikethrough"); + if (tmp_checkedStrikethrough != map.end()) { + fromRawValue(context, tmp_checkedStrikethrough->second, result.checkedStrikethrough); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleTaskListStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleTaskListStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleTaskListStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleMathStruct { + Float fontSize{0.0}; + SharedColor color{}; + SharedColor backgroundColor{}; + Float padding{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleMathStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["padding"] = padding; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleMathStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_padding = map.find("padding"); + if (tmp_padding != map.end()) { + fromRawValue(context, tmp_padding->second, result.padding); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleMathStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleMathStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleMathStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleInlineMathStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleInlineMathStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleInlineMathStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleInlineMathStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleInlineMathStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleInlineMathStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct { + Float density{0.0}; + Float speed{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["density"] = density; + result["speed"] = speed; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_density = map.find("density"); + if (tmp_density != map.end()) { + fromRawValue(context, tmp_density->second, result.density); + } + auto tmp_speed = map.find("speed"); + if (tmp_speed != map.end()) { + fromRawValue(context, tmp_speed->second, result.speed); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleSpoilerSolidStruct { + Float borderRadius{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleSpoilerSolidStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["borderRadius"] = borderRadius; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleSpoilerSolidStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleSpoilerSolidStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleSpoilerSolidStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleSpoilerSolidStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleSpoilerStruct { + SharedColor color{}; + EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct particles{}; + EnrichedMarkdownMarkdownStyleSpoilerSolidStruct solid{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleSpoilerStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["particles"] = ::facebook::react::toDynamic(particles); + result["solid"] = ::facebook::react::toDynamic(solid); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleSpoilerStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_particles = map.find("particles"); + if (tmp_particles != map.end()) { + fromRawValue(context, tmp_particles->second, result.particles); + } + auto tmp_solid = map.find("solid"); + if (tmp_solid != map.end()) { + fromRawValue(context, tmp_solid->second, result.solid); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleSpoilerStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleSpoilerStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleSpoilerStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleMentionStruct { + SharedColor color{}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + Float paddingHorizontal{0.0}; + Float paddingVertical{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + Float fontSize{0.0}; + Float pressedOpacity{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleMentionStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + result["paddingHorizontal"] = paddingHorizontal; + result["paddingVertical"] = paddingVertical; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["fontSize"] = fontSize; + result["pressedOpacity"] = pressedOpacity; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleMentionStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_paddingHorizontal = map.find("paddingHorizontal"); + if (tmp_paddingHorizontal != map.end()) { + fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal); + } + auto tmp_paddingVertical = map.find("paddingVertical"); + if (tmp_paddingVertical != map.end()) { + fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_pressedOpacity = map.find("pressedOpacity"); + if (tmp_pressedOpacity != map.end()) { + fromRawValue(context, tmp_pressedOpacity->second, result.pressedOpacity); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleMentionStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleMentionStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleMentionStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleCitationStruct { + SharedColor color{}; + Float fontSizeMultiplier{0.0}; + Float baselineOffsetPx{0.0}; + std::string fontWeight{}; + bool underline{false}; + SharedColor backgroundColor{}; + Float paddingHorizontal{0.0}; + Float paddingVertical{0.0}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleCitationStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["fontSizeMultiplier"] = fontSizeMultiplier; + result["baselineOffsetPx"] = baselineOffsetPx; + result["fontWeight"] = fontWeight; + result["underline"] = underline; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["paddingHorizontal"] = paddingHorizontal; + result["paddingVertical"] = paddingVertical; + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleCitationStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_fontSizeMultiplier = map.find("fontSizeMultiplier"); + if (tmp_fontSizeMultiplier != map.end()) { + fromRawValue(context, tmp_fontSizeMultiplier->second, result.fontSizeMultiplier); + } + auto tmp_baselineOffsetPx = map.find("baselineOffsetPx"); + if (tmp_baselineOffsetPx != map.end()) { + fromRawValue(context, tmp_baselineOffsetPx->second, result.baselineOffsetPx); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_paddingHorizontal = map.find("paddingHorizontal"); + if (tmp_paddingHorizontal != map.end()) { + fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal); + } + auto tmp_paddingVertical = map.find("paddingVertical"); + if (tmp_paddingVertical != map.end()) { + fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleCitationStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleCitationStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleCitationStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleStruct { + EnrichedMarkdownMarkdownStyleParagraphStruct paragraph{}; + EnrichedMarkdownMarkdownStyleH1Struct h1{}; + EnrichedMarkdownMarkdownStyleH2Struct h2{}; + EnrichedMarkdownMarkdownStyleH3Struct h3{}; + EnrichedMarkdownMarkdownStyleH4Struct h4{}; + EnrichedMarkdownMarkdownStyleH5Struct h5{}; + EnrichedMarkdownMarkdownStyleH6Struct h6{}; + EnrichedMarkdownMarkdownStyleBlockquoteStruct blockquote{}; + EnrichedMarkdownMarkdownStyleListStruct list{}; + EnrichedMarkdownMarkdownStyleCodeBlockStruct codeBlock{}; + EnrichedMarkdownMarkdownStyleLinkStruct link{}; + EnrichedMarkdownMarkdownStyleStrongStruct strong{}; + EnrichedMarkdownMarkdownStyleEmStruct em{}; + EnrichedMarkdownMarkdownStyleStrikethroughStruct strikethrough{}; + EnrichedMarkdownMarkdownStyleUnderlineStruct underline{}; + EnrichedMarkdownMarkdownStyleCodeStruct code{}; + EnrichedMarkdownMarkdownStyleImageStruct image{}; + EnrichedMarkdownMarkdownStyleInlineImageStruct inlineImage{}; + EnrichedMarkdownMarkdownStyleThematicBreakStruct thematicBreak{}; + EnrichedMarkdownMarkdownStyleTableStruct table{}; + EnrichedMarkdownMarkdownStyleTaskListStruct taskList{}; + EnrichedMarkdownMarkdownStyleMathStruct math{}; + EnrichedMarkdownMarkdownStyleInlineMathStruct inlineMath{}; + EnrichedMarkdownMarkdownStyleSpoilerStruct spoiler{}; + EnrichedMarkdownMarkdownStyleMentionStruct mention{}; + EnrichedMarkdownMarkdownStyleCitationStruct citation{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["paragraph"] = ::facebook::react::toDynamic(paragraph); + result["h1"] = ::facebook::react::toDynamic(h1); + result["h2"] = ::facebook::react::toDynamic(h2); + result["h3"] = ::facebook::react::toDynamic(h3); + result["h4"] = ::facebook::react::toDynamic(h4); + result["h5"] = ::facebook::react::toDynamic(h5); + result["h6"] = ::facebook::react::toDynamic(h6); + result["blockquote"] = ::facebook::react::toDynamic(blockquote); + result["list"] = ::facebook::react::toDynamic(list); + result["codeBlock"] = ::facebook::react::toDynamic(codeBlock); + result["link"] = ::facebook::react::toDynamic(link); + result["strong"] = ::facebook::react::toDynamic(strong); + result["em"] = ::facebook::react::toDynamic(em); + result["strikethrough"] = ::facebook::react::toDynamic(strikethrough); + result["underline"] = ::facebook::react::toDynamic(underline); + result["code"] = ::facebook::react::toDynamic(code); + result["image"] = ::facebook::react::toDynamic(image); + result["inlineImage"] = ::facebook::react::toDynamic(inlineImage); + result["thematicBreak"] = ::facebook::react::toDynamic(thematicBreak); + result["table"] = ::facebook::react::toDynamic(table); + result["taskList"] = ::facebook::react::toDynamic(taskList); + result["math"] = ::facebook::react::toDynamic(math); + result["inlineMath"] = ::facebook::react::toDynamic(inlineMath); + result["spoiler"] = ::facebook::react::toDynamic(spoiler); + result["mention"] = ::facebook::react::toDynamic(mention); + result["citation"] = ::facebook::react::toDynamic(citation); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_paragraph = map.find("paragraph"); + if (tmp_paragraph != map.end()) { + fromRawValue(context, tmp_paragraph->second, result.paragraph); + } + auto tmp_h1 = map.find("h1"); + if (tmp_h1 != map.end()) { + fromRawValue(context, tmp_h1->second, result.h1); + } + auto tmp_h2 = map.find("h2"); + if (tmp_h2 != map.end()) { + fromRawValue(context, tmp_h2->second, result.h2); + } + auto tmp_h3 = map.find("h3"); + if (tmp_h3 != map.end()) { + fromRawValue(context, tmp_h3->second, result.h3); + } + auto tmp_h4 = map.find("h4"); + if (tmp_h4 != map.end()) { + fromRawValue(context, tmp_h4->second, result.h4); + } + auto tmp_h5 = map.find("h5"); + if (tmp_h5 != map.end()) { + fromRawValue(context, tmp_h5->second, result.h5); + } + auto tmp_h6 = map.find("h6"); + if (tmp_h6 != map.end()) { + fromRawValue(context, tmp_h6->second, result.h6); + } + auto tmp_blockquote = map.find("blockquote"); + if (tmp_blockquote != map.end()) { + fromRawValue(context, tmp_blockquote->second, result.blockquote); + } + auto tmp_list = map.find("list"); + if (tmp_list != map.end()) { + fromRawValue(context, tmp_list->second, result.list); + } + auto tmp_codeBlock = map.find("codeBlock"); + if (tmp_codeBlock != map.end()) { + fromRawValue(context, tmp_codeBlock->second, result.codeBlock); + } + auto tmp_link = map.find("link"); + if (tmp_link != map.end()) { + fromRawValue(context, tmp_link->second, result.link); + } + auto tmp_strong = map.find("strong"); + if (tmp_strong != map.end()) { + fromRawValue(context, tmp_strong->second, result.strong); + } + auto tmp_em = map.find("em"); + if (tmp_em != map.end()) { + fromRawValue(context, tmp_em->second, result.em); + } + auto tmp_strikethrough = map.find("strikethrough"); + if (tmp_strikethrough != map.end()) { + fromRawValue(context, tmp_strikethrough->second, result.strikethrough); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_code = map.find("code"); + if (tmp_code != map.end()) { + fromRawValue(context, tmp_code->second, result.code); + } + auto tmp_image = map.find("image"); + if (tmp_image != map.end()) { + fromRawValue(context, tmp_image->second, result.image); + } + auto tmp_inlineImage = map.find("inlineImage"); + if (tmp_inlineImage != map.end()) { + fromRawValue(context, tmp_inlineImage->second, result.inlineImage); + } + auto tmp_thematicBreak = map.find("thematicBreak"); + if (tmp_thematicBreak != map.end()) { + fromRawValue(context, tmp_thematicBreak->second, result.thematicBreak); + } + auto tmp_table = map.find("table"); + if (tmp_table != map.end()) { + fromRawValue(context, tmp_table->second, result.table); + } + auto tmp_taskList = map.find("taskList"); + if (tmp_taskList != map.end()) { + fromRawValue(context, tmp_taskList->second, result.taskList); + } + auto tmp_math = map.find("math"); + if (tmp_math != map.end()) { + fromRawValue(context, tmp_math->second, result.math); + } + auto tmp_inlineMath = map.find("inlineMath"); + if (tmp_inlineMath != map.end()) { + fromRawValue(context, tmp_inlineMath->second, result.inlineMath); + } + auto tmp_spoiler = map.find("spoiler"); + if (tmp_spoiler != map.end()) { + fromRawValue(context, tmp_spoiler->second, result.spoiler); + } + auto tmp_mention = map.find("mention"); + if (tmp_mention != map.end()) { + fromRawValue(context, tmp_mention->second, result.mention); + } + auto tmp_citation = map.find("citation"); + if (tmp_citation != map.end()) { + fromRawValue(context, tmp_citation->second, result.citation); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMd4cFlagsStruct { + bool underline{false}; + bool latexMath{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMd4cFlagsStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["underline"] = underline; + result["latexMath"] = latexMath; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMd4cFlagsStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_latexMath = map.find("latexMath"); + if (tmp_latexMath != map.end()) { + fromRawValue(context, tmp_latexMath->second, result.latexMath); + } +} + +static inline std::string toString(const EnrichedMarkdownMd4cFlagsStruct &value) { + return "[Object EnrichedMarkdownMd4cFlagsStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMd4cFlagsStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownContextMenuItemsStruct { + std::string text{}; + std::string icon{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownContextMenuItemsStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["text"] = text; + result["icon"] = icon; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownContextMenuItemsStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_text = map.find("text"); + if (tmp_text != map.end()) { + fromRawValue(context, tmp_text->second, result.text); + } + auto tmp_icon = map.find("icon"); + if (tmp_icon != map.end()) { + fromRawValue(context, tmp_icon->second, result.icon); + } +} + +static inline std::string toString(const EnrichedMarkdownContextMenuItemsStruct &value) { + return "[Object EnrichedMarkdownContextMenuItemsStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownContextMenuItemsStruct &value) { + return value.toDynamic(); +} +#endif + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector &result) { + auto items = (std::vector)value; + for (const auto &item : items) { + EnrichedMarkdownContextMenuItemsStruct newItem; + fromRawValue(context, item, newItem); + result.emplace_back(newItem); + } +} + +class EnrichedMarkdownProps final : public ViewProps { + public: + EnrichedMarkdownProps() = default; + EnrichedMarkdownProps(const PropsParserContext& context, const EnrichedMarkdownProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + std::string markdown{}; + EnrichedMarkdownMarkdownStyleStruct markdownStyle{}; + bool enableLinkPreview{true}; + bool selectable{false}; + SharedColor selectionColor{}; + SharedColor selectionHandleColor{}; + EnrichedMarkdownMd4cFlagsStruct md4cFlags{}; + bool allowFontScaling{true}; + Float maxFontSizeMultiplier{0.0}; + bool allowTrailingMargin{false}; + bool streamingAnimation{false}; + std::string spoilerOverlay{std::string{"particles"}}; + std::vector contextMenuItems{}; + + #ifdef RN_SERIALIZABLE_STATE + ComponentName getDiffPropsImplementationTarget() const override; + + folly::dynamic getDiffProps(const Props* prevProps) const override; + #endif + + +}; + +struct EnrichedMarkdownTextMarkdownStyleParagraphStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleParagraphStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleParagraphStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleParagraphStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleParagraphStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleParagraphStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH1Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH1Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH1Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH1Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH1Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH1Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH2Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH2Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH2Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH2Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH2Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH2Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH3Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH3Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH3Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH3Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH3Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH3Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH4Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH4Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH4Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH4Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH4Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH4Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH5Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH5Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH5Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH5Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH5Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH5Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH6Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH6Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH6Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH6Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH6Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH6Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleBlockquoteStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float gapWidth{0.0}; + SharedColor backgroundColor{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleBlockquoteStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["gapWidth"] = gapWidth; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleBlockquoteStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_gapWidth = map.find("gapWidth"); + if (tmp_gapWidth != map.end()) { + fromRawValue(context, tmp_gapWidth->second, result.gapWidth); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleBlockquoteStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleBlockquoteStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleBlockquoteStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleListStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor bulletColor{}; + Float bulletSize{0.0}; + SharedColor markerColor{}; + std::string markerFontWeight{}; + Float gapWidth{0.0}; + Float marginLeft{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleListStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["bulletColor"] = ::facebook::react::toDynamic(bulletColor); + result["bulletSize"] = bulletSize; + result["markerColor"] = ::facebook::react::toDynamic(markerColor); + result["markerFontWeight"] = markerFontWeight; + result["gapWidth"] = gapWidth; + result["marginLeft"] = marginLeft; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleListStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_bulletColor = map.find("bulletColor"); + if (tmp_bulletColor != map.end()) { + fromRawValue(context, tmp_bulletColor->second, result.bulletColor); + } + auto tmp_bulletSize = map.find("bulletSize"); + if (tmp_bulletSize != map.end()) { + fromRawValue(context, tmp_bulletSize->second, result.bulletSize); + } + auto tmp_markerColor = map.find("markerColor"); + if (tmp_markerColor != map.end()) { + fromRawValue(context, tmp_markerColor->second, result.markerColor); + } + auto tmp_markerFontWeight = map.find("markerFontWeight"); + if (tmp_markerFontWeight != map.end()) { + fromRawValue(context, tmp_markerFontWeight->second, result.markerFontWeight); + } + auto tmp_gapWidth = map.find("gapWidth"); + if (tmp_gapWidth != map.end()) { + fromRawValue(context, tmp_gapWidth->second, result.gapWidth); + } + auto tmp_marginLeft = map.find("marginLeft"); + if (tmp_marginLeft != map.end()) { + fromRawValue(context, tmp_marginLeft->second, result.marginLeft); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleListStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleListStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleListStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleCodeBlockStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + Float borderRadius{0.0}; + Float borderWidth{0.0}; + Float padding{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleCodeBlockStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderRadius"] = borderRadius; + result["borderWidth"] = borderWidth; + result["padding"] = padding; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleCodeBlockStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_padding = map.find("padding"); + if (tmp_padding != map.end()) { + fromRawValue(context, tmp_padding->second, result.padding); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleCodeBlockStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleCodeBlockStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleCodeBlockStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleLinkStruct { + std::string fontFamily{}; + SharedColor color{}; + bool underline{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleLinkStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["color"] = ::facebook::react::toDynamic(color); + result["underline"] = underline; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleLinkStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleLinkStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleLinkStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleLinkStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleStrongStruct { + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleStrongStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleStrongStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleStrongStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleStrongStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleStrongStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleEmStruct { + std::string fontFamily{}; + std::string fontStyle{}; + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleEmStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontStyle"] = fontStyle; + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleEmStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontStyle = map.find("fontStyle"); + if (tmp_fontStyle != map.end()) { + fromRawValue(context, tmp_fontStyle->second, result.fontStyle); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleEmStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleEmStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleEmStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleStrikethroughStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleStrikethroughStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleStrikethroughStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleStrikethroughStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleStrikethroughStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleStrikethroughStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleUnderlineStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleUnderlineStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleUnderlineStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleUnderlineStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleUnderlineStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleUnderlineStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleCodeStruct { + std::string fontFamily{}; + Float fontSize{0.0}; + SharedColor color{}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleCodeStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontSize"] = fontSize; + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleCodeStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleCodeStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleCodeStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleCodeStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleImageStruct { + Float height{0.0}; + Float borderRadius{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleImageStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["height"] = height; + result["borderRadius"] = borderRadius; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleImageStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_height = map.find("height"); + if (tmp_height != map.end()) { + fromRawValue(context, tmp_height->second, result.height); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleImageStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleImageStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleImageStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleInlineImageStruct { + Float size{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleInlineImageStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["size"] = size; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleInlineImageStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_size = map.find("size"); + if (tmp_size != map.end()) { + fromRawValue(context, tmp_size->second, result.size); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleInlineImageStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleInlineImageStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleInlineImageStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleThematicBreakStruct { + SharedColor color{}; + Float height{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleThematicBreakStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["height"] = height; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleThematicBreakStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_height = map.find("height"); + if (tmp_height != map.end()) { + fromRawValue(context, tmp_height->second, result.height); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleThematicBreakStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleThematicBreakStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleThematicBreakStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleTableStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string headerFontFamily{}; + SharedColor headerBackgroundColor{}; + SharedColor headerTextColor{}; + SharedColor rowEvenBackgroundColor{}; + SharedColor rowOddBackgroundColor{}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + Float cellPaddingHorizontal{0.0}; + Float cellPaddingVertical{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleTableStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["headerFontFamily"] = headerFontFamily; + result["headerBackgroundColor"] = ::facebook::react::toDynamic(headerBackgroundColor); + result["headerTextColor"] = ::facebook::react::toDynamic(headerTextColor); + result["rowEvenBackgroundColor"] = ::facebook::react::toDynamic(rowEvenBackgroundColor); + result["rowOddBackgroundColor"] = ::facebook::react::toDynamic(rowOddBackgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + result["cellPaddingHorizontal"] = cellPaddingHorizontal; + result["cellPaddingVertical"] = cellPaddingVertical; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleTableStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_headerFontFamily = map.find("headerFontFamily"); + if (tmp_headerFontFamily != map.end()) { + fromRawValue(context, tmp_headerFontFamily->second, result.headerFontFamily); + } + auto tmp_headerBackgroundColor = map.find("headerBackgroundColor"); + if (tmp_headerBackgroundColor != map.end()) { + fromRawValue(context, tmp_headerBackgroundColor->second, result.headerBackgroundColor); + } + auto tmp_headerTextColor = map.find("headerTextColor"); + if (tmp_headerTextColor != map.end()) { + fromRawValue(context, tmp_headerTextColor->second, result.headerTextColor); + } + auto tmp_rowEvenBackgroundColor = map.find("rowEvenBackgroundColor"); + if (tmp_rowEvenBackgroundColor != map.end()) { + fromRawValue(context, tmp_rowEvenBackgroundColor->second, result.rowEvenBackgroundColor); + } + auto tmp_rowOddBackgroundColor = map.find("rowOddBackgroundColor"); + if (tmp_rowOddBackgroundColor != map.end()) { + fromRawValue(context, tmp_rowOddBackgroundColor->second, result.rowOddBackgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_cellPaddingHorizontal = map.find("cellPaddingHorizontal"); + if (tmp_cellPaddingHorizontal != map.end()) { + fromRawValue(context, tmp_cellPaddingHorizontal->second, result.cellPaddingHorizontal); + } + auto tmp_cellPaddingVertical = map.find("cellPaddingVertical"); + if (tmp_cellPaddingVertical != map.end()) { + fromRawValue(context, tmp_cellPaddingVertical->second, result.cellPaddingVertical); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleTableStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleTableStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleTableStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleTaskListStruct { + SharedColor checkedColor{}; + SharedColor borderColor{}; + Float checkboxSize{0.0}; + Float checkboxBorderRadius{0.0}; + SharedColor checkmarkColor{}; + SharedColor checkedTextColor{}; + bool checkedStrikethrough{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleTaskListStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["checkedColor"] = ::facebook::react::toDynamic(checkedColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["checkboxSize"] = checkboxSize; + result["checkboxBorderRadius"] = checkboxBorderRadius; + result["checkmarkColor"] = ::facebook::react::toDynamic(checkmarkColor); + result["checkedTextColor"] = ::facebook::react::toDynamic(checkedTextColor); + result["checkedStrikethrough"] = checkedStrikethrough; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleTaskListStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_checkedColor = map.find("checkedColor"); + if (tmp_checkedColor != map.end()) { + fromRawValue(context, tmp_checkedColor->second, result.checkedColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_checkboxSize = map.find("checkboxSize"); + if (tmp_checkboxSize != map.end()) { + fromRawValue(context, tmp_checkboxSize->second, result.checkboxSize); + } + auto tmp_checkboxBorderRadius = map.find("checkboxBorderRadius"); + if (tmp_checkboxBorderRadius != map.end()) { + fromRawValue(context, tmp_checkboxBorderRadius->second, result.checkboxBorderRadius); + } + auto tmp_checkmarkColor = map.find("checkmarkColor"); + if (tmp_checkmarkColor != map.end()) { + fromRawValue(context, tmp_checkmarkColor->second, result.checkmarkColor); + } + auto tmp_checkedTextColor = map.find("checkedTextColor"); + if (tmp_checkedTextColor != map.end()) { + fromRawValue(context, tmp_checkedTextColor->second, result.checkedTextColor); + } + auto tmp_checkedStrikethrough = map.find("checkedStrikethrough"); + if (tmp_checkedStrikethrough != map.end()) { + fromRawValue(context, tmp_checkedStrikethrough->second, result.checkedStrikethrough); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleTaskListStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleTaskListStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleTaskListStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleMathStruct { + Float fontSize{0.0}; + SharedColor color{}; + SharedColor backgroundColor{}; + Float padding{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleMathStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["padding"] = padding; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleMathStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_padding = map.find("padding"); + if (tmp_padding != map.end()) { + fromRawValue(context, tmp_padding->second, result.padding); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleMathStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleMathStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleMathStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleInlineMathStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleInlineMathStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleInlineMathStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleInlineMathStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleInlineMathStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleInlineMathStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct { + Float density{0.0}; + Float speed{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["density"] = density; + result["speed"] = speed; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_density = map.find("density"); + if (tmp_density != map.end()) { + fromRawValue(context, tmp_density->second, result.density); + } + auto tmp_speed = map.find("speed"); + if (tmp_speed != map.end()) { + fromRawValue(context, tmp_speed->second, result.speed); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct { + Float borderRadius{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["borderRadius"] = borderRadius; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleSpoilerStruct { + SharedColor color{}; + EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct particles{}; + EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct solid{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleSpoilerStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["particles"] = ::facebook::react::toDynamic(particles); + result["solid"] = ::facebook::react::toDynamic(solid); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleSpoilerStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_particles = map.find("particles"); + if (tmp_particles != map.end()) { + fromRawValue(context, tmp_particles->second, result.particles); + } + auto tmp_solid = map.find("solid"); + if (tmp_solid != map.end()) { + fromRawValue(context, tmp_solid->second, result.solid); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleSpoilerStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleSpoilerStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleSpoilerStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleMentionStruct { + SharedColor color{}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + Float paddingHorizontal{0.0}; + Float paddingVertical{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + Float fontSize{0.0}; + Float pressedOpacity{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleMentionStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + result["paddingHorizontal"] = paddingHorizontal; + result["paddingVertical"] = paddingVertical; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["fontSize"] = fontSize; + result["pressedOpacity"] = pressedOpacity; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleMentionStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_paddingHorizontal = map.find("paddingHorizontal"); + if (tmp_paddingHorizontal != map.end()) { + fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal); + } + auto tmp_paddingVertical = map.find("paddingVertical"); + if (tmp_paddingVertical != map.end()) { + fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_pressedOpacity = map.find("pressedOpacity"); + if (tmp_pressedOpacity != map.end()) { + fromRawValue(context, tmp_pressedOpacity->second, result.pressedOpacity); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleMentionStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleMentionStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleMentionStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleCitationStruct { + SharedColor color{}; + Float fontSizeMultiplier{0.0}; + Float baselineOffsetPx{0.0}; + std::string fontWeight{}; + bool underline{false}; + SharedColor backgroundColor{}; + Float paddingHorizontal{0.0}; + Float paddingVertical{0.0}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleCitationStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["fontSizeMultiplier"] = fontSizeMultiplier; + result["baselineOffsetPx"] = baselineOffsetPx; + result["fontWeight"] = fontWeight; + result["underline"] = underline; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["paddingHorizontal"] = paddingHorizontal; + result["paddingVertical"] = paddingVertical; + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleCitationStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_fontSizeMultiplier = map.find("fontSizeMultiplier"); + if (tmp_fontSizeMultiplier != map.end()) { + fromRawValue(context, tmp_fontSizeMultiplier->second, result.fontSizeMultiplier); + } + auto tmp_baselineOffsetPx = map.find("baselineOffsetPx"); + if (tmp_baselineOffsetPx != map.end()) { + fromRawValue(context, tmp_baselineOffsetPx->second, result.baselineOffsetPx); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_paddingHorizontal = map.find("paddingHorizontal"); + if (tmp_paddingHorizontal != map.end()) { + fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal); + } + auto tmp_paddingVertical = map.find("paddingVertical"); + if (tmp_paddingVertical != map.end()) { + fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleCitationStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleCitationStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleCitationStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleStruct { + EnrichedMarkdownTextMarkdownStyleParagraphStruct paragraph{}; + EnrichedMarkdownTextMarkdownStyleH1Struct h1{}; + EnrichedMarkdownTextMarkdownStyleH2Struct h2{}; + EnrichedMarkdownTextMarkdownStyleH3Struct h3{}; + EnrichedMarkdownTextMarkdownStyleH4Struct h4{}; + EnrichedMarkdownTextMarkdownStyleH5Struct h5{}; + EnrichedMarkdownTextMarkdownStyleH6Struct h6{}; + EnrichedMarkdownTextMarkdownStyleBlockquoteStruct blockquote{}; + EnrichedMarkdownTextMarkdownStyleListStruct list{}; + EnrichedMarkdownTextMarkdownStyleCodeBlockStruct codeBlock{}; + EnrichedMarkdownTextMarkdownStyleLinkStruct link{}; + EnrichedMarkdownTextMarkdownStyleStrongStruct strong{}; + EnrichedMarkdownTextMarkdownStyleEmStruct em{}; + EnrichedMarkdownTextMarkdownStyleStrikethroughStruct strikethrough{}; + EnrichedMarkdownTextMarkdownStyleUnderlineStruct underline{}; + EnrichedMarkdownTextMarkdownStyleCodeStruct code{}; + EnrichedMarkdownTextMarkdownStyleImageStruct image{}; + EnrichedMarkdownTextMarkdownStyleInlineImageStruct inlineImage{}; + EnrichedMarkdownTextMarkdownStyleThematicBreakStruct thematicBreak{}; + EnrichedMarkdownTextMarkdownStyleTableStruct table{}; + EnrichedMarkdownTextMarkdownStyleTaskListStruct taskList{}; + EnrichedMarkdownTextMarkdownStyleMathStruct math{}; + EnrichedMarkdownTextMarkdownStyleInlineMathStruct inlineMath{}; + EnrichedMarkdownTextMarkdownStyleSpoilerStruct spoiler{}; + EnrichedMarkdownTextMarkdownStyleMentionStruct mention{}; + EnrichedMarkdownTextMarkdownStyleCitationStruct citation{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["paragraph"] = ::facebook::react::toDynamic(paragraph); + result["h1"] = ::facebook::react::toDynamic(h1); + result["h2"] = ::facebook::react::toDynamic(h2); + result["h3"] = ::facebook::react::toDynamic(h3); + result["h4"] = ::facebook::react::toDynamic(h4); + result["h5"] = ::facebook::react::toDynamic(h5); + result["h6"] = ::facebook::react::toDynamic(h6); + result["blockquote"] = ::facebook::react::toDynamic(blockquote); + result["list"] = ::facebook::react::toDynamic(list); + result["codeBlock"] = ::facebook::react::toDynamic(codeBlock); + result["link"] = ::facebook::react::toDynamic(link); + result["strong"] = ::facebook::react::toDynamic(strong); + result["em"] = ::facebook::react::toDynamic(em); + result["strikethrough"] = ::facebook::react::toDynamic(strikethrough); + result["underline"] = ::facebook::react::toDynamic(underline); + result["code"] = ::facebook::react::toDynamic(code); + result["image"] = ::facebook::react::toDynamic(image); + result["inlineImage"] = ::facebook::react::toDynamic(inlineImage); + result["thematicBreak"] = ::facebook::react::toDynamic(thematicBreak); + result["table"] = ::facebook::react::toDynamic(table); + result["taskList"] = ::facebook::react::toDynamic(taskList); + result["math"] = ::facebook::react::toDynamic(math); + result["inlineMath"] = ::facebook::react::toDynamic(inlineMath); + result["spoiler"] = ::facebook::react::toDynamic(spoiler); + result["mention"] = ::facebook::react::toDynamic(mention); + result["citation"] = ::facebook::react::toDynamic(citation); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_paragraph = map.find("paragraph"); + if (tmp_paragraph != map.end()) { + fromRawValue(context, tmp_paragraph->second, result.paragraph); + } + auto tmp_h1 = map.find("h1"); + if (tmp_h1 != map.end()) { + fromRawValue(context, tmp_h1->second, result.h1); + } + auto tmp_h2 = map.find("h2"); + if (tmp_h2 != map.end()) { + fromRawValue(context, tmp_h2->second, result.h2); + } + auto tmp_h3 = map.find("h3"); + if (tmp_h3 != map.end()) { + fromRawValue(context, tmp_h3->second, result.h3); + } + auto tmp_h4 = map.find("h4"); + if (tmp_h4 != map.end()) { + fromRawValue(context, tmp_h4->second, result.h4); + } + auto tmp_h5 = map.find("h5"); + if (tmp_h5 != map.end()) { + fromRawValue(context, tmp_h5->second, result.h5); + } + auto tmp_h6 = map.find("h6"); + if (tmp_h6 != map.end()) { + fromRawValue(context, tmp_h6->second, result.h6); + } + auto tmp_blockquote = map.find("blockquote"); + if (tmp_blockquote != map.end()) { + fromRawValue(context, tmp_blockquote->second, result.blockquote); + } + auto tmp_list = map.find("list"); + if (tmp_list != map.end()) { + fromRawValue(context, tmp_list->second, result.list); + } + auto tmp_codeBlock = map.find("codeBlock"); + if (tmp_codeBlock != map.end()) { + fromRawValue(context, tmp_codeBlock->second, result.codeBlock); + } + auto tmp_link = map.find("link"); + if (tmp_link != map.end()) { + fromRawValue(context, tmp_link->second, result.link); + } + auto tmp_strong = map.find("strong"); + if (tmp_strong != map.end()) { + fromRawValue(context, tmp_strong->second, result.strong); + } + auto tmp_em = map.find("em"); + if (tmp_em != map.end()) { + fromRawValue(context, tmp_em->second, result.em); + } + auto tmp_strikethrough = map.find("strikethrough"); + if (tmp_strikethrough != map.end()) { + fromRawValue(context, tmp_strikethrough->second, result.strikethrough); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_code = map.find("code"); + if (tmp_code != map.end()) { + fromRawValue(context, tmp_code->second, result.code); + } + auto tmp_image = map.find("image"); + if (tmp_image != map.end()) { + fromRawValue(context, tmp_image->second, result.image); + } + auto tmp_inlineImage = map.find("inlineImage"); + if (tmp_inlineImage != map.end()) { + fromRawValue(context, tmp_inlineImage->second, result.inlineImage); + } + auto tmp_thematicBreak = map.find("thematicBreak"); + if (tmp_thematicBreak != map.end()) { + fromRawValue(context, tmp_thematicBreak->second, result.thematicBreak); + } + auto tmp_table = map.find("table"); + if (tmp_table != map.end()) { + fromRawValue(context, tmp_table->second, result.table); + } + auto tmp_taskList = map.find("taskList"); + if (tmp_taskList != map.end()) { + fromRawValue(context, tmp_taskList->second, result.taskList); + } + auto tmp_math = map.find("math"); + if (tmp_math != map.end()) { + fromRawValue(context, tmp_math->second, result.math); + } + auto tmp_inlineMath = map.find("inlineMath"); + if (tmp_inlineMath != map.end()) { + fromRawValue(context, tmp_inlineMath->second, result.inlineMath); + } + auto tmp_spoiler = map.find("spoiler"); + if (tmp_spoiler != map.end()) { + fromRawValue(context, tmp_spoiler->second, result.spoiler); + } + auto tmp_mention = map.find("mention"); + if (tmp_mention != map.end()) { + fromRawValue(context, tmp_mention->second, result.mention); + } + auto tmp_citation = map.find("citation"); + if (tmp_citation != map.end()) { + fromRawValue(context, tmp_citation->second, result.citation); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMd4cFlagsStruct { + bool underline{false}; + bool latexMath{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMd4cFlagsStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["underline"] = underline; + result["latexMath"] = latexMath; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMd4cFlagsStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_latexMath = map.find("latexMath"); + if (tmp_latexMath != map.end()) { + fromRawValue(context, tmp_latexMath->second, result.latexMath); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMd4cFlagsStruct &value) { + return "[Object EnrichedMarkdownTextMd4cFlagsStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMd4cFlagsStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextContextMenuItemsStruct { + std::string text{}; + std::string icon{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextContextMenuItemsStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["text"] = text; + result["icon"] = icon; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextContextMenuItemsStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_text = map.find("text"); + if (tmp_text != map.end()) { + fromRawValue(context, tmp_text->second, result.text); + } + auto tmp_icon = map.find("icon"); + if (tmp_icon != map.end()) { + fromRawValue(context, tmp_icon->second, result.icon); + } +} + +static inline std::string toString(const EnrichedMarkdownTextContextMenuItemsStruct &value) { + return "[Object EnrichedMarkdownTextContextMenuItemsStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextContextMenuItemsStruct &value) { + return value.toDynamic(); +} +#endif + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector &result) { + auto items = (std::vector)value; + for (const auto &item : items) { + EnrichedMarkdownTextContextMenuItemsStruct newItem; + fromRawValue(context, item, newItem); + result.emplace_back(newItem); + } +} + +class EnrichedMarkdownTextProps final : public ViewProps { + public: + EnrichedMarkdownTextProps() = default; + EnrichedMarkdownTextProps(const PropsParserContext& context, const EnrichedMarkdownTextProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + std::string markdown{}; + EnrichedMarkdownTextMarkdownStyleStruct markdownStyle{}; + bool enableLinkPreview{true}; + bool selectable{false}; + SharedColor selectionColor{}; + SharedColor selectionHandleColor{}; + EnrichedMarkdownTextMd4cFlagsStruct md4cFlags{}; + bool allowFontScaling{true}; + Float maxFontSizeMultiplier{0.0}; + bool allowTrailingMargin{false}; + bool streamingAnimation{false}; + std::string spoilerOverlay{std::string{"particles"}}; + std::vector contextMenuItems{}; + + #ifdef RN_SERIALIZABLE_STATE + ComponentName getDiffPropsImplementationTarget() const override; + + folly::dynamic getDiffProps(const Props* prevProps) const override; + #endif + + +}; + +} // namespace facebook::react diff --git a/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp new file mode 100644 index 00000000..feadde33 --- /dev/null +++ b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.cpp @@ -0,0 +1,17 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateShadowNodeCpp.js + */ + +#include "ShadowNodes.h" + +namespace facebook::react { + + + +} // namespace facebook::react diff --git a/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h new file mode 100644 index 00000000..615dfa65 --- /dev/null +++ b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/ShadowNodes.h @@ -0,0 +1,23 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateShadowNodeH.js + */ + +#pragma once + +#include "EventEmitters.h" +#include "Props.h" +#include "States.h" +#include +#include + +namespace facebook::react { + + + +} // namespace facebook::react diff --git a/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp new file mode 100644 index 00000000..1dbb184c --- /dev/null +++ b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.cpp @@ -0,0 +1,16 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateStateCpp.js + */ +#include "States.h" + +namespace facebook::react { + + + +} // namespace facebook::react diff --git a/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h new file mode 100644 index 00000000..2e55bce7 --- /dev/null +++ b/android/generated/jni/react/renderer/components/EnrichedMarkdownTextSpec/States.h @@ -0,0 +1,20 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateStateH.js + */ +#pragma once + +#include +#ifdef RN_SERIALIZABLE_STATE +#include +#endif + +namespace facebook::react { + + + +} // namespace facebook::react \ No newline at end of file diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp new file mode 100644 index 00000000..4d59d519 --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ComponentDescriptors.cpp @@ -0,0 +1,22 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateComponentDescriptorCpp.js + */ + +#include "ComponentDescriptors.h" +#include +#include + +namespace facebook::react { + +void EnrichedMarkdownTextSpec_registerComponentDescriptorsFromCodegen( + std::shared_ptr registry) { + +} + +} // namespace facebook::react diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ComponentDescriptors.h b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ComponentDescriptors.h new file mode 100644 index 00000000..06510880 --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ComponentDescriptors.h @@ -0,0 +1,24 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateComponentDescriptorH.js + */ + +#pragma once + +#include "ShadowNodes.h" +#include +#include + +namespace facebook::react { + + + +void EnrichedMarkdownTextSpec_registerComponentDescriptorsFromCodegen( + std::shared_ptr registry); + +} // namespace facebook::react diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp new file mode 100644 index 00000000..5fe6e87f --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.cpp @@ -0,0 +1,314 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateEventEmitterCpp.js + */ + +#include "EventEmitters.h" + + +namespace facebook::react { + +void EnrichedMarkdownInputEventEmitter::onChangeText(OnChangeText event) const { + dispatchEvent("changeText", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "value", event.value); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onChangeMarkdown(OnChangeMarkdown event) const { + dispatchEvent("changeMarkdown", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "value", event.value); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onChangeSelection(OnChangeSelection event) const { + dispatchEvent("changeSelection", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "start", event.start); +payload.setProperty(runtime, "end", event.end); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onChangeState(OnChangeState event) const { + dispatchEvent("changeState", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + { + auto bold = jsi::Object(runtime); + bold.setProperty(runtime, "isActive", event.bold.isActive); + payload.setProperty(runtime, "bold", bold); +} +{ + auto italic = jsi::Object(runtime); + italic.setProperty(runtime, "isActive", event.italic.isActive); + payload.setProperty(runtime, "italic", italic); +} +{ + auto underline = jsi::Object(runtime); + underline.setProperty(runtime, "isActive", event.underline.isActive); + payload.setProperty(runtime, "underline", underline); +} +{ + auto strikethrough = jsi::Object(runtime); + strikethrough.setProperty(runtime, "isActive", event.strikethrough.isActive); + payload.setProperty(runtime, "strikethrough", strikethrough); +} +{ + auto spoiler = jsi::Object(runtime); + spoiler.setProperty(runtime, "isActive", event.spoiler.isActive); + payload.setProperty(runtime, "spoiler", spoiler); +} +{ + auto link = jsi::Object(runtime); + link.setProperty(runtime, "isActive", event.link.isActive); + payload.setProperty(runtime, "link", link); +} + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onInputFocus(OnInputFocus event) const { + dispatchEvent("inputFocus", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onInputBlur(OnInputBlur event) const { + dispatchEvent("inputBlur", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "target", event.target); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onRequestMarkdownResult(OnRequestMarkdownResult event) const { + dispatchEvent("requestMarkdownResult", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "requestId", event.requestId); +payload.setProperty(runtime, "markdown", event.markdown); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onRequestCaretRectResult(OnRequestCaretRectResult event) const { + dispatchEvent("requestCaretRectResult", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "requestId", event.requestId); +payload.setProperty(runtime, "x", event.x); +payload.setProperty(runtime, "y", event.y); +payload.setProperty(runtime, "width", event.width); +payload.setProperty(runtime, "height", event.height); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onCaretRectChange(OnCaretRectChange event) const { + dispatchEvent("caretRectChange", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "x", event.x); +payload.setProperty(runtime, "y", event.y); +payload.setProperty(runtime, "width", event.width); +payload.setProperty(runtime, "height", event.height); + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onContextMenuItemPress(OnContextMenuItemPress event) const { + dispatchEvent("contextMenuItemPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "itemText", event.itemText); +payload.setProperty(runtime, "selectedText", event.selectedText); +payload.setProperty(runtime, "selectionStart", event.selectionStart); +payload.setProperty(runtime, "selectionEnd", event.selectionEnd); +{ + auto styleState = jsi::Object(runtime); + { + auto bold = jsi::Object(runtime); + bold.setProperty(runtime, "isActive", event.styleState.bold.isActive); + styleState.setProperty(runtime, "bold", bold); + } + { + auto italic = jsi::Object(runtime); + italic.setProperty(runtime, "isActive", event.styleState.italic.isActive); + styleState.setProperty(runtime, "italic", italic); + } + { + auto underline = jsi::Object(runtime); + underline.setProperty(runtime, "isActive", event.styleState.underline.isActive); + styleState.setProperty(runtime, "underline", underline); + } + { + auto strikethrough = jsi::Object(runtime); + strikethrough.setProperty(runtime, "isActive", event.styleState.strikethrough.isActive); + styleState.setProperty(runtime, "strikethrough", strikethrough); + } + { + auto spoiler = jsi::Object(runtime); + spoiler.setProperty(runtime, "isActive", event.styleState.spoiler.isActive); + styleState.setProperty(runtime, "spoiler", spoiler); + } + { + auto link = jsi::Object(runtime); + link.setProperty(runtime, "isActive", event.styleState.link.isActive); + styleState.setProperty(runtime, "link", link); + } + payload.setProperty(runtime, "styleState", styleState); +} + return payload; + }); +} + + +void EnrichedMarkdownInputEventEmitter::onLinkDetected(OnLinkDetected event) const { + dispatchEvent("linkDetected", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "text", event.text); +payload.setProperty(runtime, "url", event.url); +payload.setProperty(runtime, "start", event.start); +payload.setProperty(runtime, "end", event.end); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onLinkPress(OnLinkPress event) const { + dispatchEvent("linkPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onLinkLongPress(OnLinkLongPress event) const { + dispatchEvent("linkLongPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onTaskListItemPress(OnTaskListItemPress event) const { + dispatchEvent("taskListItemPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "index", event.index); +payload.setProperty(runtime, "checked", event.checked); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onMentionPress(OnMentionPress event) const { + dispatchEvent("mentionPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onCitationPress(OnCitationPress event) const { + dispatchEvent("citationPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownEventEmitter::onContextMenuItemPress(OnContextMenuItemPress event) const { + dispatchEvent("contextMenuItemPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "itemText", event.itemText); +payload.setProperty(runtime, "selectedText", event.selectedText); +payload.setProperty(runtime, "selectionStart", event.selectionStart); +payload.setProperty(runtime, "selectionEnd", event.selectionEnd); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onLinkPress(OnLinkPress event) const { + dispatchEvent("linkPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onLinkLongPress(OnLinkLongPress event) const { + dispatchEvent("linkLongPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onTaskListItemPress(OnTaskListItemPress event) const { + dispatchEvent("taskListItemPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "index", event.index); +payload.setProperty(runtime, "checked", event.checked); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onMentionPress(OnMentionPress event) const { + dispatchEvent("mentionPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onCitationPress(OnCitationPress event) const { + dispatchEvent("citationPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "url", event.url); +payload.setProperty(runtime, "text", event.text); + return payload; + }); +} + + +void EnrichedMarkdownTextEventEmitter::onContextMenuItemPress(OnContextMenuItemPress event) const { + dispatchEvent("contextMenuItemPress", [event=std::move(event)](jsi::Runtime &runtime) { + auto payload = jsi::Object(runtime); + payload.setProperty(runtime, "itemText", event.itemText); +payload.setProperty(runtime, "selectedText", event.selectedText); +payload.setProperty(runtime, "selectionStart", event.selectionStart); +payload.setProperty(runtime, "selectionEnd", event.selectionEnd); + return payload; + }); +} + +} // namespace facebook::react diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h new file mode 100644 index 00000000..8896a9bd --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/EventEmitters.h @@ -0,0 +1,255 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateEventEmitterH.js + */ +#pragma once + +#include + + +namespace facebook::react { +class EnrichedMarkdownInputEventEmitter : public ViewEventEmitter { + public: + using ViewEventEmitter::ViewEventEmitter; + + struct OnChangeText { + std::string value; + }; + + struct OnChangeMarkdown { + std::string value; + }; + + struct OnChangeSelection { + int start; + int end; + }; + + struct OnChangeStateBold { + bool isActive; + }; + + struct OnChangeStateItalic { + bool isActive; + }; + + struct OnChangeStateUnderline { + bool isActive; + }; + + struct OnChangeStateStrikethrough { + bool isActive; + }; + + struct OnChangeStateSpoiler { + bool isActive; + }; + + struct OnChangeStateLink { + bool isActive; + }; + + struct OnChangeState { + OnChangeStateBold bold; + OnChangeStateItalic italic; + OnChangeStateUnderline underline; + OnChangeStateStrikethrough strikethrough; + OnChangeStateSpoiler spoiler; + OnChangeStateLink link; + }; + + struct OnInputFocus { + int target; + }; + + struct OnInputBlur { + int target; + }; + + struct OnRequestMarkdownResult { + int requestId; + std::string markdown; + }; + + struct OnRequestCaretRectResult { + int requestId; + double x; + double y; + double width; + double height; + }; + + struct OnCaretRectChange { + double x; + double y; + double width; + double height; + }; + + struct OnContextMenuItemPressStyleStateBold { + bool isActive; + }; + + struct OnContextMenuItemPressStyleStateItalic { + bool isActive; + }; + + struct OnContextMenuItemPressStyleStateUnderline { + bool isActive; + }; + + struct OnContextMenuItemPressStyleStateStrikethrough { + bool isActive; + }; + + struct OnContextMenuItemPressStyleStateSpoiler { + bool isActive; + }; + + struct OnContextMenuItemPressStyleStateLink { + bool isActive; + }; + + struct OnContextMenuItemPressStyleState { + OnContextMenuItemPressStyleStateBold bold; + OnContextMenuItemPressStyleStateItalic italic; + OnContextMenuItemPressStyleStateUnderline underline; + OnContextMenuItemPressStyleStateStrikethrough strikethrough; + OnContextMenuItemPressStyleStateSpoiler spoiler; + OnContextMenuItemPressStyleStateLink link; + }; + + struct OnContextMenuItemPress { + std::string itemText; + std::string selectedText; + int selectionStart; + int selectionEnd; + OnContextMenuItemPressStyleState styleState; + }; + + struct OnLinkDetected { + std::string text; + std::string url; + int start; + int end; + }; + void onChangeText(OnChangeText value) const; + + void onChangeMarkdown(OnChangeMarkdown value) const; + + void onChangeSelection(OnChangeSelection value) const; + + void onChangeState(OnChangeState value) const; + + void onInputFocus(OnInputFocus value) const; + + void onInputBlur(OnInputBlur value) const; + + void onRequestMarkdownResult(OnRequestMarkdownResult value) const; + + void onRequestCaretRectResult(OnRequestCaretRectResult value) const; + + void onCaretRectChange(OnCaretRectChange value) const; + + void onContextMenuItemPress(OnContextMenuItemPress value) const; + + void onLinkDetected(OnLinkDetected value) const; +}; +class EnrichedMarkdownEventEmitter : public ViewEventEmitter { + public: + using ViewEventEmitter::ViewEventEmitter; + + struct OnLinkPress { + std::string url; + }; + + struct OnLinkLongPress { + std::string url; + }; + + struct OnTaskListItemPress { + int index; + bool checked; + std::string text; + }; + + struct OnMentionPress { + std::string url; + std::string text; + }; + + struct OnCitationPress { + std::string url; + std::string text; + }; + + struct OnContextMenuItemPress { + std::string itemText; + std::string selectedText; + int selectionStart; + int selectionEnd; + }; + void onLinkPress(OnLinkPress value) const; + + void onLinkLongPress(OnLinkLongPress value) const; + + void onTaskListItemPress(OnTaskListItemPress value) const; + + void onMentionPress(OnMentionPress value) const; + + void onCitationPress(OnCitationPress value) const; + + void onContextMenuItemPress(OnContextMenuItemPress value) const; +}; +class EnrichedMarkdownTextEventEmitter : public ViewEventEmitter { + public: + using ViewEventEmitter::ViewEventEmitter; + + struct OnLinkPress { + std::string url; + }; + + struct OnLinkLongPress { + std::string url; + }; + + struct OnTaskListItemPress { + int index; + bool checked; + std::string text; + }; + + struct OnMentionPress { + std::string url; + std::string text; + }; + + struct OnCitationPress { + std::string url; + std::string text; + }; + + struct OnContextMenuItemPress { + std::string itemText; + std::string selectedText; + int selectionStart; + int selectionEnd; + }; + void onLinkPress(OnLinkPress value) const; + + void onLinkLongPress(OnLinkLongPress value) const; + + void onTaskListItemPress(OnTaskListItemPress value) const; + + void onMentionPress(OnMentionPress value) const; + + void onCitationPress(OnCitationPress value) const; + + void onContextMenuItemPress(OnContextMenuItemPress value) const; +}; +} // namespace facebook::react diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp new file mode 100644 index 00000000..bed5a95d --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.cpp @@ -0,0 +1,315 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GeneratePropsCpp.js + */ + +#include "Props.h" +#include +#include + +namespace facebook::react { + +EnrichedMarkdownInputProps::EnrichedMarkdownInputProps( + const PropsParserContext &context, + const EnrichedMarkdownInputProps &sourceProps, + const RawProps &rawProps): ViewProps(context, sourceProps, rawProps), + + defaultValue(convertRawProp(context, rawProps, "defaultValue", sourceProps.defaultValue, {})), + placeholder(convertRawProp(context, rawProps, "placeholder", sourceProps.placeholder, {})), + placeholderTextColor(convertRawProp(context, rawProps, "placeholderTextColor", sourceProps.placeholderTextColor, {})), + editable(convertRawProp(context, rawProps, "editable", sourceProps.editable, {false})), + autoFocus(convertRawProp(context, rawProps, "autoFocus", sourceProps.autoFocus, {false})), + scrollEnabled(convertRawProp(context, rawProps, "scrollEnabled", sourceProps.scrollEnabled, {false})), + autoCapitalize(convertRawProp(context, rawProps, "autoCapitalize", sourceProps.autoCapitalize, {})), + multiline(convertRawProp(context, rawProps, "multiline", sourceProps.multiline, {false})), + cursorColor(convertRawProp(context, rawProps, "cursorColor", sourceProps.cursorColor, {})), + selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})), + markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})), + color(convertRawProp(context, rawProps, "color", sourceProps.color, {})), + fontSize(convertRawProp(context, rawProps, "fontSize", sourceProps.fontSize, {0.0})), + lineHeight(convertRawProp(context, rawProps, "lineHeight", sourceProps.lineHeight, {0.0})), + fontFamily(convertRawProp(context, rawProps, "fontFamily", sourceProps.fontFamily, {})), + fontWeight(convertRawProp(context, rawProps, "fontWeight", sourceProps.fontWeight, {})), + isOnChangeMarkdownSet(convertRawProp(context, rawProps, "isOnChangeMarkdownSet", sourceProps.isOnChangeMarkdownSet, {false})), + contextMenuItems(convertRawProp(context, rawProps, "contextMenuItems", sourceProps.contextMenuItems, {})), + linkRegex(convertRawProp(context, rawProps, "linkRegex", sourceProps.linkRegex, {})) {} + +#ifdef RN_SERIALIZABLE_STATE +ComponentName EnrichedMarkdownInputProps::getDiffPropsImplementationTarget() const { + return "EnrichedMarkdownInput"; +} + +folly::dynamic EnrichedMarkdownInputProps::getDiffProps( + const Props* prevProps) const { + static const auto defaultProps = EnrichedMarkdownInputProps(); + const EnrichedMarkdownInputProps* oldProps = prevProps == nullptr + ? &defaultProps + : static_cast(prevProps); + if (this == oldProps) { + return folly::dynamic::object(); + } + folly::dynamic result = HostPlatformViewProps::getDiffProps(prevProps); + + if (defaultValue != oldProps->defaultValue) { + result["defaultValue"] = defaultValue; + } + + if (placeholder != oldProps->placeholder) { + result["placeholder"] = placeholder; + } + + if (placeholderTextColor != oldProps->placeholderTextColor) { + result["placeholderTextColor"] = *placeholderTextColor; + } + + if (editable != oldProps->editable) { + result["editable"] = editable; + } + + if (autoFocus != oldProps->autoFocus) { + result["autoFocus"] = autoFocus; + } + + if (scrollEnabled != oldProps->scrollEnabled) { + result["scrollEnabled"] = scrollEnabled; + } + + if (autoCapitalize != oldProps->autoCapitalize) { + result["autoCapitalize"] = autoCapitalize; + } + + if (multiline != oldProps->multiline) { + result["multiline"] = multiline; + } + + if (cursorColor != oldProps->cursorColor) { + result["cursorColor"] = *cursorColor; + } + + if (selectionColor != oldProps->selectionColor) { + result["selectionColor"] = *selectionColor; + } + + if (markdownStyle != oldProps->markdownStyle) { + result["markdownStyle"] = toDynamic(markdownStyle); + } + + if (color != oldProps->color) { + result["color"] = *color; + } + + if ((fontSize != oldProps->fontSize) && !(std::isnan(fontSize) && std::isnan(oldProps->fontSize))) { + result["fontSize"] = fontSize; + } + + if ((lineHeight != oldProps->lineHeight) && !(std::isnan(lineHeight) && std::isnan(oldProps->lineHeight))) { + result["lineHeight"] = lineHeight; + } + + if (fontFamily != oldProps->fontFamily) { + result["fontFamily"] = fontFamily; + } + + if (fontWeight != oldProps->fontWeight) { + result["fontWeight"] = fontWeight; + } + + if (isOnChangeMarkdownSet != oldProps->isOnChangeMarkdownSet) { + result["isOnChangeMarkdownSet"] = isOnChangeMarkdownSet; + } + + if (contextMenuItems != oldProps->contextMenuItems) { + result["contextMenuItems"] = toDynamic(contextMenuItems); + } + + if (linkRegex != oldProps->linkRegex) { + result["linkRegex"] = toDynamic(linkRegex); + } + return result; +} +#endif +EnrichedMarkdownProps::EnrichedMarkdownProps( + const PropsParserContext &context, + const EnrichedMarkdownProps &sourceProps, + const RawProps &rawProps): ViewProps(context, sourceProps, rawProps), + + markdown(convertRawProp(context, rawProps, "markdown", sourceProps.markdown, {})), + markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})), + enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})), + selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})), + selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})), + selectionHandleColor(convertRawProp(context, rawProps, "selectionHandleColor", sourceProps.selectionHandleColor, {})), + md4cFlags(convertRawProp(context, rawProps, "md4cFlags", sourceProps.md4cFlags, {})), + allowFontScaling(convertRawProp(context, rawProps, "allowFontScaling", sourceProps.allowFontScaling, {true})), + maxFontSizeMultiplier(convertRawProp(context, rawProps, "maxFontSizeMultiplier", sourceProps.maxFontSizeMultiplier, {0.0})), + allowTrailingMargin(convertRawProp(context, rawProps, "allowTrailingMargin", sourceProps.allowTrailingMargin, {false})), + streamingAnimation(convertRawProp(context, rawProps, "streamingAnimation", sourceProps.streamingAnimation, {false})), + spoilerOverlay(convertRawProp(context, rawProps, "spoilerOverlay", sourceProps.spoilerOverlay, {std::string{"particles"}})), + contextMenuItems(convertRawProp(context, rawProps, "contextMenuItems", sourceProps.contextMenuItems, {})) {} + +#ifdef RN_SERIALIZABLE_STATE +ComponentName EnrichedMarkdownProps::getDiffPropsImplementationTarget() const { + return "EnrichedMarkdown"; +} + +folly::dynamic EnrichedMarkdownProps::getDiffProps( + const Props* prevProps) const { + static const auto defaultProps = EnrichedMarkdownProps(); + const EnrichedMarkdownProps* oldProps = prevProps == nullptr + ? &defaultProps + : static_cast(prevProps); + if (this == oldProps) { + return folly::dynamic::object(); + } + folly::dynamic result = HostPlatformViewProps::getDiffProps(prevProps); + + if (markdown != oldProps->markdown) { + result["markdown"] = markdown; + } + + if (markdownStyle != oldProps->markdownStyle) { + result["markdownStyle"] = toDynamic(markdownStyle); + } + + if (enableLinkPreview != oldProps->enableLinkPreview) { + result["enableLinkPreview"] = enableLinkPreview; + } + + if (selectable != oldProps->selectable) { + result["selectable"] = selectable; + } + + if (selectionColor != oldProps->selectionColor) { + result["selectionColor"] = *selectionColor; + } + + if (selectionHandleColor != oldProps->selectionHandleColor) { + result["selectionHandleColor"] = *selectionHandleColor; + } + + if (md4cFlags != oldProps->md4cFlags) { + result["md4cFlags"] = toDynamic(md4cFlags); + } + + if (allowFontScaling != oldProps->allowFontScaling) { + result["allowFontScaling"] = allowFontScaling; + } + + if ((maxFontSizeMultiplier != oldProps->maxFontSizeMultiplier) && !(std::isnan(maxFontSizeMultiplier) && std::isnan(oldProps->maxFontSizeMultiplier))) { + result["maxFontSizeMultiplier"] = maxFontSizeMultiplier; + } + + if (allowTrailingMargin != oldProps->allowTrailingMargin) { + result["allowTrailingMargin"] = allowTrailingMargin; + } + + if (streamingAnimation != oldProps->streamingAnimation) { + result["streamingAnimation"] = streamingAnimation; + } + + if (spoilerOverlay != oldProps->spoilerOverlay) { + result["spoilerOverlay"] = spoilerOverlay; + } + + if (contextMenuItems != oldProps->contextMenuItems) { + result["contextMenuItems"] = toDynamic(contextMenuItems); + } + return result; +} +#endif +EnrichedMarkdownTextProps::EnrichedMarkdownTextProps( + const PropsParserContext &context, + const EnrichedMarkdownTextProps &sourceProps, + const RawProps &rawProps): ViewProps(context, sourceProps, rawProps), + + markdown(convertRawProp(context, rawProps, "markdown", sourceProps.markdown, {})), + markdownStyle(convertRawProp(context, rawProps, "markdownStyle", sourceProps.markdownStyle, {})), + enableLinkPreview(convertRawProp(context, rawProps, "enableLinkPreview", sourceProps.enableLinkPreview, {true})), + selectable(convertRawProp(context, rawProps, "selectable", sourceProps.selectable, {false})), + selectionColor(convertRawProp(context, rawProps, "selectionColor", sourceProps.selectionColor, {})), + selectionHandleColor(convertRawProp(context, rawProps, "selectionHandleColor", sourceProps.selectionHandleColor, {})), + md4cFlags(convertRawProp(context, rawProps, "md4cFlags", sourceProps.md4cFlags, {})), + allowFontScaling(convertRawProp(context, rawProps, "allowFontScaling", sourceProps.allowFontScaling, {true})), + maxFontSizeMultiplier(convertRawProp(context, rawProps, "maxFontSizeMultiplier", sourceProps.maxFontSizeMultiplier, {0.0})), + allowTrailingMargin(convertRawProp(context, rawProps, "allowTrailingMargin", sourceProps.allowTrailingMargin, {false})), + streamingAnimation(convertRawProp(context, rawProps, "streamingAnimation", sourceProps.streamingAnimation, {false})), + spoilerOverlay(convertRawProp(context, rawProps, "spoilerOverlay", sourceProps.spoilerOverlay, {std::string{"particles"}})), + contextMenuItems(convertRawProp(context, rawProps, "contextMenuItems", sourceProps.contextMenuItems, {})) {} + +#ifdef RN_SERIALIZABLE_STATE +ComponentName EnrichedMarkdownTextProps::getDiffPropsImplementationTarget() const { + return "EnrichedMarkdownText"; +} + +folly::dynamic EnrichedMarkdownTextProps::getDiffProps( + const Props* prevProps) const { + static const auto defaultProps = EnrichedMarkdownTextProps(); + const EnrichedMarkdownTextProps* oldProps = prevProps == nullptr + ? &defaultProps + : static_cast(prevProps); + if (this == oldProps) { + return folly::dynamic::object(); + } + folly::dynamic result = HostPlatformViewProps::getDiffProps(prevProps); + + if (markdown != oldProps->markdown) { + result["markdown"] = markdown; + } + + if (markdownStyle != oldProps->markdownStyle) { + result["markdownStyle"] = toDynamic(markdownStyle); + } + + if (enableLinkPreview != oldProps->enableLinkPreview) { + result["enableLinkPreview"] = enableLinkPreview; + } + + if (selectable != oldProps->selectable) { + result["selectable"] = selectable; + } + + if (selectionColor != oldProps->selectionColor) { + result["selectionColor"] = *selectionColor; + } + + if (selectionHandleColor != oldProps->selectionHandleColor) { + result["selectionHandleColor"] = *selectionHandleColor; + } + + if (md4cFlags != oldProps->md4cFlags) { + result["md4cFlags"] = toDynamic(md4cFlags); + } + + if (allowFontScaling != oldProps->allowFontScaling) { + result["allowFontScaling"] = allowFontScaling; + } + + if ((maxFontSizeMultiplier != oldProps->maxFontSizeMultiplier) && !(std::isnan(maxFontSizeMultiplier) && std::isnan(oldProps->maxFontSizeMultiplier))) { + result["maxFontSizeMultiplier"] = maxFontSizeMultiplier; + } + + if (allowTrailingMargin != oldProps->allowTrailingMargin) { + result["allowTrailingMargin"] = allowTrailingMargin; + } + + if (streamingAnimation != oldProps->streamingAnimation) { + result["streamingAnimation"] = streamingAnimation; + } + + if (spoilerOverlay != oldProps->spoilerOverlay) { + result["spoilerOverlay"] = spoilerOverlay; + } + + if (contextMenuItems != oldProps->contextMenuItems) { + result["contextMenuItems"] = toDynamic(contextMenuItems); + } + return result; +} +#endif + +} // namespace facebook::react diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h new file mode 100644 index 00000000..be040887 --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/Props.h @@ -0,0 +1,4662 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GeneratePropsH.js + */ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace facebook::react { + +struct EnrichedMarkdownInputMarkdownStyleStrongStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputMarkdownStyleStrongStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputMarkdownStyleStrongStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownInputMarkdownStyleStrongStruct &value) { + return "[Object EnrichedMarkdownInputMarkdownStyleStrongStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputMarkdownStyleStrongStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownInputMarkdownStyleEmStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputMarkdownStyleEmStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputMarkdownStyleEmStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownInputMarkdownStyleEmStruct &value) { + return "[Object EnrichedMarkdownInputMarkdownStyleEmStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputMarkdownStyleEmStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownInputMarkdownStyleLinkStruct { + SharedColor color{}; + bool underline{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputMarkdownStyleLinkStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["underline"] = underline; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputMarkdownStyleLinkStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } +} + +static inline std::string toString(const EnrichedMarkdownInputMarkdownStyleLinkStruct &value) { + return "[Object EnrichedMarkdownInputMarkdownStyleLinkStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputMarkdownStyleLinkStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownInputMarkdownStyleSpoilerStruct { + SharedColor color{}; + SharedColor backgroundColor{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputMarkdownStyleSpoilerStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputMarkdownStyleSpoilerStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } +} + +static inline std::string toString(const EnrichedMarkdownInputMarkdownStyleSpoilerStruct &value) { + return "[Object EnrichedMarkdownInputMarkdownStyleSpoilerStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputMarkdownStyleSpoilerStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownInputMarkdownStyleStruct { + EnrichedMarkdownInputMarkdownStyleStrongStruct strong{}; + EnrichedMarkdownInputMarkdownStyleEmStruct em{}; + EnrichedMarkdownInputMarkdownStyleLinkStruct link{}; + EnrichedMarkdownInputMarkdownStyleSpoilerStruct spoiler{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputMarkdownStyleStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["strong"] = ::facebook::react::toDynamic(strong); + result["em"] = ::facebook::react::toDynamic(em); + result["link"] = ::facebook::react::toDynamic(link); + result["spoiler"] = ::facebook::react::toDynamic(spoiler); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputMarkdownStyleStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_strong = map.find("strong"); + if (tmp_strong != map.end()) { + fromRawValue(context, tmp_strong->second, result.strong); + } + auto tmp_em = map.find("em"); + if (tmp_em != map.end()) { + fromRawValue(context, tmp_em->second, result.em); + } + auto tmp_link = map.find("link"); + if (tmp_link != map.end()) { + fromRawValue(context, tmp_link->second, result.link); + } + auto tmp_spoiler = map.find("spoiler"); + if (tmp_spoiler != map.end()) { + fromRawValue(context, tmp_spoiler->second, result.spoiler); + } +} + +static inline std::string toString(const EnrichedMarkdownInputMarkdownStyleStruct &value) { + return "[Object EnrichedMarkdownInputMarkdownStyleStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputMarkdownStyleStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownInputContextMenuItemsStruct { + std::string text{}; + std::string icon{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputContextMenuItemsStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["text"] = text; + result["icon"] = icon; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputContextMenuItemsStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_text = map.find("text"); + if (tmp_text != map.end()) { + fromRawValue(context, tmp_text->second, result.text); + } + auto tmp_icon = map.find("icon"); + if (tmp_icon != map.end()) { + fromRawValue(context, tmp_icon->second, result.icon); + } +} + +static inline std::string toString(const EnrichedMarkdownInputContextMenuItemsStruct &value) { + return "[Object EnrichedMarkdownInputContextMenuItemsStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputContextMenuItemsStruct &value) { + return value.toDynamic(); +} +#endif + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector &result) { + auto items = (std::vector)value; + for (const auto &item : items) { + EnrichedMarkdownInputContextMenuItemsStruct newItem; + fromRawValue(context, item, newItem); + result.emplace_back(newItem); + } +} + + +struct EnrichedMarkdownInputLinkRegexStruct { + std::string pattern{}; + bool caseInsensitive{false}; + bool dotAll{false}; + bool isDisabled{false}; + bool isDefault{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownInputLinkRegexStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["pattern"] = pattern; + result["caseInsensitive"] = caseInsensitive; + result["dotAll"] = dotAll; + result["isDisabled"] = isDisabled; + result["isDefault"] = isDefault; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownInputLinkRegexStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_pattern = map.find("pattern"); + if (tmp_pattern != map.end()) { + fromRawValue(context, tmp_pattern->second, result.pattern); + } + auto tmp_caseInsensitive = map.find("caseInsensitive"); + if (tmp_caseInsensitive != map.end()) { + fromRawValue(context, tmp_caseInsensitive->second, result.caseInsensitive); + } + auto tmp_dotAll = map.find("dotAll"); + if (tmp_dotAll != map.end()) { + fromRawValue(context, tmp_dotAll->second, result.dotAll); + } + auto tmp_isDisabled = map.find("isDisabled"); + if (tmp_isDisabled != map.end()) { + fromRawValue(context, tmp_isDisabled->second, result.isDisabled); + } + auto tmp_isDefault = map.find("isDefault"); + if (tmp_isDefault != map.end()) { + fromRawValue(context, tmp_isDefault->second, result.isDefault); + } +} + +static inline std::string toString(const EnrichedMarkdownInputLinkRegexStruct &value) { + return "[Object EnrichedMarkdownInputLinkRegexStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownInputLinkRegexStruct &value) { + return value.toDynamic(); +} +#endif +class EnrichedMarkdownInputProps final : public ViewProps { + public: + EnrichedMarkdownInputProps() = default; + EnrichedMarkdownInputProps(const PropsParserContext& context, const EnrichedMarkdownInputProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + std::string defaultValue{}; + std::string placeholder{}; + SharedColor placeholderTextColor{}; + bool editable{false}; + bool autoFocus{false}; + bool scrollEnabled{false}; + std::string autoCapitalize{}; + bool multiline{false}; + SharedColor cursorColor{}; + SharedColor selectionColor{}; + EnrichedMarkdownInputMarkdownStyleStruct markdownStyle{}; + SharedColor color{}; + Float fontSize{0.0}; + Float lineHeight{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + bool isOnChangeMarkdownSet{false}; + std::vector contextMenuItems{}; + EnrichedMarkdownInputLinkRegexStruct linkRegex{}; + + #ifdef RN_SERIALIZABLE_STATE + ComponentName getDiffPropsImplementationTarget() const override; + + folly::dynamic getDiffProps(const Props* prevProps) const override; + #endif + + +}; + +struct EnrichedMarkdownMarkdownStyleParagraphStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleParagraphStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleParagraphStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleParagraphStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleParagraphStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleParagraphStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH1Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH1Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH1Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH1Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH1Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH1Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH2Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH2Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH2Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH2Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH2Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH2Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH3Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH3Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH3Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH3Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH3Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH3Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH4Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH4Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH4Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH4Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH4Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH4Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH5Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH5Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH5Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH5Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH5Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH5Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleH6Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleH6Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleH6Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleH6Struct &value) { + return "[Object EnrichedMarkdownMarkdownStyleH6Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleH6Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleBlockquoteStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float gapWidth{0.0}; + SharedColor backgroundColor{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleBlockquoteStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["gapWidth"] = gapWidth; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleBlockquoteStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_gapWidth = map.find("gapWidth"); + if (tmp_gapWidth != map.end()) { + fromRawValue(context, tmp_gapWidth->second, result.gapWidth); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleBlockquoteStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleBlockquoteStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleBlockquoteStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleListStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor bulletColor{}; + Float bulletSize{0.0}; + SharedColor markerColor{}; + std::string markerFontWeight{}; + Float gapWidth{0.0}; + Float marginLeft{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleListStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["bulletColor"] = ::facebook::react::toDynamic(bulletColor); + result["bulletSize"] = bulletSize; + result["markerColor"] = ::facebook::react::toDynamic(markerColor); + result["markerFontWeight"] = markerFontWeight; + result["gapWidth"] = gapWidth; + result["marginLeft"] = marginLeft; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleListStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_bulletColor = map.find("bulletColor"); + if (tmp_bulletColor != map.end()) { + fromRawValue(context, tmp_bulletColor->second, result.bulletColor); + } + auto tmp_bulletSize = map.find("bulletSize"); + if (tmp_bulletSize != map.end()) { + fromRawValue(context, tmp_bulletSize->second, result.bulletSize); + } + auto tmp_markerColor = map.find("markerColor"); + if (tmp_markerColor != map.end()) { + fromRawValue(context, tmp_markerColor->second, result.markerColor); + } + auto tmp_markerFontWeight = map.find("markerFontWeight"); + if (tmp_markerFontWeight != map.end()) { + fromRawValue(context, tmp_markerFontWeight->second, result.markerFontWeight); + } + auto tmp_gapWidth = map.find("gapWidth"); + if (tmp_gapWidth != map.end()) { + fromRawValue(context, tmp_gapWidth->second, result.gapWidth); + } + auto tmp_marginLeft = map.find("marginLeft"); + if (tmp_marginLeft != map.end()) { + fromRawValue(context, tmp_marginLeft->second, result.marginLeft); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleListStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleListStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleListStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleCodeBlockStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + Float borderRadius{0.0}; + Float borderWidth{0.0}; + Float padding{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleCodeBlockStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderRadius"] = borderRadius; + result["borderWidth"] = borderWidth; + result["padding"] = padding; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleCodeBlockStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_padding = map.find("padding"); + if (tmp_padding != map.end()) { + fromRawValue(context, tmp_padding->second, result.padding); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleCodeBlockStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleCodeBlockStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleCodeBlockStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleLinkStruct { + std::string fontFamily{}; + SharedColor color{}; + bool underline{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleLinkStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["color"] = ::facebook::react::toDynamic(color); + result["underline"] = underline; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleLinkStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleLinkStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleLinkStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleLinkStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleStrongStruct { + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleStrongStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleStrongStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleStrongStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleStrongStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleStrongStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleEmStruct { + std::string fontFamily{}; + std::string fontStyle{}; + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleEmStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontStyle"] = fontStyle; + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleEmStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontStyle = map.find("fontStyle"); + if (tmp_fontStyle != map.end()) { + fromRawValue(context, tmp_fontStyle->second, result.fontStyle); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleEmStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleEmStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleEmStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleStrikethroughStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleStrikethroughStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleStrikethroughStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleStrikethroughStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleStrikethroughStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleStrikethroughStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleUnderlineStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleUnderlineStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleUnderlineStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleUnderlineStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleUnderlineStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleUnderlineStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleCodeStruct { + std::string fontFamily{}; + Float fontSize{0.0}; + SharedColor color{}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleCodeStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontSize"] = fontSize; + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleCodeStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleCodeStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleCodeStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleCodeStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleImageStruct { + Float height{0.0}; + Float borderRadius{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleImageStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["height"] = height; + result["borderRadius"] = borderRadius; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleImageStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_height = map.find("height"); + if (tmp_height != map.end()) { + fromRawValue(context, tmp_height->second, result.height); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleImageStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleImageStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleImageStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleInlineImageStruct { + Float size{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleInlineImageStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["size"] = size; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleInlineImageStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_size = map.find("size"); + if (tmp_size != map.end()) { + fromRawValue(context, tmp_size->second, result.size); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleInlineImageStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleInlineImageStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleInlineImageStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleThematicBreakStruct { + SharedColor color{}; + Float height{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleThematicBreakStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["height"] = height; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleThematicBreakStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_height = map.find("height"); + if (tmp_height != map.end()) { + fromRawValue(context, tmp_height->second, result.height); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleThematicBreakStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleThematicBreakStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleThematicBreakStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleTableStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string headerFontFamily{}; + SharedColor headerBackgroundColor{}; + SharedColor headerTextColor{}; + SharedColor rowEvenBackgroundColor{}; + SharedColor rowOddBackgroundColor{}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + Float cellPaddingHorizontal{0.0}; + Float cellPaddingVertical{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleTableStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["headerFontFamily"] = headerFontFamily; + result["headerBackgroundColor"] = ::facebook::react::toDynamic(headerBackgroundColor); + result["headerTextColor"] = ::facebook::react::toDynamic(headerTextColor); + result["rowEvenBackgroundColor"] = ::facebook::react::toDynamic(rowEvenBackgroundColor); + result["rowOddBackgroundColor"] = ::facebook::react::toDynamic(rowOddBackgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + result["cellPaddingHorizontal"] = cellPaddingHorizontal; + result["cellPaddingVertical"] = cellPaddingVertical; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleTableStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_headerFontFamily = map.find("headerFontFamily"); + if (tmp_headerFontFamily != map.end()) { + fromRawValue(context, tmp_headerFontFamily->second, result.headerFontFamily); + } + auto tmp_headerBackgroundColor = map.find("headerBackgroundColor"); + if (tmp_headerBackgroundColor != map.end()) { + fromRawValue(context, tmp_headerBackgroundColor->second, result.headerBackgroundColor); + } + auto tmp_headerTextColor = map.find("headerTextColor"); + if (tmp_headerTextColor != map.end()) { + fromRawValue(context, tmp_headerTextColor->second, result.headerTextColor); + } + auto tmp_rowEvenBackgroundColor = map.find("rowEvenBackgroundColor"); + if (tmp_rowEvenBackgroundColor != map.end()) { + fromRawValue(context, tmp_rowEvenBackgroundColor->second, result.rowEvenBackgroundColor); + } + auto tmp_rowOddBackgroundColor = map.find("rowOddBackgroundColor"); + if (tmp_rowOddBackgroundColor != map.end()) { + fromRawValue(context, tmp_rowOddBackgroundColor->second, result.rowOddBackgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_cellPaddingHorizontal = map.find("cellPaddingHorizontal"); + if (tmp_cellPaddingHorizontal != map.end()) { + fromRawValue(context, tmp_cellPaddingHorizontal->second, result.cellPaddingHorizontal); + } + auto tmp_cellPaddingVertical = map.find("cellPaddingVertical"); + if (tmp_cellPaddingVertical != map.end()) { + fromRawValue(context, tmp_cellPaddingVertical->second, result.cellPaddingVertical); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleTableStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleTableStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleTableStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleTaskListStruct { + SharedColor checkedColor{}; + SharedColor borderColor{}; + Float checkboxSize{0.0}; + Float checkboxBorderRadius{0.0}; + SharedColor checkmarkColor{}; + SharedColor checkedTextColor{}; + bool checkedStrikethrough{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleTaskListStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["checkedColor"] = ::facebook::react::toDynamic(checkedColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["checkboxSize"] = checkboxSize; + result["checkboxBorderRadius"] = checkboxBorderRadius; + result["checkmarkColor"] = ::facebook::react::toDynamic(checkmarkColor); + result["checkedTextColor"] = ::facebook::react::toDynamic(checkedTextColor); + result["checkedStrikethrough"] = checkedStrikethrough; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleTaskListStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_checkedColor = map.find("checkedColor"); + if (tmp_checkedColor != map.end()) { + fromRawValue(context, tmp_checkedColor->second, result.checkedColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_checkboxSize = map.find("checkboxSize"); + if (tmp_checkboxSize != map.end()) { + fromRawValue(context, tmp_checkboxSize->second, result.checkboxSize); + } + auto tmp_checkboxBorderRadius = map.find("checkboxBorderRadius"); + if (tmp_checkboxBorderRadius != map.end()) { + fromRawValue(context, tmp_checkboxBorderRadius->second, result.checkboxBorderRadius); + } + auto tmp_checkmarkColor = map.find("checkmarkColor"); + if (tmp_checkmarkColor != map.end()) { + fromRawValue(context, tmp_checkmarkColor->second, result.checkmarkColor); + } + auto tmp_checkedTextColor = map.find("checkedTextColor"); + if (tmp_checkedTextColor != map.end()) { + fromRawValue(context, tmp_checkedTextColor->second, result.checkedTextColor); + } + auto tmp_checkedStrikethrough = map.find("checkedStrikethrough"); + if (tmp_checkedStrikethrough != map.end()) { + fromRawValue(context, tmp_checkedStrikethrough->second, result.checkedStrikethrough); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleTaskListStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleTaskListStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleTaskListStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleMathStruct { + Float fontSize{0.0}; + SharedColor color{}; + SharedColor backgroundColor{}; + Float padding{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleMathStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["padding"] = padding; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleMathStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_padding = map.find("padding"); + if (tmp_padding != map.end()) { + fromRawValue(context, tmp_padding->second, result.padding); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleMathStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleMathStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleMathStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleInlineMathStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleInlineMathStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleInlineMathStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleInlineMathStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleInlineMathStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleInlineMathStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct { + Float density{0.0}; + Float speed{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["density"] = density; + result["speed"] = speed; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_density = map.find("density"); + if (tmp_density != map.end()) { + fromRawValue(context, tmp_density->second, result.density); + } + auto tmp_speed = map.find("speed"); + if (tmp_speed != map.end()) { + fromRawValue(context, tmp_speed->second, result.speed); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleSpoilerSolidStruct { + Float borderRadius{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleSpoilerSolidStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["borderRadius"] = borderRadius; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleSpoilerSolidStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleSpoilerSolidStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleSpoilerSolidStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleSpoilerSolidStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleSpoilerStruct { + SharedColor color{}; + EnrichedMarkdownMarkdownStyleSpoilerParticlesStruct particles{}; + EnrichedMarkdownMarkdownStyleSpoilerSolidStruct solid{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleSpoilerStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["particles"] = ::facebook::react::toDynamic(particles); + result["solid"] = ::facebook::react::toDynamic(solid); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleSpoilerStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_particles = map.find("particles"); + if (tmp_particles != map.end()) { + fromRawValue(context, tmp_particles->second, result.particles); + } + auto tmp_solid = map.find("solid"); + if (tmp_solid != map.end()) { + fromRawValue(context, tmp_solid->second, result.solid); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleSpoilerStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleSpoilerStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleSpoilerStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleMentionStruct { + SharedColor color{}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + Float paddingHorizontal{0.0}; + Float paddingVertical{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + Float fontSize{0.0}; + Float pressedOpacity{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleMentionStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + result["paddingHorizontal"] = paddingHorizontal; + result["paddingVertical"] = paddingVertical; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["fontSize"] = fontSize; + result["pressedOpacity"] = pressedOpacity; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleMentionStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_paddingHorizontal = map.find("paddingHorizontal"); + if (tmp_paddingHorizontal != map.end()) { + fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal); + } + auto tmp_paddingVertical = map.find("paddingVertical"); + if (tmp_paddingVertical != map.end()) { + fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_pressedOpacity = map.find("pressedOpacity"); + if (tmp_pressedOpacity != map.end()) { + fromRawValue(context, tmp_pressedOpacity->second, result.pressedOpacity); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleMentionStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleMentionStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleMentionStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleCitationStruct { + SharedColor color{}; + Float fontSizeMultiplier{0.0}; + Float baselineOffsetPx{0.0}; + std::string fontWeight{}; + bool underline{false}; + SharedColor backgroundColor{}; + Float paddingHorizontal{0.0}; + Float paddingVertical{0.0}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleCitationStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["fontSizeMultiplier"] = fontSizeMultiplier; + result["baselineOffsetPx"] = baselineOffsetPx; + result["fontWeight"] = fontWeight; + result["underline"] = underline; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["paddingHorizontal"] = paddingHorizontal; + result["paddingVertical"] = paddingVertical; + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleCitationStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_fontSizeMultiplier = map.find("fontSizeMultiplier"); + if (tmp_fontSizeMultiplier != map.end()) { + fromRawValue(context, tmp_fontSizeMultiplier->second, result.fontSizeMultiplier); + } + auto tmp_baselineOffsetPx = map.find("baselineOffsetPx"); + if (tmp_baselineOffsetPx != map.end()) { + fromRawValue(context, tmp_baselineOffsetPx->second, result.baselineOffsetPx); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_paddingHorizontal = map.find("paddingHorizontal"); + if (tmp_paddingHorizontal != map.end()) { + fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal); + } + auto tmp_paddingVertical = map.find("paddingVertical"); + if (tmp_paddingVertical != map.end()) { + fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleCitationStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleCitationStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleCitationStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMarkdownStyleStruct { + EnrichedMarkdownMarkdownStyleParagraphStruct paragraph{}; + EnrichedMarkdownMarkdownStyleH1Struct h1{}; + EnrichedMarkdownMarkdownStyleH2Struct h2{}; + EnrichedMarkdownMarkdownStyleH3Struct h3{}; + EnrichedMarkdownMarkdownStyleH4Struct h4{}; + EnrichedMarkdownMarkdownStyleH5Struct h5{}; + EnrichedMarkdownMarkdownStyleH6Struct h6{}; + EnrichedMarkdownMarkdownStyleBlockquoteStruct blockquote{}; + EnrichedMarkdownMarkdownStyleListStruct list{}; + EnrichedMarkdownMarkdownStyleCodeBlockStruct codeBlock{}; + EnrichedMarkdownMarkdownStyleLinkStruct link{}; + EnrichedMarkdownMarkdownStyleStrongStruct strong{}; + EnrichedMarkdownMarkdownStyleEmStruct em{}; + EnrichedMarkdownMarkdownStyleStrikethroughStruct strikethrough{}; + EnrichedMarkdownMarkdownStyleUnderlineStruct underline{}; + EnrichedMarkdownMarkdownStyleCodeStruct code{}; + EnrichedMarkdownMarkdownStyleImageStruct image{}; + EnrichedMarkdownMarkdownStyleInlineImageStruct inlineImage{}; + EnrichedMarkdownMarkdownStyleThematicBreakStruct thematicBreak{}; + EnrichedMarkdownMarkdownStyleTableStruct table{}; + EnrichedMarkdownMarkdownStyleTaskListStruct taskList{}; + EnrichedMarkdownMarkdownStyleMathStruct math{}; + EnrichedMarkdownMarkdownStyleInlineMathStruct inlineMath{}; + EnrichedMarkdownMarkdownStyleSpoilerStruct spoiler{}; + EnrichedMarkdownMarkdownStyleMentionStruct mention{}; + EnrichedMarkdownMarkdownStyleCitationStruct citation{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMarkdownStyleStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["paragraph"] = ::facebook::react::toDynamic(paragraph); + result["h1"] = ::facebook::react::toDynamic(h1); + result["h2"] = ::facebook::react::toDynamic(h2); + result["h3"] = ::facebook::react::toDynamic(h3); + result["h4"] = ::facebook::react::toDynamic(h4); + result["h5"] = ::facebook::react::toDynamic(h5); + result["h6"] = ::facebook::react::toDynamic(h6); + result["blockquote"] = ::facebook::react::toDynamic(blockquote); + result["list"] = ::facebook::react::toDynamic(list); + result["codeBlock"] = ::facebook::react::toDynamic(codeBlock); + result["link"] = ::facebook::react::toDynamic(link); + result["strong"] = ::facebook::react::toDynamic(strong); + result["em"] = ::facebook::react::toDynamic(em); + result["strikethrough"] = ::facebook::react::toDynamic(strikethrough); + result["underline"] = ::facebook::react::toDynamic(underline); + result["code"] = ::facebook::react::toDynamic(code); + result["image"] = ::facebook::react::toDynamic(image); + result["inlineImage"] = ::facebook::react::toDynamic(inlineImage); + result["thematicBreak"] = ::facebook::react::toDynamic(thematicBreak); + result["table"] = ::facebook::react::toDynamic(table); + result["taskList"] = ::facebook::react::toDynamic(taskList); + result["math"] = ::facebook::react::toDynamic(math); + result["inlineMath"] = ::facebook::react::toDynamic(inlineMath); + result["spoiler"] = ::facebook::react::toDynamic(spoiler); + result["mention"] = ::facebook::react::toDynamic(mention); + result["citation"] = ::facebook::react::toDynamic(citation); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMarkdownStyleStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_paragraph = map.find("paragraph"); + if (tmp_paragraph != map.end()) { + fromRawValue(context, tmp_paragraph->second, result.paragraph); + } + auto tmp_h1 = map.find("h1"); + if (tmp_h1 != map.end()) { + fromRawValue(context, tmp_h1->second, result.h1); + } + auto tmp_h2 = map.find("h2"); + if (tmp_h2 != map.end()) { + fromRawValue(context, tmp_h2->second, result.h2); + } + auto tmp_h3 = map.find("h3"); + if (tmp_h3 != map.end()) { + fromRawValue(context, tmp_h3->second, result.h3); + } + auto tmp_h4 = map.find("h4"); + if (tmp_h4 != map.end()) { + fromRawValue(context, tmp_h4->second, result.h4); + } + auto tmp_h5 = map.find("h5"); + if (tmp_h5 != map.end()) { + fromRawValue(context, tmp_h5->second, result.h5); + } + auto tmp_h6 = map.find("h6"); + if (tmp_h6 != map.end()) { + fromRawValue(context, tmp_h6->second, result.h6); + } + auto tmp_blockquote = map.find("blockquote"); + if (tmp_blockquote != map.end()) { + fromRawValue(context, tmp_blockquote->second, result.blockquote); + } + auto tmp_list = map.find("list"); + if (tmp_list != map.end()) { + fromRawValue(context, tmp_list->second, result.list); + } + auto tmp_codeBlock = map.find("codeBlock"); + if (tmp_codeBlock != map.end()) { + fromRawValue(context, tmp_codeBlock->second, result.codeBlock); + } + auto tmp_link = map.find("link"); + if (tmp_link != map.end()) { + fromRawValue(context, tmp_link->second, result.link); + } + auto tmp_strong = map.find("strong"); + if (tmp_strong != map.end()) { + fromRawValue(context, tmp_strong->second, result.strong); + } + auto tmp_em = map.find("em"); + if (tmp_em != map.end()) { + fromRawValue(context, tmp_em->second, result.em); + } + auto tmp_strikethrough = map.find("strikethrough"); + if (tmp_strikethrough != map.end()) { + fromRawValue(context, tmp_strikethrough->second, result.strikethrough); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_code = map.find("code"); + if (tmp_code != map.end()) { + fromRawValue(context, tmp_code->second, result.code); + } + auto tmp_image = map.find("image"); + if (tmp_image != map.end()) { + fromRawValue(context, tmp_image->second, result.image); + } + auto tmp_inlineImage = map.find("inlineImage"); + if (tmp_inlineImage != map.end()) { + fromRawValue(context, tmp_inlineImage->second, result.inlineImage); + } + auto tmp_thematicBreak = map.find("thematicBreak"); + if (tmp_thematicBreak != map.end()) { + fromRawValue(context, tmp_thematicBreak->second, result.thematicBreak); + } + auto tmp_table = map.find("table"); + if (tmp_table != map.end()) { + fromRawValue(context, tmp_table->second, result.table); + } + auto tmp_taskList = map.find("taskList"); + if (tmp_taskList != map.end()) { + fromRawValue(context, tmp_taskList->second, result.taskList); + } + auto tmp_math = map.find("math"); + if (tmp_math != map.end()) { + fromRawValue(context, tmp_math->second, result.math); + } + auto tmp_inlineMath = map.find("inlineMath"); + if (tmp_inlineMath != map.end()) { + fromRawValue(context, tmp_inlineMath->second, result.inlineMath); + } + auto tmp_spoiler = map.find("spoiler"); + if (tmp_spoiler != map.end()) { + fromRawValue(context, tmp_spoiler->second, result.spoiler); + } + auto tmp_mention = map.find("mention"); + if (tmp_mention != map.end()) { + fromRawValue(context, tmp_mention->second, result.mention); + } + auto tmp_citation = map.find("citation"); + if (tmp_citation != map.end()) { + fromRawValue(context, tmp_citation->second, result.citation); + } +} + +static inline std::string toString(const EnrichedMarkdownMarkdownStyleStruct &value) { + return "[Object EnrichedMarkdownMarkdownStyleStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMarkdownStyleStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownMd4cFlagsStruct { + bool underline{false}; + bool latexMath{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownMd4cFlagsStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["underline"] = underline; + result["latexMath"] = latexMath; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownMd4cFlagsStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_latexMath = map.find("latexMath"); + if (tmp_latexMath != map.end()) { + fromRawValue(context, tmp_latexMath->second, result.latexMath); + } +} + +static inline std::string toString(const EnrichedMarkdownMd4cFlagsStruct &value) { + return "[Object EnrichedMarkdownMd4cFlagsStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownMd4cFlagsStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownContextMenuItemsStruct { + std::string text{}; + std::string icon{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownContextMenuItemsStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["text"] = text; + result["icon"] = icon; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownContextMenuItemsStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_text = map.find("text"); + if (tmp_text != map.end()) { + fromRawValue(context, tmp_text->second, result.text); + } + auto tmp_icon = map.find("icon"); + if (tmp_icon != map.end()) { + fromRawValue(context, tmp_icon->second, result.icon); + } +} + +static inline std::string toString(const EnrichedMarkdownContextMenuItemsStruct &value) { + return "[Object EnrichedMarkdownContextMenuItemsStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownContextMenuItemsStruct &value) { + return value.toDynamic(); +} +#endif + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector &result) { + auto items = (std::vector)value; + for (const auto &item : items) { + EnrichedMarkdownContextMenuItemsStruct newItem; + fromRawValue(context, item, newItem); + result.emplace_back(newItem); + } +} + +class EnrichedMarkdownProps final : public ViewProps { + public: + EnrichedMarkdownProps() = default; + EnrichedMarkdownProps(const PropsParserContext& context, const EnrichedMarkdownProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + std::string markdown{}; + EnrichedMarkdownMarkdownStyleStruct markdownStyle{}; + bool enableLinkPreview{true}; + bool selectable{false}; + SharedColor selectionColor{}; + SharedColor selectionHandleColor{}; + EnrichedMarkdownMd4cFlagsStruct md4cFlags{}; + bool allowFontScaling{true}; + Float maxFontSizeMultiplier{0.0}; + bool allowTrailingMargin{false}; + bool streamingAnimation{false}; + std::string spoilerOverlay{std::string{"particles"}}; + std::vector contextMenuItems{}; + + #ifdef RN_SERIALIZABLE_STATE + ComponentName getDiffPropsImplementationTarget() const override; + + folly::dynamic getDiffProps(const Props* prevProps) const override; + #endif + + +}; + +struct EnrichedMarkdownTextMarkdownStyleParagraphStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleParagraphStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleParagraphStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleParagraphStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleParagraphStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleParagraphStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH1Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH1Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH1Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH1Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH1Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH1Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH2Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH2Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH2Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH2Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH2Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH2Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH3Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH3Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH3Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH3Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH3Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH3Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH4Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH4Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH4Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH4Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH4Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH4Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH5Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH5Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH5Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH5Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH5Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH5Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleH6Struct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleH6Struct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleH6Struct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleH6Struct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleH6Struct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleH6Struct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleBlockquoteStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float gapWidth{0.0}; + SharedColor backgroundColor{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleBlockquoteStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["gapWidth"] = gapWidth; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleBlockquoteStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_gapWidth = map.find("gapWidth"); + if (tmp_gapWidth != map.end()) { + fromRawValue(context, tmp_gapWidth->second, result.gapWidth); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleBlockquoteStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleBlockquoteStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleBlockquoteStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleListStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor bulletColor{}; + Float bulletSize{0.0}; + SharedColor markerColor{}; + std::string markerFontWeight{}; + Float gapWidth{0.0}; + Float marginLeft{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleListStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["bulletColor"] = ::facebook::react::toDynamic(bulletColor); + result["bulletSize"] = bulletSize; + result["markerColor"] = ::facebook::react::toDynamic(markerColor); + result["markerFontWeight"] = markerFontWeight; + result["gapWidth"] = gapWidth; + result["marginLeft"] = marginLeft; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleListStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_bulletColor = map.find("bulletColor"); + if (tmp_bulletColor != map.end()) { + fromRawValue(context, tmp_bulletColor->second, result.bulletColor); + } + auto tmp_bulletSize = map.find("bulletSize"); + if (tmp_bulletSize != map.end()) { + fromRawValue(context, tmp_bulletSize->second, result.bulletSize); + } + auto tmp_markerColor = map.find("markerColor"); + if (tmp_markerColor != map.end()) { + fromRawValue(context, tmp_markerColor->second, result.markerColor); + } + auto tmp_markerFontWeight = map.find("markerFontWeight"); + if (tmp_markerFontWeight != map.end()) { + fromRawValue(context, tmp_markerFontWeight->second, result.markerFontWeight); + } + auto tmp_gapWidth = map.find("gapWidth"); + if (tmp_gapWidth != map.end()) { + fromRawValue(context, tmp_gapWidth->second, result.gapWidth); + } + auto tmp_marginLeft = map.find("marginLeft"); + if (tmp_marginLeft != map.end()) { + fromRawValue(context, tmp_marginLeft->second, result.marginLeft); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleListStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleListStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleListStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleCodeBlockStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + Float borderRadius{0.0}; + Float borderWidth{0.0}; + Float padding{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleCodeBlockStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderRadius"] = borderRadius; + result["borderWidth"] = borderWidth; + result["padding"] = padding; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleCodeBlockStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_padding = map.find("padding"); + if (tmp_padding != map.end()) { + fromRawValue(context, tmp_padding->second, result.padding); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleCodeBlockStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleCodeBlockStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleCodeBlockStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleLinkStruct { + std::string fontFamily{}; + SharedColor color{}; + bool underline{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleLinkStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["color"] = ::facebook::react::toDynamic(color); + result["underline"] = underline; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleLinkStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleLinkStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleLinkStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleLinkStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleStrongStruct { + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleStrongStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleStrongStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleStrongStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleStrongStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleStrongStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleEmStruct { + std::string fontFamily{}; + std::string fontStyle{}; + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleEmStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontStyle"] = fontStyle; + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleEmStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontStyle = map.find("fontStyle"); + if (tmp_fontStyle != map.end()) { + fromRawValue(context, tmp_fontStyle->second, result.fontStyle); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleEmStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleEmStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleEmStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleStrikethroughStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleStrikethroughStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleStrikethroughStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleStrikethroughStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleStrikethroughStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleStrikethroughStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleUnderlineStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleUnderlineStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleUnderlineStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleUnderlineStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleUnderlineStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleUnderlineStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleCodeStruct { + std::string fontFamily{}; + Float fontSize{0.0}; + SharedColor color{}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleCodeStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontFamily"] = fontFamily; + result["fontSize"] = fontSize; + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleCodeStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleCodeStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleCodeStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleCodeStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleImageStruct { + Float height{0.0}; + Float borderRadius{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleImageStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["height"] = height; + result["borderRadius"] = borderRadius; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleImageStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_height = map.find("height"); + if (tmp_height != map.end()) { + fromRawValue(context, tmp_height->second, result.height); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleImageStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleImageStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleImageStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleInlineImageStruct { + Float size{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleInlineImageStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["size"] = size; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleInlineImageStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_size = map.find("size"); + if (tmp_size != map.end()) { + fromRawValue(context, tmp_size->second, result.size); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleInlineImageStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleInlineImageStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleInlineImageStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleThematicBreakStruct { + SharedColor color{}; + Float height{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleThematicBreakStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["height"] = height; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleThematicBreakStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_height = map.find("height"); + if (tmp_height != map.end()) { + fromRawValue(context, tmp_height->second, result.height); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleThematicBreakStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleThematicBreakStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleThematicBreakStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleTableStruct { + Float fontSize{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + SharedColor color{}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + Float lineHeight{0.0}; + std::string headerFontFamily{}; + SharedColor headerBackgroundColor{}; + SharedColor headerTextColor{}; + SharedColor rowEvenBackgroundColor{}; + SharedColor rowOddBackgroundColor{}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + Float cellPaddingHorizontal{0.0}; + Float cellPaddingVertical{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleTableStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["color"] = ::facebook::react::toDynamic(color); + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["lineHeight"] = lineHeight; + result["headerFontFamily"] = headerFontFamily; + result["headerBackgroundColor"] = ::facebook::react::toDynamic(headerBackgroundColor); + result["headerTextColor"] = ::facebook::react::toDynamic(headerTextColor); + result["rowEvenBackgroundColor"] = ::facebook::react::toDynamic(rowEvenBackgroundColor); + result["rowOddBackgroundColor"] = ::facebook::react::toDynamic(rowOddBackgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + result["cellPaddingHorizontal"] = cellPaddingHorizontal; + result["cellPaddingVertical"] = cellPaddingVertical; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleTableStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_lineHeight = map.find("lineHeight"); + if (tmp_lineHeight != map.end()) { + fromRawValue(context, tmp_lineHeight->second, result.lineHeight); + } + auto tmp_headerFontFamily = map.find("headerFontFamily"); + if (tmp_headerFontFamily != map.end()) { + fromRawValue(context, tmp_headerFontFamily->second, result.headerFontFamily); + } + auto tmp_headerBackgroundColor = map.find("headerBackgroundColor"); + if (tmp_headerBackgroundColor != map.end()) { + fromRawValue(context, tmp_headerBackgroundColor->second, result.headerBackgroundColor); + } + auto tmp_headerTextColor = map.find("headerTextColor"); + if (tmp_headerTextColor != map.end()) { + fromRawValue(context, tmp_headerTextColor->second, result.headerTextColor); + } + auto tmp_rowEvenBackgroundColor = map.find("rowEvenBackgroundColor"); + if (tmp_rowEvenBackgroundColor != map.end()) { + fromRawValue(context, tmp_rowEvenBackgroundColor->second, result.rowEvenBackgroundColor); + } + auto tmp_rowOddBackgroundColor = map.find("rowOddBackgroundColor"); + if (tmp_rowOddBackgroundColor != map.end()) { + fromRawValue(context, tmp_rowOddBackgroundColor->second, result.rowOddBackgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_cellPaddingHorizontal = map.find("cellPaddingHorizontal"); + if (tmp_cellPaddingHorizontal != map.end()) { + fromRawValue(context, tmp_cellPaddingHorizontal->second, result.cellPaddingHorizontal); + } + auto tmp_cellPaddingVertical = map.find("cellPaddingVertical"); + if (tmp_cellPaddingVertical != map.end()) { + fromRawValue(context, tmp_cellPaddingVertical->second, result.cellPaddingVertical); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleTableStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleTableStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleTableStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleTaskListStruct { + SharedColor checkedColor{}; + SharedColor borderColor{}; + Float checkboxSize{0.0}; + Float checkboxBorderRadius{0.0}; + SharedColor checkmarkColor{}; + SharedColor checkedTextColor{}; + bool checkedStrikethrough{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleTaskListStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["checkedColor"] = ::facebook::react::toDynamic(checkedColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["checkboxSize"] = checkboxSize; + result["checkboxBorderRadius"] = checkboxBorderRadius; + result["checkmarkColor"] = ::facebook::react::toDynamic(checkmarkColor); + result["checkedTextColor"] = ::facebook::react::toDynamic(checkedTextColor); + result["checkedStrikethrough"] = checkedStrikethrough; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleTaskListStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_checkedColor = map.find("checkedColor"); + if (tmp_checkedColor != map.end()) { + fromRawValue(context, tmp_checkedColor->second, result.checkedColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_checkboxSize = map.find("checkboxSize"); + if (tmp_checkboxSize != map.end()) { + fromRawValue(context, tmp_checkboxSize->second, result.checkboxSize); + } + auto tmp_checkboxBorderRadius = map.find("checkboxBorderRadius"); + if (tmp_checkboxBorderRadius != map.end()) { + fromRawValue(context, tmp_checkboxBorderRadius->second, result.checkboxBorderRadius); + } + auto tmp_checkmarkColor = map.find("checkmarkColor"); + if (tmp_checkmarkColor != map.end()) { + fromRawValue(context, tmp_checkmarkColor->second, result.checkmarkColor); + } + auto tmp_checkedTextColor = map.find("checkedTextColor"); + if (tmp_checkedTextColor != map.end()) { + fromRawValue(context, tmp_checkedTextColor->second, result.checkedTextColor); + } + auto tmp_checkedStrikethrough = map.find("checkedStrikethrough"); + if (tmp_checkedStrikethrough != map.end()) { + fromRawValue(context, tmp_checkedStrikethrough->second, result.checkedStrikethrough); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleTaskListStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleTaskListStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleTaskListStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleMathStruct { + Float fontSize{0.0}; + SharedColor color{}; + SharedColor backgroundColor{}; + Float padding{0.0}; + Float marginTop{0.0}; + Float marginBottom{0.0}; + std::string textAlign{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleMathStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["fontSize"] = fontSize; + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["padding"] = padding; + result["marginTop"] = marginTop; + result["marginBottom"] = marginBottom; + result["textAlign"] = textAlign; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleMathStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_padding = map.find("padding"); + if (tmp_padding != map.end()) { + fromRawValue(context, tmp_padding->second, result.padding); + } + auto tmp_marginTop = map.find("marginTop"); + if (tmp_marginTop != map.end()) { + fromRawValue(context, tmp_marginTop->second, result.marginTop); + } + auto tmp_marginBottom = map.find("marginBottom"); + if (tmp_marginBottom != map.end()) { + fromRawValue(context, tmp_marginBottom->second, result.marginBottom); + } + auto tmp_textAlign = map.find("textAlign"); + if (tmp_textAlign != map.end()) { + fromRawValue(context, tmp_textAlign->second, result.textAlign); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleMathStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleMathStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleMathStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleInlineMathStruct { + SharedColor color{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleInlineMathStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleInlineMathStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleInlineMathStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleInlineMathStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleInlineMathStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct { + Float density{0.0}; + Float speed{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["density"] = density; + result["speed"] = speed; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_density = map.find("density"); + if (tmp_density != map.end()) { + fromRawValue(context, tmp_density->second, result.density); + } + auto tmp_speed = map.find("speed"); + if (tmp_speed != map.end()) { + fromRawValue(context, tmp_speed->second, result.speed); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct { + Float borderRadius{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["borderRadius"] = borderRadius; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleSpoilerStruct { + SharedColor color{}; + EnrichedMarkdownTextMarkdownStyleSpoilerParticlesStruct particles{}; + EnrichedMarkdownTextMarkdownStyleSpoilerSolidStruct solid{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleSpoilerStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["particles"] = ::facebook::react::toDynamic(particles); + result["solid"] = ::facebook::react::toDynamic(solid); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleSpoilerStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_particles = map.find("particles"); + if (tmp_particles != map.end()) { + fromRawValue(context, tmp_particles->second, result.particles); + } + auto tmp_solid = map.find("solid"); + if (tmp_solid != map.end()) { + fromRawValue(context, tmp_solid->second, result.solid); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleSpoilerStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleSpoilerStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleSpoilerStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleMentionStruct { + SharedColor color{}; + SharedColor backgroundColor{}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + Float paddingHorizontal{0.0}; + Float paddingVertical{0.0}; + std::string fontFamily{}; + std::string fontWeight{}; + Float fontSize{0.0}; + Float pressedOpacity{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleMentionStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + result["paddingHorizontal"] = paddingHorizontal; + result["paddingVertical"] = paddingVertical; + result["fontFamily"] = fontFamily; + result["fontWeight"] = fontWeight; + result["fontSize"] = fontSize; + result["pressedOpacity"] = pressedOpacity; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleMentionStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } + auto tmp_paddingHorizontal = map.find("paddingHorizontal"); + if (tmp_paddingHorizontal != map.end()) { + fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal); + } + auto tmp_paddingVertical = map.find("paddingVertical"); + if (tmp_paddingVertical != map.end()) { + fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical); + } + auto tmp_fontFamily = map.find("fontFamily"); + if (tmp_fontFamily != map.end()) { + fromRawValue(context, tmp_fontFamily->second, result.fontFamily); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_fontSize = map.find("fontSize"); + if (tmp_fontSize != map.end()) { + fromRawValue(context, tmp_fontSize->second, result.fontSize); + } + auto tmp_pressedOpacity = map.find("pressedOpacity"); + if (tmp_pressedOpacity != map.end()) { + fromRawValue(context, tmp_pressedOpacity->second, result.pressedOpacity); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleMentionStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleMentionStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleMentionStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleCitationStruct { + SharedColor color{}; + Float fontSizeMultiplier{0.0}; + Float baselineOffsetPx{0.0}; + std::string fontWeight{}; + bool underline{false}; + SharedColor backgroundColor{}; + Float paddingHorizontal{0.0}; + Float paddingVertical{0.0}; + SharedColor borderColor{}; + Float borderWidth{0.0}; + Float borderRadius{0.0}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleCitationStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["color"] = ::facebook::react::toDynamic(color); + result["fontSizeMultiplier"] = fontSizeMultiplier; + result["baselineOffsetPx"] = baselineOffsetPx; + result["fontWeight"] = fontWeight; + result["underline"] = underline; + result["backgroundColor"] = ::facebook::react::toDynamic(backgroundColor); + result["paddingHorizontal"] = paddingHorizontal; + result["paddingVertical"] = paddingVertical; + result["borderColor"] = ::facebook::react::toDynamic(borderColor); + result["borderWidth"] = borderWidth; + result["borderRadius"] = borderRadius; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleCitationStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_color = map.find("color"); + if (tmp_color != map.end()) { + fromRawValue(context, tmp_color->second, result.color); + } + auto tmp_fontSizeMultiplier = map.find("fontSizeMultiplier"); + if (tmp_fontSizeMultiplier != map.end()) { + fromRawValue(context, tmp_fontSizeMultiplier->second, result.fontSizeMultiplier); + } + auto tmp_baselineOffsetPx = map.find("baselineOffsetPx"); + if (tmp_baselineOffsetPx != map.end()) { + fromRawValue(context, tmp_baselineOffsetPx->second, result.baselineOffsetPx); + } + auto tmp_fontWeight = map.find("fontWeight"); + if (tmp_fontWeight != map.end()) { + fromRawValue(context, tmp_fontWeight->second, result.fontWeight); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_backgroundColor = map.find("backgroundColor"); + if (tmp_backgroundColor != map.end()) { + fromRawValue(context, tmp_backgroundColor->second, result.backgroundColor); + } + auto tmp_paddingHorizontal = map.find("paddingHorizontal"); + if (tmp_paddingHorizontal != map.end()) { + fromRawValue(context, tmp_paddingHorizontal->second, result.paddingHorizontal); + } + auto tmp_paddingVertical = map.find("paddingVertical"); + if (tmp_paddingVertical != map.end()) { + fromRawValue(context, tmp_paddingVertical->second, result.paddingVertical); + } + auto tmp_borderColor = map.find("borderColor"); + if (tmp_borderColor != map.end()) { + fromRawValue(context, tmp_borderColor->second, result.borderColor); + } + auto tmp_borderWidth = map.find("borderWidth"); + if (tmp_borderWidth != map.end()) { + fromRawValue(context, tmp_borderWidth->second, result.borderWidth); + } + auto tmp_borderRadius = map.find("borderRadius"); + if (tmp_borderRadius != map.end()) { + fromRawValue(context, tmp_borderRadius->second, result.borderRadius); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleCitationStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleCitationStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleCitationStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMarkdownStyleStruct { + EnrichedMarkdownTextMarkdownStyleParagraphStruct paragraph{}; + EnrichedMarkdownTextMarkdownStyleH1Struct h1{}; + EnrichedMarkdownTextMarkdownStyleH2Struct h2{}; + EnrichedMarkdownTextMarkdownStyleH3Struct h3{}; + EnrichedMarkdownTextMarkdownStyleH4Struct h4{}; + EnrichedMarkdownTextMarkdownStyleH5Struct h5{}; + EnrichedMarkdownTextMarkdownStyleH6Struct h6{}; + EnrichedMarkdownTextMarkdownStyleBlockquoteStruct blockquote{}; + EnrichedMarkdownTextMarkdownStyleListStruct list{}; + EnrichedMarkdownTextMarkdownStyleCodeBlockStruct codeBlock{}; + EnrichedMarkdownTextMarkdownStyleLinkStruct link{}; + EnrichedMarkdownTextMarkdownStyleStrongStruct strong{}; + EnrichedMarkdownTextMarkdownStyleEmStruct em{}; + EnrichedMarkdownTextMarkdownStyleStrikethroughStruct strikethrough{}; + EnrichedMarkdownTextMarkdownStyleUnderlineStruct underline{}; + EnrichedMarkdownTextMarkdownStyleCodeStruct code{}; + EnrichedMarkdownTextMarkdownStyleImageStruct image{}; + EnrichedMarkdownTextMarkdownStyleInlineImageStruct inlineImage{}; + EnrichedMarkdownTextMarkdownStyleThematicBreakStruct thematicBreak{}; + EnrichedMarkdownTextMarkdownStyleTableStruct table{}; + EnrichedMarkdownTextMarkdownStyleTaskListStruct taskList{}; + EnrichedMarkdownTextMarkdownStyleMathStruct math{}; + EnrichedMarkdownTextMarkdownStyleInlineMathStruct inlineMath{}; + EnrichedMarkdownTextMarkdownStyleSpoilerStruct spoiler{}; + EnrichedMarkdownTextMarkdownStyleMentionStruct mention{}; + EnrichedMarkdownTextMarkdownStyleCitationStruct citation{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMarkdownStyleStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["paragraph"] = ::facebook::react::toDynamic(paragraph); + result["h1"] = ::facebook::react::toDynamic(h1); + result["h2"] = ::facebook::react::toDynamic(h2); + result["h3"] = ::facebook::react::toDynamic(h3); + result["h4"] = ::facebook::react::toDynamic(h4); + result["h5"] = ::facebook::react::toDynamic(h5); + result["h6"] = ::facebook::react::toDynamic(h6); + result["blockquote"] = ::facebook::react::toDynamic(blockquote); + result["list"] = ::facebook::react::toDynamic(list); + result["codeBlock"] = ::facebook::react::toDynamic(codeBlock); + result["link"] = ::facebook::react::toDynamic(link); + result["strong"] = ::facebook::react::toDynamic(strong); + result["em"] = ::facebook::react::toDynamic(em); + result["strikethrough"] = ::facebook::react::toDynamic(strikethrough); + result["underline"] = ::facebook::react::toDynamic(underline); + result["code"] = ::facebook::react::toDynamic(code); + result["image"] = ::facebook::react::toDynamic(image); + result["inlineImage"] = ::facebook::react::toDynamic(inlineImage); + result["thematicBreak"] = ::facebook::react::toDynamic(thematicBreak); + result["table"] = ::facebook::react::toDynamic(table); + result["taskList"] = ::facebook::react::toDynamic(taskList); + result["math"] = ::facebook::react::toDynamic(math); + result["inlineMath"] = ::facebook::react::toDynamic(inlineMath); + result["spoiler"] = ::facebook::react::toDynamic(spoiler); + result["mention"] = ::facebook::react::toDynamic(mention); + result["citation"] = ::facebook::react::toDynamic(citation); + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMarkdownStyleStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_paragraph = map.find("paragraph"); + if (tmp_paragraph != map.end()) { + fromRawValue(context, tmp_paragraph->second, result.paragraph); + } + auto tmp_h1 = map.find("h1"); + if (tmp_h1 != map.end()) { + fromRawValue(context, tmp_h1->second, result.h1); + } + auto tmp_h2 = map.find("h2"); + if (tmp_h2 != map.end()) { + fromRawValue(context, tmp_h2->second, result.h2); + } + auto tmp_h3 = map.find("h3"); + if (tmp_h3 != map.end()) { + fromRawValue(context, tmp_h3->second, result.h3); + } + auto tmp_h4 = map.find("h4"); + if (tmp_h4 != map.end()) { + fromRawValue(context, tmp_h4->second, result.h4); + } + auto tmp_h5 = map.find("h5"); + if (tmp_h5 != map.end()) { + fromRawValue(context, tmp_h5->second, result.h5); + } + auto tmp_h6 = map.find("h6"); + if (tmp_h6 != map.end()) { + fromRawValue(context, tmp_h6->second, result.h6); + } + auto tmp_blockquote = map.find("blockquote"); + if (tmp_blockquote != map.end()) { + fromRawValue(context, tmp_blockquote->second, result.blockquote); + } + auto tmp_list = map.find("list"); + if (tmp_list != map.end()) { + fromRawValue(context, tmp_list->second, result.list); + } + auto tmp_codeBlock = map.find("codeBlock"); + if (tmp_codeBlock != map.end()) { + fromRawValue(context, tmp_codeBlock->second, result.codeBlock); + } + auto tmp_link = map.find("link"); + if (tmp_link != map.end()) { + fromRawValue(context, tmp_link->second, result.link); + } + auto tmp_strong = map.find("strong"); + if (tmp_strong != map.end()) { + fromRawValue(context, tmp_strong->second, result.strong); + } + auto tmp_em = map.find("em"); + if (tmp_em != map.end()) { + fromRawValue(context, tmp_em->second, result.em); + } + auto tmp_strikethrough = map.find("strikethrough"); + if (tmp_strikethrough != map.end()) { + fromRawValue(context, tmp_strikethrough->second, result.strikethrough); + } + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_code = map.find("code"); + if (tmp_code != map.end()) { + fromRawValue(context, tmp_code->second, result.code); + } + auto tmp_image = map.find("image"); + if (tmp_image != map.end()) { + fromRawValue(context, tmp_image->second, result.image); + } + auto tmp_inlineImage = map.find("inlineImage"); + if (tmp_inlineImage != map.end()) { + fromRawValue(context, tmp_inlineImage->second, result.inlineImage); + } + auto tmp_thematicBreak = map.find("thematicBreak"); + if (tmp_thematicBreak != map.end()) { + fromRawValue(context, tmp_thematicBreak->second, result.thematicBreak); + } + auto tmp_table = map.find("table"); + if (tmp_table != map.end()) { + fromRawValue(context, tmp_table->second, result.table); + } + auto tmp_taskList = map.find("taskList"); + if (tmp_taskList != map.end()) { + fromRawValue(context, tmp_taskList->second, result.taskList); + } + auto tmp_math = map.find("math"); + if (tmp_math != map.end()) { + fromRawValue(context, tmp_math->second, result.math); + } + auto tmp_inlineMath = map.find("inlineMath"); + if (tmp_inlineMath != map.end()) { + fromRawValue(context, tmp_inlineMath->second, result.inlineMath); + } + auto tmp_spoiler = map.find("spoiler"); + if (tmp_spoiler != map.end()) { + fromRawValue(context, tmp_spoiler->second, result.spoiler); + } + auto tmp_mention = map.find("mention"); + if (tmp_mention != map.end()) { + fromRawValue(context, tmp_mention->second, result.mention); + } + auto tmp_citation = map.find("citation"); + if (tmp_citation != map.end()) { + fromRawValue(context, tmp_citation->second, result.citation); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMarkdownStyleStruct &value) { + return "[Object EnrichedMarkdownTextMarkdownStyleStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMarkdownStyleStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextMd4cFlagsStruct { + bool underline{false}; + bool latexMath{false}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextMd4cFlagsStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["underline"] = underline; + result["latexMath"] = latexMath; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextMd4cFlagsStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_underline = map.find("underline"); + if (tmp_underline != map.end()) { + fromRawValue(context, tmp_underline->second, result.underline); + } + auto tmp_latexMath = map.find("latexMath"); + if (tmp_latexMath != map.end()) { + fromRawValue(context, tmp_latexMath->second, result.latexMath); + } +} + +static inline std::string toString(const EnrichedMarkdownTextMd4cFlagsStruct &value) { + return "[Object EnrichedMarkdownTextMd4cFlagsStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextMd4cFlagsStruct &value) { + return value.toDynamic(); +} +#endif + +struct EnrichedMarkdownTextContextMenuItemsStruct { + std::string text{}; + std::string icon{}; + + +#ifdef RN_SERIALIZABLE_STATE + bool operator==(const EnrichedMarkdownTextContextMenuItemsStruct&) const = default; + + folly::dynamic toDynamic() const { + folly::dynamic result = folly::dynamic::object(); + result["text"] = text; + result["icon"] = icon; + return result; + } +#endif +}; + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, EnrichedMarkdownTextContextMenuItemsStruct &result) { + auto map = (std::unordered_map)value; + + auto tmp_text = map.find("text"); + if (tmp_text != map.end()) { + fromRawValue(context, tmp_text->second, result.text); + } + auto tmp_icon = map.find("icon"); + if (tmp_icon != map.end()) { + fromRawValue(context, tmp_icon->second, result.icon); + } +} + +static inline std::string toString(const EnrichedMarkdownTextContextMenuItemsStruct &value) { + return "[Object EnrichedMarkdownTextContextMenuItemsStruct]"; +} + +#ifdef RN_SERIALIZABLE_STATE +static inline folly::dynamic toDynamic(const EnrichedMarkdownTextContextMenuItemsStruct &value) { + return value.toDynamic(); +} +#endif + +static inline void fromRawValue(const PropsParserContext& context, const RawValue &value, std::vector &result) { + auto items = (std::vector)value; + for (const auto &item : items) { + EnrichedMarkdownTextContextMenuItemsStruct newItem; + fromRawValue(context, item, newItem); + result.emplace_back(newItem); + } +} + +class EnrichedMarkdownTextProps final : public ViewProps { + public: + EnrichedMarkdownTextProps() = default; + EnrichedMarkdownTextProps(const PropsParserContext& context, const EnrichedMarkdownTextProps &sourceProps, const RawProps &rawProps); + +#pragma mark - Props + + std::string markdown{}; + EnrichedMarkdownTextMarkdownStyleStruct markdownStyle{}; + bool enableLinkPreview{true}; + bool selectable{false}; + SharedColor selectionColor{}; + SharedColor selectionHandleColor{}; + EnrichedMarkdownTextMd4cFlagsStruct md4cFlags{}; + bool allowFontScaling{true}; + Float maxFontSizeMultiplier{0.0}; + bool allowTrailingMargin{false}; + bool streamingAnimation{false}; + std::string spoilerOverlay{std::string{"particles"}}; + std::vector contextMenuItems{}; + + #ifdef RN_SERIALIZABLE_STATE + ComponentName getDiffPropsImplementationTarget() const override; + + folly::dynamic getDiffProps(const Props* prevProps) const override; + #endif + + +}; + +} // namespace facebook::react diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h new file mode 100644 index 00000000..a517f179 --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/RCTComponentViewHelpers.h @@ -0,0 +1,299 @@ +/** +* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). +* +* Do not edit this file as changes may cause incorrect behavior and will be lost +* once the code is regenerated. +* +* @generated by codegen project: GenerateComponentHObjCpp.js +*/ + +#import +#import +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol RCTEnrichedMarkdownInputViewProtocol +- (void)focus; +- (void)blur; +- (void)setValue:(NSString *)markdown; +- (void)setSelection:(NSInteger)start end:(NSInteger)end; +- (void)toggleBold; +- (void)toggleItalic; +- (void)toggleUnderline; +- (void)toggleStrikethrough; +- (void)toggleSpoiler; +- (void)setLink:(NSString *)url; +- (void)insertLink:(NSString *)text url:(NSString *)url; +- (void)removeLink; +- (void)requestMarkdown:(NSInteger)requestId; +- (void)requestCaretRect:(NSInteger)requestId; +@end + +RCT_EXTERN inline void RCTEnrichedMarkdownInputHandleCommand( + id componentView, + NSString const *commandName, + NSArray const *args) +{ + if ([commandName isEqualToString:@"focus"]) { +#if RCT_DEBUG + if ([args count] != 0) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 0); + return; + } +#endif + + + + [componentView focus]; + return; +} + +if ([commandName isEqualToString:@"blur"]) { +#if RCT_DEBUG + if ([args count] != 0) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 0); + return; + } +#endif + + + + [componentView blur]; + return; +} + +if ([commandName isEqualToString:@"setValue"]) { +#if RCT_DEBUG + if ([args count] != 1) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 1); + return; + } +#endif + + NSObject *arg0 = args[0]; +#if RCT_DEBUG + if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSString class], @"string", @"EnrichedMarkdownInput", commandName, @"1st")) { + return; + } +#endif + NSString * markdown = (NSString *)arg0; + + [componentView setValue:markdown]; + return; +} + +if ([commandName isEqualToString:@"setSelection"]) { +#if RCT_DEBUG + if ([args count] != 2) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 2); + return; + } +#endif + + NSObject *arg0 = args[0]; +#if RCT_DEBUG + if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @"number", @"EnrichedMarkdownInput", commandName, @"1st")) { + return; + } +#endif + NSInteger start = [(NSNumber *)arg0 intValue]; + +NSObject *arg1 = args[1]; +#if RCT_DEBUG + if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSNumber class], @"number", @"EnrichedMarkdownInput", commandName, @"2nd")) { + return; + } +#endif + NSInteger end = [(NSNumber *)arg1 intValue]; + + [componentView setSelection:start end:end]; + return; +} + +if ([commandName isEqualToString:@"toggleBold"]) { +#if RCT_DEBUG + if ([args count] != 0) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 0); + return; + } +#endif + + + + [componentView toggleBold]; + return; +} + +if ([commandName isEqualToString:@"toggleItalic"]) { +#if RCT_DEBUG + if ([args count] != 0) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 0); + return; + } +#endif + + + + [componentView toggleItalic]; + return; +} + +if ([commandName isEqualToString:@"toggleUnderline"]) { +#if RCT_DEBUG + if ([args count] != 0) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 0); + return; + } +#endif + + + + [componentView toggleUnderline]; + return; +} + +if ([commandName isEqualToString:@"toggleStrikethrough"]) { +#if RCT_DEBUG + if ([args count] != 0) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 0); + return; + } +#endif + + + + [componentView toggleStrikethrough]; + return; +} + +if ([commandName isEqualToString:@"toggleSpoiler"]) { +#if RCT_DEBUG + if ([args count] != 0) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 0); + return; + } +#endif + + + + [componentView toggleSpoiler]; + return; +} + +if ([commandName isEqualToString:@"setLink"]) { +#if RCT_DEBUG + if ([args count] != 1) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 1); + return; + } +#endif + + NSObject *arg0 = args[0]; +#if RCT_DEBUG + if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSString class], @"string", @"EnrichedMarkdownInput", commandName, @"1st")) { + return; + } +#endif + NSString * url = (NSString *)arg0; + + [componentView setLink:url]; + return; +} + +if ([commandName isEqualToString:@"insertLink"]) { +#if RCT_DEBUG + if ([args count] != 2) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 2); + return; + } +#endif + + NSObject *arg0 = args[0]; +#if RCT_DEBUG + if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSString class], @"string", @"EnrichedMarkdownInput", commandName, @"1st")) { + return; + } +#endif + NSString * text = (NSString *)arg0; + +NSObject *arg1 = args[1]; +#if RCT_DEBUG + if (!RCTValidateTypeOfViewCommandArgument(arg1, [NSString class], @"string", @"EnrichedMarkdownInput", commandName, @"2nd")) { + return; + } +#endif + NSString * url = (NSString *)arg1; + + [componentView insertLink:text url:url]; + return; +} + +if ([commandName isEqualToString:@"removeLink"]) { +#if RCT_DEBUG + if ([args count] != 0) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 0); + return; + } +#endif + + + + [componentView removeLink]; + return; +} + +if ([commandName isEqualToString:@"requestMarkdown"]) { +#if RCT_DEBUG + if ([args count] != 1) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 1); + return; + } +#endif + + NSObject *arg0 = args[0]; +#if RCT_DEBUG + if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @"number", @"EnrichedMarkdownInput", commandName, @"1st")) { + return; + } +#endif + NSInteger requestId = [(NSNumber *)arg0 intValue]; + + [componentView requestMarkdown:requestId]; + return; +} + +if ([commandName isEqualToString:@"requestCaretRect"]) { +#if RCT_DEBUG + if ([args count] != 1) { + RCTLogError(@"%@ command %@ received %d arguments, expected %d.", @"EnrichedMarkdownInput", commandName, (int)[args count], 1); + return; + } +#endif + + NSObject *arg0 = args[0]; +#if RCT_DEBUG + if (!RCTValidateTypeOfViewCommandArgument(arg0, [NSNumber class], @"number", @"EnrichedMarkdownInput", commandName, @"1st")) { + return; + } +#endif + NSInteger requestId = [(NSNumber *)arg0 intValue]; + + [componentView requestCaretRect:requestId]; + return; +} + +#if RCT_DEBUG + RCTLogError(@"%@ received command %@, which is not a supported command.", @"EnrichedMarkdownInput", commandName); +#endif +} + +@protocol RCTEnrichedMarkdownViewProtocol + +@end + +@protocol RCTEnrichedMarkdownTextViewProtocol + +@end + +NS_ASSUME_NONNULL_END \ No newline at end of file diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ShadowNodes.cpp b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ShadowNodes.cpp new file mode 100644 index 00000000..feadde33 --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ShadowNodes.cpp @@ -0,0 +1,17 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateShadowNodeCpp.js + */ + +#include "ShadowNodes.h" + +namespace facebook::react { + + + +} // namespace facebook::react diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ShadowNodes.h b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ShadowNodes.h new file mode 100644 index 00000000..615dfa65 --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/ShadowNodes.h @@ -0,0 +1,23 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateShadowNodeH.js + */ + +#pragma once + +#include "EventEmitters.h" +#include "Props.h" +#include "States.h" +#include +#include + +namespace facebook::react { + + + +} // namespace facebook::react diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/States.cpp b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/States.cpp new file mode 100644 index 00000000..1dbb184c --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/States.cpp @@ -0,0 +1,16 @@ + +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateStateCpp.js + */ +#include "States.h" + +namespace facebook::react { + + + +} // namespace facebook::react diff --git a/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/States.h b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/States.h new file mode 100644 index 00000000..2e55bce7 --- /dev/null +++ b/ios/generated/ReactCodegen/EnrichedMarkdownTextSpec/States.h @@ -0,0 +1,20 @@ +/** + * This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen). + * + * Do not edit this file as changes may cause incorrect behavior and will be lost + * once the code is regenerated. + * + * @generated by codegen project: GenerateStateH.js + */ +#pragma once + +#include +#ifdef RN_SERIALIZABLE_STATE +#include +#endif + +namespace facebook::react { + + + +} // namespace facebook::react \ No newline at end of file diff --git a/lib/module/EnrichedMarkdownInput.js b/lib/module/EnrichedMarkdownInput.js new file mode 100644 index 00000000..11438b46 --- /dev/null +++ b/lib/module/EnrichedMarkdownInput.js @@ -0,0 +1,258 @@ +"use strict"; + +import { useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react'; +import EnrichedMarkdownInputNativeComponent, { Commands } from './EnrichedMarkdownInputNativeComponent'; +import { normalizeMarkdownInputStyle } from "./normalizeMarkdownInputStyle.js"; +import { toNativeRegexConfig } from "./utils/regexParser.js"; +import { jsx as _jsx } from "react/jsx-runtime"; +function getNativeRef(ref) { + if (ref.current == null) { + throw new Error('EnrichedMarkdownInput: native ref is not attached. Ensure the component is mounted.'); + } + return ref.current; +} +export const EnrichedMarkdownInput = ({ + ref, + markdownStyle, + style, + defaultValue, + placeholder, + placeholderTextColor, + editable = true, + autoFocus = false, + scrollEnabled = true, + autoCapitalize = 'sentences', + multiline = true, + cursorColor, + selectionColor, + onChangeText, + onChangeMarkdown, + onChangeSelection, + onChangeState, + onCaretRectChange, + onLinkDetected, + onFocus, + onBlur, + contextMenuItems, + linkRegex: _linkRegex +}) => { + const nativeRef = useRef(null); + const nextRequestId = useRef(1); + const pendingRequests = useRef(new Map()); + const pendingCaretRectRequests = useRef(new Map()); + const contextMenuCallbacksRef = useRef(new Map()); + useEffect(() => { + const callbacksMap = new Map(); + if (contextMenuItems) { + for (const item of contextMenuItems) { + callbacksMap.set(item.text, item.onPress); + } + } + contextMenuCallbacksRef.current = callbacksMap; + }, [contextMenuItems]); + const nativeContextMenuItems = useMemo(() => contextMenuItems?.filter(item => item.visible !== false).map(item => ({ + text: item.text, + icon: item.icon + })), [contextMenuItems]); + useEffect(() => { + const pending = pendingRequests.current; + const pendingCaretRect = pendingCaretRectRequests.current; + return () => { + const err = new Error('Component unmounted'); + pending.forEach(({ + reject + }) => reject(err)); + pending.clear(); + pendingCaretRect.forEach(({ + reject + }) => reject(err)); + pendingCaretRect.clear(); + }; + }, []); + const normalizedStyle = normalizeMarkdownInputStyle(markdownStyle); + const linkRegex = useMemo(() => toNativeRegexConfig(_linkRegex), [_linkRegex]); + const handleLinkDetected = useCallback(e => { + const { + text, + url, + start, + end + } = e.nativeEvent; + onLinkDetected?.({ + text, + url, + start, + end + }); + }, [onLinkDetected]); + const handleChangeText = useCallback(e => { + onChangeText?.(e.nativeEvent.value); + }, [onChangeText]); + const handleChangeMarkdown = useCallback(e => { + onChangeMarkdown?.(e.nativeEvent.value); + }, [onChangeMarkdown]); + const handleChangeSelection = useCallback(e => { + const { + start, + end + } = e.nativeEvent; + onChangeSelection?.({ + start, + end + }); + }, [onChangeSelection]); + const handleChangeState = useCallback(e => { + const { + bold, + italic, + underline, + strikethrough, + spoiler, + link + } = e.nativeEvent; + onChangeState?.({ + bold, + italic, + underline, + strikethrough, + spoiler, + link + }); + }, [onChangeState]); + const handleCaretRectChange = useCallback(e => { + const { + x, + y, + width, + height + } = e.nativeEvent; + onCaretRectChange?.({ + x, + y, + width, + height + }); + }, [onCaretRectChange]); + const handleFocus = useCallback(() => { + onFocus?.(); + }, [onFocus]); + const handleBlur = useCallback(() => { + onBlur?.(); + }, [onBlur]); + const handleRequestMarkdownResult = useCallback(e => { + const { + requestId, + markdown + } = e.nativeEvent; + const pending = pendingRequests.current.get(requestId); + if (!pending) return; + pending.resolve(markdown); + pendingRequests.current.delete(requestId); + }, []); + const handleRequestCaretRectResult = useCallback(e => { + const { + requestId, + x, + y, + width, + height + } = e.nativeEvent; + const pending = pendingCaretRectRequests.current.get(requestId); + if (!pending) return; + pending.resolve({ + x, + y, + width, + height + }); + pendingCaretRectRequests.current.delete(requestId); + }, []); + const handleContextMenuItemPress = useCallback(e => { + const { + itemText, + selectedText, + selectionStart, + selectionEnd, + styleState + } = e.nativeEvent; + const callback = contextMenuCallbacksRef.current.get(itemText); + callback?.({ + text: selectedText, + selection: { + start: selectionStart, + end: selectionEnd + }, + styleState + }); + }, []); + useImperativeHandle(ref, () => { + const node = getNativeRef(nativeRef); + // Codegen's ViewRef resolves to `never` with RN 0.84's function-based + // HostComponent type — the cast is safe at runtime. + const commandRef = node; + return { + measure: callback => node.measure(callback), + measureInWindow: callback => node.measureInWindow(callback), + measureLayout: (relativeToNativeNode, onSuccess, onFail) => node.measureLayout(relativeToNativeNode, onSuccess, onFail), + focus: () => Commands.focus(commandRef), + blur: () => Commands.blur(commandRef), + setValue: markdown => Commands.setValue(commandRef, markdown), + setSelection: (start, end) => Commands.setSelection(commandRef, start, end), + toggleBold: () => Commands.toggleBold(commandRef), + toggleItalic: () => Commands.toggleItalic(commandRef), + toggleUnderline: () => Commands.toggleUnderline(commandRef), + toggleStrikethrough: () => Commands.toggleStrikethrough(commandRef), + toggleSpoiler: () => Commands.toggleSpoiler(commandRef), + setLink: url => Commands.setLink(commandRef, url), + insertLink: (text, url) => Commands.insertLink(commandRef, text, url), + removeLink: () => Commands.removeLink(commandRef), + getMarkdown: () => new Promise((resolve, reject) => { + const requestId = nextRequestId.current++; + pendingRequests.current.set(requestId, { + resolve, + reject + }); + Commands.requestMarkdown(commandRef, requestId); + }), + getCaretRect: () => new Promise((resolve, reject) => { + const requestId = nextRequestId.current++; + pendingCaretRectRequests.current.set(requestId, { + resolve, + reject + }); + Commands.requestCaretRect(commandRef, requestId); + }) + }; + }); + return /*#__PURE__*/_jsx(EnrichedMarkdownInputNativeComponent, { + ref: nativeRef, + style: style, + markdownStyle: normalizedStyle, + defaultValue: defaultValue, + placeholder: placeholder, + placeholderTextColor: placeholderTextColor, + editable: editable, + autoFocus: autoFocus, + scrollEnabled: scrollEnabled, + autoCapitalize: autoCapitalize, + multiline: multiline, + cursorColor: cursorColor, + selectionColor: selectionColor, + isOnChangeMarkdownSet: onChangeMarkdown !== undefined, + onChangeText: handleChangeText, + onChangeMarkdown: handleChangeMarkdown, + onChangeSelection: handleChangeSelection, + onChangeState: handleChangeState, + onLinkDetected: handleLinkDetected, + onInputFocus: handleFocus, + onInputBlur: handleBlur, + onRequestMarkdownResult: handleRequestMarkdownResult, + onRequestCaretRectResult: handleRequestCaretRectResult, + onCaretRectChange: handleCaretRectChange, + contextMenuItems: nativeContextMenuItems, + onContextMenuItemPress: handleContextMenuItemPress, + linkRegex: linkRegex + }); +}; +export default EnrichedMarkdownInput; +//# sourceMappingURL=EnrichedMarkdownInput.js.map \ No newline at end of file diff --git a/lib/module/EnrichedMarkdownInput.js.map b/lib/module/EnrichedMarkdownInput.js.map new file mode 100644 index 00000000..2d57b0e7 --- /dev/null +++ b/lib/module/EnrichedMarkdownInput.js.map @@ -0,0 +1 @@ +{"version":3,"names":["useCallback","useEffect","useImperativeHandle","useMemo","useRef","EnrichedMarkdownInputNativeComponent","Commands","normalizeMarkdownInputStyle","toNativeRegexConfig","jsx","_jsx","getNativeRef","ref","current","Error","EnrichedMarkdownInput","markdownStyle","style","defaultValue","placeholder","placeholderTextColor","editable","autoFocus","scrollEnabled","autoCapitalize","multiline","cursorColor","selectionColor","onChangeText","onChangeMarkdown","onChangeSelection","onChangeState","onCaretRectChange","onLinkDetected","onFocus","onBlur","contextMenuItems","linkRegex","_linkRegex","nativeRef","nextRequestId","pendingRequests","Map","pendingCaretRectRequests","contextMenuCallbacksRef","callbacksMap","item","set","text","onPress","nativeContextMenuItems","filter","visible","map","icon","pending","pendingCaretRect","err","forEach","reject","clear","normalizedStyle","handleLinkDetected","e","url","start","end","nativeEvent","handleChangeText","value","handleChangeMarkdown","handleChangeSelection","handleChangeState","bold","italic","underline","strikethrough","spoiler","link","handleCaretRectChange","x","y","width","height","handleFocus","handleBlur","handleRequestMarkdownResult","requestId","markdown","get","resolve","delete","handleRequestCaretRectResult","handleContextMenuItemPress","itemText","selectedText","selectionStart","selectionEnd","styleState","callback","selection","node","commandRef","measure","measureInWindow","measureLayout","relativeToNativeNode","onSuccess","onFail","focus","blur","setValue","setSelection","toggleBold","toggleItalic","toggleUnderline","toggleStrikethrough","toggleSpoiler","setLink","insertLink","removeLink","getMarkdown","Promise","requestMarkdown","getCaretRect","requestCaretRect","isOnChangeMarkdownSet","undefined","onInputFocus","onInputBlur","onRequestMarkdownResult","onRequestCaretRectResult","onContextMenuItemPress"],"sourceRoot":"../../src","sources":["EnrichedMarkdownInput.tsx"],"mappings":";;AAAA,SACEA,WAAW,EACXC,SAAS,EACTC,mBAAmB,EACnBC,OAAO,EACPC,MAAM,QACD,OAAO;AAEd,OAAOC,oCAAoC,IACzCC,QAAQ,QAWH,wCAAwC;AAS/C,SAASC,2BAA2B,QAAQ,kCAA+B;AAC3E,SAASC,mBAAmB,QAAQ,wBAAqB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAoG1D,SAASC,YAAYA,CAACC,GAAsC,EAAa;EACvE,IAAIA,GAAG,CAACC,OAAO,IAAI,IAAI,EAAE;IACvB,MAAM,IAAIC,KAAK,CACb,qFACF,CAAC;EACH;EACA,OAAOF,GAAG,CAACC,OAAO;AACpB;AAEA,OAAO,MAAME,qBAAqB,GAAGA,CAAC;EACpCH,GAAG;EACHI,aAAa;EACbC,KAAK;EACLC,YAAY;EACZC,WAAW;EACXC,oBAAoB;EACpBC,QAAQ,GAAG,IAAI;EACfC,SAAS,GAAG,KAAK;EACjBC,aAAa,GAAG,IAAI;EACpBC,cAAc,GAAG,WAAW;EAC5BC,SAAS,GAAG,IAAI;EAChBC,WAAW;EACXC,cAAc;EACdC,YAAY;EACZC,gBAAgB;EAChBC,iBAAiB;EACjBC,aAAa;EACbC,iBAAiB;EACjBC,cAAc;EACdC,OAAO;EACPC,MAAM;EACNC,gBAAgB;EAChBC,SAAS,EAAEC;AACe,CAAC,KAAK;EAChC,MAAMC,SAAS,GAAGnC,MAAM,CAAmB,IAAI,CAAC;EAEhD,MAAMoC,aAAa,GAAGpC,MAAM,CAAC,CAAC,CAAC;EAC/B,MAAMqC,eAAe,GAAGrC,MAAM,CAAC,IAAIsC,GAAG,CAAiC,CAAC,CAAC;EACzE,MAAMC,wBAAwB,GAAGvC,MAAM,CACrC,IAAIsC,GAAG,CAAoC,CAC7C,CAAC;EAED,MAAME,uBAAuB,GAAGxC,MAAM,CAEpC,IAAIsC,GAAG,CAAC,CAAC,CAAC;EAEZzC,SAAS,CAAC,MAAM;IACd,MAAM4C,YAAY,GAAG,IAAIH,GAAG,CAAqC,CAAC;IAClE,IAAIN,gBAAgB,EAAE;MACpB,KAAK,MAAMU,IAAI,IAAIV,gBAAgB,EAAE;QACnCS,YAAY,CAACE,GAAG,CAACD,IAAI,CAACE,IAAI,EAAEF,IAAI,CAACG,OAAO,CAAC;MAC3C;IACF;IACAL,uBAAuB,CAAC/B,OAAO,GAAGgC,YAAY;EAChD,CAAC,EAAE,CAACT,gBAAgB,CAAC,CAAC;EAEtB,MAAMc,sBAAsB,GAAG/C,OAAO,CACpC,MACEiC,gBAAgB,EACZe,MAAM,CAAEL,IAAI,IAAKA,IAAI,CAACM,OAAO,KAAK,KAAK,CAAC,CACzCC,GAAG,CAAEP,IAAI,KAAM;IAAEE,IAAI,EAAEF,IAAI,CAACE,IAAI;IAAEM,IAAI,EAAER,IAAI,CAACQ;EAAK,CAAC,CAAC,CAAC,EAC1D,CAAClB,gBAAgB,CACnB,CAAC;EAEDnC,SAAS,CAAC,MAAM;IACd,MAAMsD,OAAO,GAAGd,eAAe,CAAC5B,OAAO;IACvC,MAAM2C,gBAAgB,GAAGb,wBAAwB,CAAC9B,OAAO;IACzD,OAAO,MAAM;MACX,MAAM4C,GAAG,GAAG,IAAI3C,KAAK,CAAC,qBAAqB,CAAC;MAC5CyC,OAAO,CAACG,OAAO,CAAC,CAAC;QAAEC;MAAO,CAAC,KAAKA,MAAM,CAACF,GAAG,CAAC,CAAC;MAC5CF,OAAO,CAACK,KAAK,CAAC,CAAC;MACfJ,gBAAgB,CAACE,OAAO,CAAC,CAAC;QAAEC;MAAO,CAAC,KAAKA,MAAM,CAACF,GAAG,CAAC,CAAC;MACrDD,gBAAgB,CAACI,KAAK,CAAC,CAAC;IAC1B,CAAC;EACH,CAAC,EAAE,EAAE,CAAC;EAEN,MAAMC,eAAe,GAAGtD,2BAA2B,CAACS,aAAa,CAAC;EAElE,MAAMqB,SAAS,GAAGlC,OAAO,CACvB,MAAMK,mBAAmB,CAAC8B,UAAU,CAAC,EACrC,CAACA,UAAU,CACb,CAAC;EAED,MAAMwB,kBAAkB,GAAG9D,WAAW,CACnC+D,CAAuC,IAAK;IAC3C,MAAM;MAAEf,IAAI;MAAEgB,GAAG;MAAEC,KAAK;MAAEC;IAAI,CAAC,GAAGH,CAAC,CAACI,WAAW;IAC/ClC,cAAc,GAAG;MAAEe,IAAI;MAAEgB,GAAG;MAAEC,KAAK;MAAEC;IAAI,CAAC,CAAC;EAC7C,CAAC,EACD,CAACjC,cAAc,CACjB,CAAC;EAED,MAAMmC,gBAAgB,GAAGpE,WAAW,CACjC+D,CAA0C,IAAK;IAC9CnC,YAAY,GAAGmC,CAAC,CAACI,WAAW,CAACE,KAAK,CAAC;EACrC,CAAC,EACD,CAACzC,YAAY,CACf,CAAC;EAED,MAAM0C,oBAAoB,GAAGtE,WAAW,CACrC+D,CAA8C,IAAK;IAClDlC,gBAAgB,GAAGkC,CAAC,CAACI,WAAW,CAACE,KAAK,CAAC;EACzC,CAAC,EACD,CAACxC,gBAAgB,CACnB,CAAC;EAED,MAAM0C,qBAAqB,GAAGvE,WAAW,CACtC+D,CAA+C,IAAK;IACnD,MAAM;MAAEE,KAAK;MAAEC;IAAI,CAAC,GAAGH,CAAC,CAACI,WAAW;IACpCrC,iBAAiB,GAAG;MAAEmC,KAAK;MAAEC;IAAI,CAAC,CAAC;EACrC,CAAC,EACD,CAACpC,iBAAiB,CACpB,CAAC;EAED,MAAM0C,iBAAiB,GAAGxE,WAAW,CAClC+D,CAA2C,IAAK;IAC/C,MAAM;MAAEU,IAAI;MAAEC,MAAM;MAAEC,SAAS;MAAEC,aAAa;MAAEC,OAAO;MAAEC;IAAK,CAAC,GAC7Df,CAAC,CAACI,WAAW;IACfpC,aAAa,GAAG;MACd0C,IAAI;MACJC,MAAM;MACNC,SAAS;MACTC,aAAa;MACbC,OAAO;MACPC;IACF,CAAC,CAAC;EACJ,CAAC,EACD,CAAC/C,aAAa,CAChB,CAAC;EAED,MAAMgD,qBAAqB,GAAG/E,WAAW,CACtC+D,CAA+C,IAAK;IACnD,MAAM;MAAEiB,CAAC;MAAEC,CAAC;MAAEC,KAAK;MAAEC;IAAO,CAAC,GAAGpB,CAAC,CAACI,WAAW;IAC7CnC,iBAAiB,GAAG;MAAEgD,CAAC;MAAEC,CAAC;MAAEC,KAAK;MAAEC;IAAO,CAAC,CAAC;EAC9C,CAAC,EACD,CAACnD,iBAAiB,CACpB,CAAC;EAED,MAAMoD,WAAW,GAAGpF,WAAW,CAAC,MAAM;IACpCkC,OAAO,GAAG,CAAC;EACb,CAAC,EAAE,CAACA,OAAO,CAAC,CAAC;EAEb,MAAMmD,UAAU,GAAGrF,WAAW,CAAC,MAAM;IACnCmC,MAAM,GAAG,CAAC;EACZ,CAAC,EAAE,CAACA,MAAM,CAAC,CAAC;EAEZ,MAAMmD,2BAA2B,GAAGtF,WAAW,CAC5C+D,CAAqD,IAAK;IACzD,MAAM;MAAEwB,SAAS;MAAEC;IAAS,CAAC,GAAGzB,CAAC,CAACI,WAAW;IAC7C,MAAMZ,OAAO,GAAGd,eAAe,CAAC5B,OAAO,CAAC4E,GAAG,CAACF,SAAS,CAAC;IACtD,IAAI,CAAChC,OAAO,EAAE;IAEdA,OAAO,CAACmC,OAAO,CAACF,QAAQ,CAAC;IACzB/C,eAAe,CAAC5B,OAAO,CAAC8E,MAAM,CAACJ,SAAS,CAAC;EAC3C,CAAC,EACD,EACF,CAAC;EAED,MAAMK,4BAA4B,GAAG5F,WAAW,CAC7C+D,CAAsD,IAAK;IAC1D,MAAM;MAAEwB,SAAS;MAAEP,CAAC;MAAEC,CAAC;MAAEC,KAAK;MAAEC;IAAO,CAAC,GAAGpB,CAAC,CAACI,WAAW;IACxD,MAAMZ,OAAO,GAAGZ,wBAAwB,CAAC9B,OAAO,CAAC4E,GAAG,CAACF,SAAS,CAAC;IAC/D,IAAI,CAAChC,OAAO,EAAE;IAEdA,OAAO,CAACmC,OAAO,CAAC;MAAEV,CAAC;MAAEC,CAAC;MAAEC,KAAK;MAAEC;IAAO,CAAC,CAAC;IACxCxC,wBAAwB,CAAC9B,OAAO,CAAC8E,MAAM,CAACJ,SAAS,CAAC;EACpD,CAAC,EACD,EACF,CAAC;EAED,MAAMM,0BAA0B,GAAG7F,WAAW,CAC3C+D,CAAoD,IAAK;IACxD,MAAM;MACJ+B,QAAQ;MACRC,YAAY;MACZC,cAAc;MACdC,YAAY;MACZC;IACF,CAAC,GAAGnC,CAAC,CAACI,WAAW;IACjB,MAAMgC,QAAQ,GAAGvD,uBAAuB,CAAC/B,OAAO,CAAC4E,GAAG,CAACK,QAAQ,CAAC;IAC9DK,QAAQ,GAAG;MACTnD,IAAI,EAAE+C,YAAY;MAClBK,SAAS,EAAE;QAAEnC,KAAK,EAAE+B,cAAc;QAAE9B,GAAG,EAAE+B;MAAa,CAAC;MACvDC;IACF,CAAC,CAAC;EACJ,CAAC,EACD,EACF,CAAC;EAEDhG,mBAAmB,CAACU,GAAG,EAAE,MAAM;IAC7B,MAAMyF,IAAI,GAAG1F,YAAY,CAAC4B,SAAS,CAAC;IACpC;IACA;IACA,MAAM+D,UAAU,GAAGD,IAAiD;IACpE,OAAO;MACLE,OAAO,EAAGJ,QAAQ,IAAKE,IAAI,CAACE,OAAO,CAACJ,QAAQ,CAAC;MAC7CK,eAAe,EAAGL,QAAQ,IAAKE,IAAI,CAACG,eAAe,CAACL,QAAQ,CAAC;MAC7DM,aAAa,EAAEA,CAACC,oBAAoB,EAAEC,SAAS,EAAEC,MAAM,KACrDP,IAAI,CAACI,aAAa,CAACC,oBAAoB,EAAEC,SAAS,EAAEC,MAAM,CAAC;MAC7DC,KAAK,EAAEA,CAAA,KAAMvG,QAAQ,CAACuG,KAAK,CAACP,UAAU,CAAC;MACvCQ,IAAI,EAAEA,CAAA,KAAMxG,QAAQ,CAACwG,IAAI,CAACR,UAAU,CAAC;MACrCS,QAAQ,EAAGvB,QAAQ,IAAKlF,QAAQ,CAACyG,QAAQ,CAACT,UAAU,EAAEd,QAAQ,CAAC;MAC/DwB,YAAY,EAAEA,CAAC/C,KAAK,EAAEC,GAAG,KACvB5D,QAAQ,CAAC0G,YAAY,CAACV,UAAU,EAAErC,KAAK,EAAEC,GAAG,CAAC;MAC/C+C,UAAU,EAAEA,CAAA,KAAM3G,QAAQ,CAAC2G,UAAU,CAACX,UAAU,CAAC;MACjDY,YAAY,EAAEA,CAAA,KAAM5G,QAAQ,CAAC4G,YAAY,CAACZ,UAAU,CAAC;MACrDa,eAAe,EAAEA,CAAA,KAAM7G,QAAQ,CAAC6G,eAAe,CAACb,UAAU,CAAC;MAC3Dc,mBAAmB,EAAEA,CAAA,KAAM9G,QAAQ,CAAC8G,mBAAmB,CAACd,UAAU,CAAC;MACnEe,aAAa,EAAEA,CAAA,KAAM/G,QAAQ,CAAC+G,aAAa,CAACf,UAAU,CAAC;MACvDgB,OAAO,EAAGtD,GAAG,IAAK1D,QAAQ,CAACgH,OAAO,CAAChB,UAAU,EAAEtC,GAAG,CAAC;MACnDuD,UAAU,EAAEA,CAACvE,IAAI,EAAEgB,GAAG,KAAK1D,QAAQ,CAACiH,UAAU,CAACjB,UAAU,EAAEtD,IAAI,EAAEgB,GAAG,CAAC;MACrEwD,UAAU,EAAEA,CAAA,KAAMlH,QAAQ,CAACkH,UAAU,CAAClB,UAAU,CAAC;MACjDmB,WAAW,EAAEA,CAAA,KACX,IAAIC,OAAO,CAAS,CAAChC,OAAO,EAAE/B,MAAM,KAAK;QACvC,MAAM4B,SAAS,GAAG/C,aAAa,CAAC3B,OAAO,EAAE;QACzC4B,eAAe,CAAC5B,OAAO,CAACkC,GAAG,CAACwC,SAAS,EAAE;UAAEG,OAAO;UAAE/B;QAAO,CAAC,CAAC;QAC3DrD,QAAQ,CAACqH,eAAe,CAACrB,UAAU,EAAEf,SAAS,CAAC;MACjD,CAAC,CAAC;MACJqC,YAAY,EAAEA,CAAA,KACZ,IAAIF,OAAO,CAAY,CAAChC,OAAO,EAAE/B,MAAM,KAAK;QAC1C,MAAM4B,SAAS,GAAG/C,aAAa,CAAC3B,OAAO,EAAE;QACzC8B,wBAAwB,CAAC9B,OAAO,CAACkC,GAAG,CAACwC,SAAS,EAAE;UAC9CG,OAAO;UACP/B;QACF,CAAC,CAAC;QACFrD,QAAQ,CAACuH,gBAAgB,CAACvB,UAAU,EAAEf,SAAS,CAAC;MAClD,CAAC;IACL,CAAC;EACH,CAAC,CAAC;EAEF,oBACE7E,IAAA,CAACL,oCAAoC;IACnCO,GAAG,EAAE2B,SAAU;IACftB,KAAK,EAAEA,KAAM;IACbD,aAAa,EAAE6C,eAAgB;IAC/B3C,YAAY,EAAEA,YAAa;IAC3BC,WAAW,EAAEA,WAAY;IACzBC,oBAAoB,EAAEA,oBAAqB;IAC3CC,QAAQ,EAAEA,QAAS;IACnBC,SAAS,EAAEA,SAAU;IACrBC,aAAa,EAAEA,aAAc;IAC7BC,cAAc,EAAEA,cAAe;IAC/BC,SAAS,EAAEA,SAAU;IACrBC,WAAW,EAAEA,WAAY;IACzBC,cAAc,EAAEA,cAAe;IAC/BmG,qBAAqB,EAAEjG,gBAAgB,KAAKkG,SAAU;IACtDnG,YAAY,EAAEwC,gBAAgD;IAC9DvC,gBAAgB,EAAEyC,oBAAwD;IAC1ExC,iBAAiB,EACfyC,qBACD;IACDxC,aAAa,EAAEyC,iBAAkD;IACjEvC,cAAc,EAAE6B,kBAAoD;IACpEkE,YAAY,EAAE5C,WAA2C;IACzD6C,WAAW,EAAE5C,UAAyC;IACtD6C,uBAAuB,EACrB5C,2BACD;IACD6C,wBAAwB,EACtBvC,4BACD;IACD5D,iBAAiB,EACf+C,qBACD;IACD3C,gBAAgB,EAAEc,sBAAuB;IACzCkF,sBAAsB,EACpBvC,0BACD;IACDxD,SAAS,EAAEA;EAAU,CACtB,CAAC;AAEN,CAAC;AAED,eAAetB,qBAAqB","ignoreList":[]} diff --git a/lib/module/EnrichedMarkdownInputNativeComponent.ts b/lib/module/EnrichedMarkdownInputNativeComponent.ts new file mode 100644 index 00000000..0921228f --- /dev/null +++ b/lib/module/EnrichedMarkdownInputNativeComponent.ts @@ -0,0 +1,256 @@ +import { + codegenNativeComponent, + codegenNativeCommands, + type ViewProps, + type ColorValue, + type HostComponent, + type CodegenTypes, +} from 'react-native'; +import type React from 'react'; + +interface MarkdownInputStyleInternal { + strong: { + color?: ColorValue; + }; + em: { + color?: ColorValue; + }; + link: { + color: ColorValue; + underline: boolean; + }; + spoiler: { + color: ColorValue; + backgroundColor: ColorValue; + }; +} + +interface TargetedEvent { + target: CodegenTypes.Int32; +} + +export interface OnChangeTextEvent { + value: string; +} + +export interface OnChangeMarkdownEvent { + value: string; +} + +export interface OnChangeSelectionEvent { + start: CodegenTypes.Int32; + end: CodegenTypes.Int32; +} + +export interface OnChangeStateEvent { + bold: { isActive: boolean }; + italic: { isActive: boolean }; + underline: { isActive: boolean }; + strikethrough: { isActive: boolean }; + spoiler: { isActive: boolean }; + link: { isActive: boolean }; +} + +export interface OnRequestMarkdownResultEvent { + requestId: CodegenTypes.Int32; + markdown: string; +} + +export interface OnRequestCaretRectResultEvent { + requestId: CodegenTypes.Int32; + x: CodegenTypes.Double; + y: CodegenTypes.Double; + width: CodegenTypes.Double; + height: CodegenTypes.Double; +} + +export interface OnCaretRectChangeEvent { + x: CodegenTypes.Double; + y: CodegenTypes.Double; + width: CodegenTypes.Double; + height: CodegenTypes.Double; +} + +export interface LinkNativeRegex { + pattern: string; + caseInsensitive: boolean; + dotAll: boolean; + isDisabled: boolean; + isDefault: boolean; +} + +export interface OnLinkDetected { + text: string; + url: string; + start: CodegenTypes.Int32; + end: CodegenTypes.Int32; +} + +export interface ContextMenuItemConfig { + text: string; + icon?: string; +} + +export interface OnContextMenuItemPressEvent { + itemText: string; + selectedText: string; + selectionStart: CodegenTypes.Int32; + selectionEnd: CodegenTypes.Int32; + styleState: { + bold: { isActive: boolean }; + italic: { isActive: boolean }; + underline: { isActive: boolean }; + strikethrough: { isActive: boolean }; + spoiler: { isActive: boolean }; + link: { isActive: boolean }; + }; +} + +export interface NativeProps extends ViewProps { + /** + * Initial markdown content. + */ + defaultValue?: string; + /** + * Placeholder text shown when the input is empty. + */ + placeholder?: string; + /** + * Color of the placeholder text. + */ + placeholderTextColor?: ColorValue; + /** + * Whether the input is editable. + * @default true + */ + editable?: boolean; + /** + * Whether the input should auto-focus on mount. + * @default false + */ + autoFocus?: boolean; + /** + * Whether the input is scrollable. + * @default true + */ + scrollEnabled?: boolean; + /** + * Auto-capitalization behavior. + */ + autoCapitalize?: string; + /** + * Whether the input supports multiple lines. + * @default true + */ + multiline?: boolean; + /** + * Color of the cursor. + */ + cursorColor?: ColorValue; + /** + * Color of the text selection highlight. + */ + selectionColor?: ColorValue; + /** + * Inline format style overrides. + * Always provided with complete defaults via normalizeMarkdownInputStyle. + */ + markdownStyle: MarkdownInputStyleInternal; + + // These should not be passed as regular props. + color?: ColorValue; + fontSize?: CodegenTypes.Float; + lineHeight?: CodegenTypes.Float; + fontFamily?: string; + fontWeight?: string; + + /** + * Whether onChangeMarkdown handler is set. When true, the native side + * serializes formatting ranges to Markdown on every change. + */ + isOnChangeMarkdownSet?: boolean; + + /** + * Custom items to show in the text selection context menu. + * Each item is shown by its `text` label; invisible items should be filtered out before passing here. + */ + contextMenuItems?: ReadonlyArray>; + + /** + * Regex configuration for automatic link detection. + * Omit or pass undefined to use platform defaults. + */ + linkRegex?: Readonly; + + // Events + onChangeText?: CodegenTypes.DirectEventHandler; + onChangeMarkdown?: CodegenTypes.DirectEventHandler; + onChangeSelection?: CodegenTypes.DirectEventHandler; + onChangeState?: CodegenTypes.DirectEventHandler; + onInputFocus?: CodegenTypes.DirectEventHandler; + onInputBlur?: CodegenTypes.DirectEventHandler; + onRequestMarkdownResult?: CodegenTypes.DirectEventHandler; + onRequestCaretRectResult?: CodegenTypes.DirectEventHandler; + onCaretRectChange?: CodegenTypes.DirectEventHandler; + onContextMenuItemPress?: CodegenTypes.DirectEventHandler; + onLinkDetected?: CodegenTypes.DirectEventHandler; +} + +type ComponentType = HostComponent; + +interface NativeCommands { + focus: (viewRef: React.ElementRef) => void; + blur: (viewRef: React.ElementRef) => void; + setValue: ( + viewRef: React.ElementRef, + markdown: string + ) => void; + setSelection: ( + viewRef: React.ElementRef, + start: CodegenTypes.Int32, + end: CodegenTypes.Int32 + ) => void; + toggleBold: (viewRef: React.ElementRef) => void; + toggleItalic: (viewRef: React.ElementRef) => void; + toggleUnderline: (viewRef: React.ElementRef) => void; + toggleStrikethrough: (viewRef: React.ElementRef) => void; + toggleSpoiler: (viewRef: React.ElementRef) => void; + setLink: (viewRef: React.ElementRef, url: string) => void; + insertLink: ( + viewRef: React.ElementRef, + text: string, + url: string + ) => void; + removeLink: (viewRef: React.ElementRef) => void; + requestMarkdown: ( + viewRef: React.ElementRef, + requestId: CodegenTypes.Int32 + ) => void; + requestCaretRect: ( + viewRef: React.ElementRef, + requestId: CodegenTypes.Int32 + ) => void; +} + +export const Commands: NativeCommands = codegenNativeCommands({ + supportedCommands: [ + 'focus', + 'blur', + 'setValue', + 'setSelection', + 'toggleBold', + 'toggleItalic', + 'toggleUnderline', + 'toggleStrikethrough', + 'toggleSpoiler', + 'setLink', + 'insertLink', + 'removeLink', + 'requestMarkdown', + 'requestCaretRect', + ], +}); + +export default codegenNativeComponent('EnrichedMarkdownInput', { + interfaceOnly: true, +}) as HostComponent; diff --git a/lib/module/EnrichedMarkdownNativeComponent.ts b/lib/module/EnrichedMarkdownNativeComponent.ts new file mode 100644 index 00000000..2b86296c --- /dev/null +++ b/lib/module/EnrichedMarkdownNativeComponent.ts @@ -0,0 +1,392 @@ +import { + codegenNativeComponent, + type ViewProps, + type CodegenTypes, + type ColorValue, +} from 'react-native'; + +// All block styles extend this interface +interface BaseBlockStyleInternal { + fontSize: CodegenTypes.Float; + fontFamily: string; + fontWeight: string; + color: ColorValue; + marginTop: CodegenTypes.Float; + marginBottom: CodegenTypes.Float; + lineHeight: CodegenTypes.Float; +} + +interface ParagraphStyleInternal extends BaseBlockStyleInternal { + textAlign: string; +} + +interface HeadingStyleInternal extends BaseBlockStyleInternal { + textAlign: string; +} + +interface BlockquoteStyleInternal extends BaseBlockStyleInternal { + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + gapWidth: CodegenTypes.Float; + backgroundColor: ColorValue; +} + +interface ListStyleInternal extends BaseBlockStyleInternal { + bulletColor: ColorValue; + bulletSize: CodegenTypes.Float; + markerColor: ColorValue; + markerFontWeight: string; + gapWidth: CodegenTypes.Float; + marginLeft: CodegenTypes.Float; +} + +interface CodeBlockStyleInternal extends BaseBlockStyleInternal { + backgroundColor: ColorValue; + borderColor: ColorValue; + borderRadius: CodegenTypes.Float; + borderWidth: CodegenTypes.Float; + padding: CodegenTypes.Float; +} + +interface LinkStyleInternal { + fontFamily: string; + color: ColorValue; + underline: boolean; +} + +interface StrongStyleInternal { + fontFamily: string; + fontWeight: string; + color?: ColorValue; +} + +interface EmphasisStyleInternal { + fontFamily: string; + fontStyle: string; + color?: ColorValue; +} + +interface StrikethroughStyleInternal { + color: ColorValue; +} + +interface UnderlineStyleInternal { + color: ColorValue; +} + +interface CodeStyleInternal { + fontFamily: string; + fontSize: CodegenTypes.Float; + color: ColorValue; + backgroundColor: ColorValue; + borderColor: ColorValue; +} + +interface ImageStyleInternal { + height: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; + marginTop: CodegenTypes.Float; + marginBottom: CodegenTypes.Float; +} + +interface InlineImageStyleInternal { + size: CodegenTypes.Float; +} + +interface ThematicBreakStyleInternal { + color: ColorValue; + height: CodegenTypes.Float; + marginTop: CodegenTypes.Float; + marginBottom: CodegenTypes.Float; +} + +interface TableStyleInternal extends BaseBlockStyleInternal { + headerFontFamily: string; + headerBackgroundColor: ColorValue; + headerTextColor: ColorValue; + rowEvenBackgroundColor: ColorValue; + rowOddBackgroundColor: ColorValue; + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; + cellPaddingHorizontal: CodegenTypes.Float; + cellPaddingVertical: CodegenTypes.Float; +} + +interface TaskListStyleInternal { + checkedColor: ColorValue; + borderColor: ColorValue; + checkboxSize: CodegenTypes.Float; + checkboxBorderRadius: CodegenTypes.Float; + checkmarkColor: ColorValue; + checkedTextColor: ColorValue; + checkedStrikethrough: boolean; +} + +interface MathStyleInternal { + fontSize: CodegenTypes.Float; + color: ColorValue; + backgroundColor: ColorValue; + padding: CodegenTypes.Float; + marginTop: CodegenTypes.Float; + marginBottom: CodegenTypes.Float; + textAlign: string; +} + +interface InlineMathStyleInternal { + color: ColorValue; +} + +interface SpoilerParticlesStyleInternal { + density: CodegenTypes.Float; + speed: CodegenTypes.Float; +} + +interface SpoilerSolidStyleInternal { + borderRadius: CodegenTypes.Float; +} + +interface SpoilerStyleInternal { + color: ColorValue; + particles: SpoilerParticlesStyleInternal; + solid: SpoilerSolidStyleInternal; +} + +interface MentionStyleInternal { + color: ColorValue; + backgroundColor: ColorValue; + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; + paddingHorizontal: CodegenTypes.Float; + paddingVertical: CodegenTypes.Float; + fontFamily: string; + fontWeight: string; + fontSize: CodegenTypes.Float; + pressedOpacity: CodegenTypes.Float; +} + +interface CitationStyleInternal { + color: ColorValue; + fontSizeMultiplier: CodegenTypes.Float; + baselineOffsetPx: CodegenTypes.Float; + fontWeight: string; + underline: boolean; + backgroundColor: ColorValue; + paddingHorizontal: CodegenTypes.Float; + paddingVertical: CodegenTypes.Float; + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; +} + +export interface MarkdownStyleInternal { + paragraph: ParagraphStyleInternal; + h1: HeadingStyleInternal; + h2: HeadingStyleInternal; + h3: HeadingStyleInternal; + h4: HeadingStyleInternal; + h5: HeadingStyleInternal; + h6: HeadingStyleInternal; + blockquote: BlockquoteStyleInternal; + list: ListStyleInternal; + codeBlock: CodeBlockStyleInternal; + link: LinkStyleInternal; + strong: StrongStyleInternal; + em: EmphasisStyleInternal; + strikethrough: StrikethroughStyleInternal; + underline: UnderlineStyleInternal; + code: CodeStyleInternal; + image: ImageStyleInternal; + inlineImage: InlineImageStyleInternal; + thematicBreak: ThematicBreakStyleInternal; + table: TableStyleInternal; + taskList: TaskListStyleInternal; + math: MathStyleInternal; + inlineMath: InlineMathStyleInternal; + spoiler: SpoilerStyleInternal; + mention: MentionStyleInternal; + citation: CitationStyleInternal; +} + +export interface LinkPressEvent { + url: string; +} + +export interface LinkLongPressEvent { + url: string; +} + +export interface MentionPressEvent { + url: string; + text: string; +} + +export interface CitationPressEvent { + url: string; + text: string; +} + +export interface TaskListItemPressEvent { + index: CodegenTypes.Int32; + checked: boolean; + text: string; +} + +export interface ContextMenuItemConfig { + text: string; + icon?: string; +} + +export interface OnContextMenuItemPressEvent { + itemText: string; + selectedText: string; + selectionStart: CodegenTypes.Int32; + selectionEnd: CodegenTypes.Int32; +} + +/** + * MD4C parser flags configuration. + * Controls how the markdown parser interprets certain syntax. + */ +export interface Md4cFlagsInternal { + /** + * Enable underline syntax support (__text__). + * When enabled, underscores are treated as underline markers. + * When disabled, underscores are treated as emphasis markers (same as asterisks). + * @default false + */ + underline: boolean; + /** + * Enable LaTeX math span parsing ($..$ and $$..$$). + * When disabled, dollar signs are treated as plain text. + * @default true + */ + latexMath: boolean; +} + +export interface NativeProps extends ViewProps { + /** + * Markdown content to render. + */ + markdown: string; + /** + * Internal style configuration for markdown elements. + * Always provided with complete defaults via normalizeMarkdownStyle. + * Block styles (paragraph, headings) contain fontSize, fontFamily, fontWeight, and color. + */ + markdownStyle: MarkdownStyleInternal; + /** + * Callback fired when a link is pressed. + * Receives the URL that was tapped. + */ + onLinkPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when a link is long pressed. + * Receives the URL that was long pressed. + * - iOS: When provided, overrides the system link preview behavior. + * - Android: Handles long press gestures on links. + */ + onLinkLongPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when a task list checkbox is tapped. + * Receives the 0-based task index, current checked state, and the item's plain text. + */ + onTaskListItemPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when an inline mention pill (`mention://`) is pressed. + */ + onMentionPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when an inline citation (`citation://`) is pressed. + */ + onCitationPress?: CodegenTypes.BubblingEventHandler; + /** + * Controls whether the system link preview is shown on long press (iOS only). + * + * When `true` (default), long-pressing a link shows the native iOS link preview. + * When `false`, the system preview is suppressed. + * + * Automatically set to `false` when `onLinkLongPress` is provided (unless explicitly overridden). + * + * Android: No-op (Android doesn't have a system link preview). + * + * @default true + */ + enableLinkPreview?: CodegenTypes.WithDefault; + /** + * - iOS: Controls text selection and link previews on long press. + * - Android: Controls text selection. + * @default true + */ + selectable?: boolean; + /** + * Color of the text selection highlight. + * + * On iOS, this also affects the caret and selection handle colors + * (they share a single tint). + * + * @platform ios, android, web + */ + selectionColor?: ColorValue; + /** + * Color of the selection handles (drag anchors). + * No-op on API levels below 29. + * + * @platform android + */ + selectionHandleColor?: ColorValue; + /** + * MD4C parser flags configuration. + * Controls how the markdown parser interprets certain syntax. + */ + md4cFlags: Md4cFlagsInternal; + /** + * Specifies whether fonts should scale to respect Text Size accessibility settings. + * When false, text will not scale with the user's accessibility settings. + * @default true + */ + allowFontScaling?: CodegenTypes.WithDefault; + /** + * Specifies the largest possible scale a font can reach when allowFontScaling is enabled. + * Possible values: + * - undefined/null (default): inherit from parent or global default (no limit) + * - 0: no limit, ignore parent/global default + * - >= 1: sets the maxFontSizeMultiplier of this node to this value + * @default undefined + */ + maxFontSizeMultiplier?: CodegenTypes.Float; + /** + * When false (default), removes trailing margin from the last element to eliminate bottom spacing. + * When true, keeps the trailing margin from the last element's marginBottom style. + * @default false + */ + allowTrailingMargin?: CodegenTypes.WithDefault; + /** + * When true, newly appended content fades in during streaming updates. + * Only the tail (new characters beyond the previous content) is animated. + * @default false + */ + streamingAnimation?: CodegenTypes.WithDefault; + /** + * Controls how spoiler text is displayed before being revealed. + * - 'particles' (default): animated particle overlay. + * - 'solid': opaque rectangle covering the text. + * @default 'particles' + */ + spoilerOverlay?: CodegenTypes.WithDefault; + + /** + * Custom items to show in the text selection context menu. + */ + contextMenuItems?: ReadonlyArray>; + /** + * Fired when a custom context menu item is pressed. + * Receives the item label, the currently selected text, and the selection range. + */ + onContextMenuItemPress?: CodegenTypes.BubblingEventHandler; +} + +export default codegenNativeComponent('EnrichedMarkdown', { + interfaceOnly: true, +}); diff --git a/lib/module/EnrichedMarkdownTextNativeComponent.ts b/lib/module/EnrichedMarkdownTextNativeComponent.ts new file mode 100644 index 00000000..3a6e076d --- /dev/null +++ b/lib/module/EnrichedMarkdownTextNativeComponent.ts @@ -0,0 +1,391 @@ +import { + codegenNativeComponent, + type ViewProps, + type CodegenTypes, + type ColorValue, +} from 'react-native'; + +// All block styles extend this interface +interface BaseBlockStyleInternal { + fontSize: CodegenTypes.Float; + fontFamily: string; + fontWeight: string; + color: ColorValue; + marginTop: CodegenTypes.Float; + marginBottom: CodegenTypes.Float; + lineHeight: CodegenTypes.Float; +} + +interface ParagraphStyleInternal extends BaseBlockStyleInternal { + textAlign: string; +} + +interface HeadingStyleInternal extends BaseBlockStyleInternal { + textAlign: string; +} + +interface BlockquoteStyleInternal extends BaseBlockStyleInternal { + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + gapWidth: CodegenTypes.Float; + backgroundColor: ColorValue; +} + +interface ListStyleInternal extends BaseBlockStyleInternal { + bulletColor: ColorValue; + bulletSize: CodegenTypes.Float; + markerColor: ColorValue; + markerFontWeight: string; + gapWidth: CodegenTypes.Float; + marginLeft: CodegenTypes.Float; +} + +interface CodeBlockStyleInternal extends BaseBlockStyleInternal { + backgroundColor: ColorValue; + borderColor: ColorValue; + borderRadius: CodegenTypes.Float; + borderWidth: CodegenTypes.Float; + padding: CodegenTypes.Float; +} + +interface LinkStyleInternal { + fontFamily: string; + color: ColorValue; + underline: boolean; +} + +interface StrongStyleInternal { + fontFamily: string; + fontWeight: string; + color?: ColorValue; +} + +interface EmphasisStyleInternal { + fontFamily: string; + fontStyle: string; + color?: ColorValue; +} + +interface StrikethroughStyleInternal { + color: ColorValue; +} + +interface UnderlineStyleInternal { + color: ColorValue; +} + +interface CodeStyleInternal { + fontFamily: string; + fontSize: CodegenTypes.Float; + color: ColorValue; + backgroundColor: ColorValue; + borderColor: ColorValue; +} + +interface ImageStyleInternal { + height: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; + marginTop: CodegenTypes.Float; + marginBottom: CodegenTypes.Float; +} + +interface InlineImageStyleInternal { + size: CodegenTypes.Float; +} + +interface ThematicBreakStyleInternal { + color: ColorValue; + height: CodegenTypes.Float; + marginTop: CodegenTypes.Float; + marginBottom: CodegenTypes.Float; +} + +interface TableStyleInternal extends BaseBlockStyleInternal { + headerFontFamily: string; + headerBackgroundColor: ColorValue; + headerTextColor: ColorValue; + rowEvenBackgroundColor: ColorValue; + rowOddBackgroundColor: ColorValue; + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; + cellPaddingHorizontal: CodegenTypes.Float; + cellPaddingVertical: CodegenTypes.Float; +} + +interface TaskListStyleInternal { + checkedColor: ColorValue; + borderColor: ColorValue; + checkboxSize: CodegenTypes.Float; + checkboxBorderRadius: CodegenTypes.Float; + checkmarkColor: ColorValue; + checkedTextColor: ColorValue; + checkedStrikethrough: boolean; +} + +interface MathStyleInternal { + fontSize: CodegenTypes.Float; + color: ColorValue; + backgroundColor: ColorValue; + padding: CodegenTypes.Float; + marginTop: CodegenTypes.Float; + marginBottom: CodegenTypes.Float; + textAlign: string; +} + +interface InlineMathStyleInternal { + color: ColorValue; +} + +interface SpoilerParticlesStyleInternal { + density: CodegenTypes.Float; + speed: CodegenTypes.Float; +} + +interface SpoilerSolidStyleInternal { + borderRadius: CodegenTypes.Float; +} + +interface SpoilerStyleInternal { + color: ColorValue; + particles: SpoilerParticlesStyleInternal; + solid: SpoilerSolidStyleInternal; +} + +interface MentionStyleInternal { + color: ColorValue; + backgroundColor: ColorValue; + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; + paddingHorizontal: CodegenTypes.Float; + paddingVertical: CodegenTypes.Float; + fontFamily: string; + fontWeight: string; + fontSize: CodegenTypes.Float; + pressedOpacity: CodegenTypes.Float; +} + +interface CitationStyleInternal { + color: ColorValue; + fontSizeMultiplier: CodegenTypes.Float; + baselineOffsetPx: CodegenTypes.Float; + fontWeight: string; + underline: boolean; + backgroundColor: ColorValue; + paddingHorizontal: CodegenTypes.Float; + paddingVertical: CodegenTypes.Float; + borderColor: ColorValue; + borderWidth: CodegenTypes.Float; + borderRadius: CodegenTypes.Float; +} + +export interface MarkdownStyleInternal { + paragraph: ParagraphStyleInternal; + h1: HeadingStyleInternal; + h2: HeadingStyleInternal; + h3: HeadingStyleInternal; + h4: HeadingStyleInternal; + h5: HeadingStyleInternal; + h6: HeadingStyleInternal; + blockquote: BlockquoteStyleInternal; + list: ListStyleInternal; + codeBlock: CodeBlockStyleInternal; + link: LinkStyleInternal; + strong: StrongStyleInternal; + em: EmphasisStyleInternal; + strikethrough: StrikethroughStyleInternal; + underline: UnderlineStyleInternal; + code: CodeStyleInternal; + image: ImageStyleInternal; + inlineImage: InlineImageStyleInternal; + thematicBreak: ThematicBreakStyleInternal; + table: TableStyleInternal; + taskList: TaskListStyleInternal; + math: MathStyleInternal; + inlineMath: InlineMathStyleInternal; + spoiler: SpoilerStyleInternal; + mention: MentionStyleInternal; + citation: CitationStyleInternal; +} + +export interface LinkPressEvent { + url: string; +} + +export interface LinkLongPressEvent { + url: string; +} + +export interface MentionPressEvent { + url: string; + text: string; +} + +export interface CitationPressEvent { + url: string; + text: string; +} + +export interface TaskListItemPressEvent { + index: CodegenTypes.Int32; + checked: boolean; + text: string; +} + +export interface ContextMenuItemConfig { + text: string; + icon?: string; +} + +export interface OnContextMenuItemPressEvent { + itemText: string; + selectedText: string; + selectionStart: CodegenTypes.Int32; + selectionEnd: CodegenTypes.Int32; +} + +/** + * MD4C parser flags configuration. + * Controls how the markdown parser interprets certain syntax. + */ +export interface Md4cFlagsInternal { + /** + * Enable underline syntax support (__text__). + * When enabled, underscores are treated as underline markers. + * When disabled, underscores are treated as emphasis markers (same as asterisks). + * @default false + */ + underline: boolean; + /** + * Enable LaTeX math span parsing ($..$ and $$..$$). + * When disabled, dollar signs are treated as plain text. + * @default true + */ + latexMath: boolean; +} + +export interface NativeProps extends ViewProps { + /** + * Markdown content to render. + */ + markdown: string; + /** + * Internal style configuration for markdown elements. + * Always provided with complete defaults via normalizeMarkdownStyle. + * Block styles (paragraph, headings) contain fontSize, fontFamily, fontWeight, and color. + */ + markdownStyle: MarkdownStyleInternal; + /** + * Callback fired when a link is pressed. + * Receives the URL that was tapped. + */ + onLinkPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when a link is long pressed. + * Receives the URL that was long pressed. + * - iOS: When provided, overrides the system link preview behavior. + * - Android: Handles long press gestures on links. + */ + onLinkLongPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when a task list checkbox is tapped. + * Receives the 0-based task index, current checked state, and the item's plain text. + */ + onTaskListItemPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when an inline mention pill (`mention://`) is pressed. + */ + onMentionPress?: CodegenTypes.BubblingEventHandler; + /** + * Callback fired when an inline citation (`citation://`) is pressed. + */ + onCitationPress?: CodegenTypes.BubblingEventHandler; + /** + * Controls whether the system link preview is shown on long press (iOS only). + * + * When `true` (default), long-pressing a link shows the native iOS link preview. + * When `false`, the system preview is suppressed. + * + * Automatically set to `false` when `onLinkLongPress` is provided (unless explicitly overridden). + * + * Android: No-op (Android doesn't have a system link preview). + * + * @default true + */ + enableLinkPreview?: CodegenTypes.WithDefault; + /** + * - iOS: Controls text selection and link previews on long press. + * - Android: Controls text selection. + * @default true + */ + selectable?: boolean; + /** + * Color of the text selection highlight. + * + * On iOS, this also affects the caret and selection handle colors + * (they share a single tint). + * + * @platform ios, android, web + */ + selectionColor?: ColorValue; + /** + * Color of the selection handles (drag anchors). + * No-op on API levels below 29. + * + * @platform android + */ + selectionHandleColor?: ColorValue; + /** + * MD4C parser flags configuration. + * Controls how the markdown parser interprets certain syntax. + */ + md4cFlags: Md4cFlagsInternal; + /** + * Specifies whether fonts should scale to respect Text Size accessibility settings. + * When false, text will not scale with the user's accessibility settings. + * @default true + */ + allowFontScaling?: CodegenTypes.WithDefault; + /** + * Specifies the largest possible scale a font can reach when allowFontScaling is enabled. + * Possible values: + * - undefined/null (default): inherit from parent or global default (no limit) + * - 0: no limit, ignore parent/global default + * - >= 1: sets the maxFontSizeMultiplier of this node to this value + * @default undefined + */ + maxFontSizeMultiplier?: CodegenTypes.Float; + /** + * When false (default), removes trailing margin from the last element to eliminate bottom spacing. + * When true, keeps the trailing margin from the last element's marginBottom style. + * @default false + */ + allowTrailingMargin?: CodegenTypes.WithDefault; + /** + * When true, newly appended content fades in during streaming updates. + * Only the tail (new characters beyond the previous content) is animated. + * @default false + */ + streamingAnimation?: CodegenTypes.WithDefault; + /** + * Controls how spoiler text is displayed before being revealed. + * - 'particles' (default): animated particle overlay. + * - 'solid': opaque rectangle covering the text. + * @default 'particles' + */ + spoilerOverlay?: CodegenTypes.WithDefault; + /** + * Custom items to show in the text selection context menu. + */ + contextMenuItems?: ReadonlyArray>; + /** + * Fired when a custom context menu item is pressed. + * Receives the item label, the currently selected text, and the selection range. + */ + onContextMenuItemPress?: CodegenTypes.BubblingEventHandler; +} + +export default codegenNativeComponent('EnrichedMarkdownText', { + interfaceOnly: true, +}); diff --git a/lib/module/index.js b/lib/module/index.js new file mode 100644 index 00000000..1ab6a9e3 --- /dev/null +++ b/lib/module/index.js @@ -0,0 +1,5 @@ +"use strict"; + +export { default as EnrichedMarkdownText } from "./native/EnrichedMarkdownText.js"; +export { EnrichedMarkdownInput } from "./EnrichedMarkdownInput.js"; +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/lib/module/index.js.map b/lib/module/index.js.map new file mode 100644 index 00000000..c405aac8 --- /dev/null +++ b/lib/module/index.js.map @@ -0,0 +1 @@ +{"version":3,"names":["default","EnrichedMarkdownText","EnrichedMarkdownInput"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,OAAO,IAAIC,oBAAoB,QAAQ,kCAA+B;AAe/E,SAASC,qBAAqB,QAAQ,4BAAyB","ignoreList":[]} diff --git a/lib/module/index.web.js b/lib/module/index.web.js new file mode 100644 index 00000000..d8308e62 --- /dev/null +++ b/lib/module/index.web.js @@ -0,0 +1,4 @@ +"use strict"; + +export { EnrichedMarkdownText, default } from "./web/EnrichedMarkdownText.js"; +//# sourceMappingURL=index.web.js.map \ No newline at end of file diff --git a/lib/module/index.web.js.map b/lib/module/index.web.js.map new file mode 100644 index 00000000..2691825e --- /dev/null +++ b/lib/module/index.web.js.map @@ -0,0 +1 @@ +{"version":3,"names":["EnrichedMarkdownText","default"],"sourceRoot":"../../src","sources":["index.web.tsx"],"mappings":";;AAAA,SAASA,oBAAoB,EAAEC,OAAO,QAAQ,+BAA4B","ignoreList":[]} diff --git a/lib/module/native/EnrichedMarkdownText.js b/lib/module/native/EnrichedMarkdownText.js new file mode 100644 index 00000000..74cae237 --- /dev/null +++ b/lib/module/native/EnrichedMarkdownText.js @@ -0,0 +1,158 @@ +"use strict"; + +import { useMemo, useCallback, useRef, useEffect } from 'react'; +import EnrichedMarkdownTextNativeComponent from '../EnrichedMarkdownTextNativeComponent'; +import EnrichedMarkdownNativeComponent from '../EnrichedMarkdownNativeComponent'; +import { normalizeMarkdownStyle } from '../normalizeMarkdownStyle'; +import { jsx as _jsx } from "react/jsx-runtime"; +const defaultMd4cFlags = { + underline: false, + latexMath: true +}; +export const EnrichedMarkdownText = ({ + markdown, + markdownStyle = {}, + containerStyle, + onLinkPress, + onLinkLongPress, + onTaskListItemPress, + onMentionPress, + onCitationPress, + enableLinkPreview, + selectable = true, + md4cFlags = defaultMd4cFlags, + allowFontScaling = true, + maxFontSizeMultiplier, + allowTrailingMargin = false, + flavor = 'commonmark', + streamingAnimation = false, + spoilerOverlay = 'particles', + contextMenuItems, + selectionColor, + selectionHandleColor, + ...rest +}) => { + const normalizedStyleRef = useRef(null); + const normalized = normalizeMarkdownStyle(markdownStyle); + // normalizeMarkdownStyle returns cached objects for structurally equal inputs, + // so this referential check is sufficient to preserve a stable prop reference. + if (normalizedStyleRef.current !== normalized) { + normalizedStyleRef.current = normalized; + } + const normalizedStyle = normalizedStyleRef.current; + const normalizedMd4cFlags = useMemo(() => ({ + underline: md4cFlags.underline ?? false, + latexMath: md4cFlags.latexMath ?? true + }), [md4cFlags]); + const contextMenuCallbacksRef = useRef(new Map()); + useEffect(() => { + const callbacksMap = new Map(); + if (contextMenuItems) { + for (const item of contextMenuItems) { + callbacksMap.set(item.text, item.onPress); + } + } + contextMenuCallbacksRef.current = callbacksMap; + }, [contextMenuItems]); + const nativeContextMenuItems = useMemo(() => contextMenuItems?.filter(item => item.visible !== false).map(item => ({ + text: item.text, + icon: item.icon + })), [contextMenuItems]); + const handleContextMenuItemPress = useCallback(e => { + const { + itemText, + selectedText, + selectionStart, + selectionEnd + } = e.nativeEvent; + const callback = contextMenuCallbacksRef.current.get(itemText); + callback?.({ + text: selectedText, + selection: { + start: selectionStart, + end: selectionEnd + } + }); + }, []); + const handleLinkPress = useCallback(e => { + const { + url + } = e.nativeEvent; + onLinkPress?.({ + url + }); + }, [onLinkPress]); + const handleLinkLongPress = useCallback(e => { + const { + url + } = e.nativeEvent; + onLinkLongPress?.({ + url + }); + }, [onLinkLongPress]); + const handleTaskListItemPress = useCallback(e => { + const { + index, + checked, + text + } = e.nativeEvent; + onTaskListItemPress?.({ + index, + checked, + text + }); + }, [onTaskListItemPress]); + const handleMentionPress = useCallback(e => { + const { + url, + text + } = e.nativeEvent; + onMentionPress?.({ + url, + text + }); + }, [onMentionPress]); + const handleCitationPress = useCallback(e => { + const { + url, + text + } = e.nativeEvent; + onCitationPress?.({ + url, + text + }); + }, [onCitationPress]); + const sharedProps = { + markdown, + markdownStyle: normalizedStyle, + onLinkPress: handleLinkPress, + onLinkLongPress: handleLinkLongPress, + onTaskListItemPress: handleTaskListItemPress, + onMentionPress: onMentionPress ? handleMentionPress : undefined, + onCitationPress: onCitationPress ? handleCitationPress : undefined, + enableLinkPreview: onLinkLongPress == null && (enableLinkPreview ?? true), + selectable, + md4cFlags: normalizedMd4cFlags, + allowFontScaling, + maxFontSizeMultiplier, + allowTrailingMargin, + streamingAnimation, + spoilerOverlay, + style: containerStyle, + contextMenuItems: nativeContextMenuItems, + onContextMenuItemPress: handleContextMenuItemPress, + selectionColor, + selectionHandleColor, + ...rest + }; + if (flavor === 'github') { + return /*#__PURE__*/_jsx(EnrichedMarkdownNativeComponent, { + ...sharedProps + }); + } + return /*#__PURE__*/_jsx(EnrichedMarkdownTextNativeComponent, { + ...sharedProps + }); +}; +export default EnrichedMarkdownText; +//# sourceMappingURL=EnrichedMarkdownText.js.map \ No newline at end of file diff --git a/lib/module/native/EnrichedMarkdownText.js.map b/lib/module/native/EnrichedMarkdownText.js.map new file mode 100644 index 00000000..65993fa0 --- /dev/null +++ b/lib/module/native/EnrichedMarkdownText.js.map @@ -0,0 +1 @@ +{"version":3,"names":["useMemo","useCallback","useRef","useEffect","EnrichedMarkdownTextNativeComponent","EnrichedMarkdownNativeComponent","normalizeMarkdownStyle","jsx","_jsx","defaultMd4cFlags","underline","latexMath","EnrichedMarkdownText","markdown","markdownStyle","containerStyle","onLinkPress","onLinkLongPress","onTaskListItemPress","onMentionPress","onCitationPress","enableLinkPreview","selectable","md4cFlags","allowFontScaling","maxFontSizeMultiplier","allowTrailingMargin","flavor","streamingAnimation","spoilerOverlay","contextMenuItems","selectionColor","selectionHandleColor","rest","normalizedStyleRef","normalized","current","normalizedStyle","normalizedMd4cFlags","contextMenuCallbacksRef","Map","callbacksMap","item","set","text","onPress","nativeContextMenuItems","filter","visible","map","icon","handleContextMenuItemPress","e","itemText","selectedText","selectionStart","selectionEnd","nativeEvent","callback","get","selection","start","end","handleLinkPress","url","handleLinkLongPress","handleTaskListItemPress","index","checked","handleMentionPress","handleCitationPress","sharedProps","undefined","style","onContextMenuItemPress"],"sourceRoot":"../../../src","sources":["native/EnrichedMarkdownText.tsx"],"mappings":";;AAAA,SAASA,OAAO,EAAEC,WAAW,EAAEC,MAAM,EAAEC,SAAS,QAAQ,OAAO;AAC/D,OAAOC,mCAAmC,MAAM,wCAAwC;AAExF,OAAOC,+BAA+B,MAAM,oCAAoC;AAChF,SAASC,sBAAsB,QAAQ,2BAA2B;AAAC,SAAAC,GAAA,IAAAC,IAAA;AA0BnE,MAAMC,gBAA2B,GAAG;EAClCC,SAAS,EAAE,KAAK;EAChBC,SAAS,EAAE;AACb,CAAC;AAED,OAAO,MAAMC,oBAAoB,GAAGA,CAAC;EACnCC,QAAQ;EACRC,aAAa,GAAG,CAAC,CAAC;EAClBC,cAAc;EACdC,WAAW;EACXC,eAAe;EACfC,mBAAmB;EACnBC,cAAc;EACdC,eAAe;EACfC,iBAAiB;EACjBC,UAAU,GAAG,IAAI;EACjBC,SAAS,GAAGd,gBAAgB;EAC5Be,gBAAgB,GAAG,IAAI;EACvBC,qBAAqB;EACrBC,mBAAmB,GAAG,KAAK;EAC3BC,MAAM,GAAG,YAAY;EACrBC,kBAAkB,GAAG,KAAK;EAC1BC,cAAc,GAAG,WAAW;EAC5BC,gBAAgB;EAChBC,cAAc;EACdC,oBAAoB;EACpB,GAAGC;AACsB,CAAC,KAAK;EAC/B,MAAMC,kBAAkB,GAAGhC,MAAM,CAA+B,IAAI,CAAC;EACrE,MAAMiC,UAAU,GAAG7B,sBAAsB,CAACQ,aAAa,CAAC;EACxD;EACA;EACA,IAAIoB,kBAAkB,CAACE,OAAO,KAAKD,UAAU,EAAE;IAC7CD,kBAAkB,CAACE,OAAO,GAAGD,UAAU;EACzC;EACA,MAAME,eAAe,GAAGH,kBAAkB,CAACE,OAAQ;EAEnD,MAAME,mBAAmB,GAAGtC,OAAO,CACjC,OAAO;IACLU,SAAS,EAAEa,SAAS,CAACb,SAAS,IAAI,KAAK;IACvCC,SAAS,EAAEY,SAAS,CAACZ,SAAS,IAAI;EACpC,CAAC,CAAC,EACF,CAACY,SAAS,CACZ,CAAC;EAED,MAAMgB,uBAAuB,GAAGrC,MAAM,CAEpC,IAAIsC,GAAG,CAAC,CAAC,CAAC;EAEZrC,SAAS,CAAC,MAAM;IACd,MAAMsC,YAAY,GAAG,IAAID,GAAG,CAAqC,CAAC;IAClE,IAAIV,gBAAgB,EAAE;MACpB,KAAK,MAAMY,IAAI,IAAIZ,gBAAgB,EAAE;QACnCW,YAAY,CAACE,GAAG,CAACD,IAAI,CAACE,IAAI,EAAEF,IAAI,CAACG,OAAO,CAAC;MAC3C;IACF;IACAN,uBAAuB,CAACH,OAAO,GAAGK,YAAY;EAChD,CAAC,EAAE,CAACX,gBAAgB,CAAC,CAAC;EAEtB,MAAMgB,sBAAsB,GAAG9C,OAAO,CACpC,MACE8B,gBAAgB,EACZiB,MAAM,CAAEL,IAAI,IAAKA,IAAI,CAACM,OAAO,KAAK,KAAK,CAAC,CACzCC,GAAG,CAAEP,IAAI,KAAM;IAAEE,IAAI,EAAEF,IAAI,CAACE,IAAI;IAAEM,IAAI,EAAER,IAAI,CAACQ;EAAK,CAAC,CAAC,CAAC,EAC1D,CAACpB,gBAAgB,CACnB,CAAC;EAED,MAAMqB,0BAA0B,GAAGlD,WAAW,CAC3CmD,CAAoD,IAAK;IACxD,MAAM;MAAEC,QAAQ;MAAEC,YAAY;MAAEC,cAAc;MAAEC;IAAa,CAAC,GAC5DJ,CAAC,CAACK,WAAW;IACf,MAAMC,QAAQ,GAAGnB,uBAAuB,CAACH,OAAO,CAACuB,GAAG,CAACN,QAAQ,CAAC;IAC9DK,QAAQ,GAAG;MACTd,IAAI,EAAEU,YAAY;MAClBM,SAAS,EAAE;QAAEC,KAAK,EAAEN,cAAc;QAAEO,GAAG,EAAEN;MAAa;IACxD,CAAC,CAAC;EACJ,CAAC,EACD,EACF,CAAC;EAED,MAAMO,eAAe,GAAG9D,WAAW,CAChCmD,CAAuC,IAAK;IAC3C,MAAM;MAAEY;IAAI,CAAC,GAAGZ,CAAC,CAACK,WAAW;IAC7BzC,WAAW,GAAG;MAAEgD;IAAI,CAAC,CAAC;EACxB,CAAC,EACD,CAAChD,WAAW,CACd,CAAC;EAED,MAAMiD,mBAAmB,GAAGhE,WAAW,CACpCmD,CAA2C,IAAK;IAC/C,MAAM;MAAEY;IAAI,CAAC,GAAGZ,CAAC,CAACK,WAAW;IAC7BxC,eAAe,GAAG;MAAE+C;IAAI,CAAC,CAAC;EAC5B,CAAC,EACD,CAAC/C,eAAe,CAClB,CAAC;EAED,MAAMiD,uBAAuB,GAAGjE,WAAW,CACxCmD,CAA+C,IAAK;IACnD,MAAM;MAAEe,KAAK;MAAEC,OAAO;MAAExB;IAAK,CAAC,GAAGQ,CAAC,CAACK,WAAW;IAC9CvC,mBAAmB,GAAG;MAAEiD,KAAK;MAAEC,OAAO;MAAExB;IAAK,CAAC,CAAC;EACjD,CAAC,EACD,CAAC1B,mBAAmB,CACtB,CAAC;EAED,MAAMmD,kBAAkB,GAAGpE,WAAW,CACnCmD,CAA0C,IAAK;IAC9C,MAAM;MAAEY,GAAG;MAAEpB;IAAK,CAAC,GAAGQ,CAAC,CAACK,WAAW;IACnCtC,cAAc,GAAG;MAAE6C,GAAG;MAAEpB;IAAK,CAAC,CAAC;EACjC,CAAC,EACD,CAACzB,cAAc,CACjB,CAAC;EAED,MAAMmD,mBAAmB,GAAGrE,WAAW,CACpCmD,CAA2C,IAAK;IAC/C,MAAM;MAAEY,GAAG;MAAEpB;IAAK,CAAC,GAAGQ,CAAC,CAACK,WAAW;IACnCrC,eAAe,GAAG;MAAE4C,GAAG;MAAEpB;IAAK,CAAC,CAAC;EAClC,CAAC,EACD,CAACxB,eAAe,CAClB,CAAC;EAED,MAAMmD,WAAW,GAAG;IAClB1D,QAAQ;IACRC,aAAa,EAAEuB,eAAe;IAC9BrB,WAAW,EAAE+C,eAAe;IAC5B9C,eAAe,EAAEgD,mBAAmB;IACpC/C,mBAAmB,EAAEgD,uBAAuB;IAC5C/C,cAAc,EAAEA,cAAc,GAAGkD,kBAAkB,GAAGG,SAAS;IAC/DpD,eAAe,EAAEA,eAAe,GAAGkD,mBAAmB,GAAGE,SAAS;IAClEnD,iBAAiB,EAAEJ,eAAe,IAAI,IAAI,KAAKI,iBAAiB,IAAI,IAAI,CAAC;IACzEC,UAAU;IACVC,SAAS,EAAEe,mBAAmB;IAC9Bd,gBAAgB;IAChBC,qBAAqB;IACrBC,mBAAmB;IACnBE,kBAAkB;IAClBC,cAAc;IACd4C,KAAK,EAAE1D,cAAc;IACrBe,gBAAgB,EAAEgB,sBAAsB;IACxC4B,sBAAsB,EAAEvB,0BAA0B;IAClDpB,cAAc;IACdC,oBAAoB;IACpB,GAAGC;EACL,CAAC;EAED,IAAIN,MAAM,KAAK,QAAQ,EAAE;IACvB,oBAAOnB,IAAA,CAACH,+BAA+B;MAAA,GAAKkE;IAAW,CAAG,CAAC;EAC7D;EAEA,oBAAO/D,IAAA,CAACJ,mCAAmC;IAAA,GAAKmE;EAAW,CAAG,CAAC;AACjE,CAAC;AAED,eAAe3D,oBAAoB","ignoreList":[]} diff --git a/lib/module/normalizeMarkdownInputStyle.js b/lib/module/normalizeMarkdownInputStyle.js new file mode 100644 index 00000000..3c9bcc7b --- /dev/null +++ b/lib/module/normalizeMarkdownInputStyle.js @@ -0,0 +1,53 @@ +"use strict"; + +import { processColor } from 'react-native'; +import { normalizeColor } from "./styleUtils.js"; +const DEFAULT_LINK_COLOR = '#2563EB'; +const DEFAULT_SPOILER_COLOR = '#374151'; +const DEFAULT_SPOILER_BG_COLOR = '#E5E7EB'; +const defaultInternal = Object.freeze({ + strong: { + color: undefined + }, + em: { + color: undefined + }, + link: { + color: processColor(DEFAULT_LINK_COLOR), + underline: true + }, + spoiler: { + color: processColor(DEFAULT_SPOILER_COLOR), + backgroundColor: processColor(DEFAULT_SPOILER_BG_COLOR) + } +}); +let cachedInput; +let cachedResult; +export const normalizeMarkdownInputStyle = style => { + if (!style || Object.keys(style).length === 0) { + return defaultInternal; + } + if (style === cachedInput && cachedResult) { + return cachedResult; + } + const result = { + strong: { + color: normalizeColor(style.strong?.color) + }, + em: { + color: normalizeColor(style.em?.color) + }, + link: { + color: normalizeColor(style.link?.color) ?? defaultInternal.link.color, + underline: style.link?.underline ?? defaultInternal.link.underline + }, + spoiler: { + color: normalizeColor(style.spoiler?.color) ?? defaultInternal.spoiler.color, + backgroundColor: normalizeColor(style.spoiler?.backgroundColor) ?? defaultInternal.spoiler.backgroundColor + } + }; + cachedInput = style; + cachedResult = result; + return result; +}; +//# sourceMappingURL=normalizeMarkdownInputStyle.js.map \ No newline at end of file diff --git a/lib/module/normalizeMarkdownInputStyle.js.map b/lib/module/normalizeMarkdownInputStyle.js.map new file mode 100644 index 00000000..c21d33ee --- /dev/null +++ b/lib/module/normalizeMarkdownInputStyle.js.map @@ -0,0 +1 @@ +{"version":3,"names":["processColor","normalizeColor","DEFAULT_LINK_COLOR","DEFAULT_SPOILER_COLOR","DEFAULT_SPOILER_BG_COLOR","defaultInternal","Object","freeze","strong","color","undefined","em","link","underline","spoiler","backgroundColor","cachedInput","cachedResult","normalizeMarkdownInputStyle","style","keys","length","result"],"sourceRoot":"../../src","sources":["normalizeMarkdownInputStyle.ts"],"mappings":";;AAAA,SAASA,YAAY,QAAyB,cAAc;AAE5D,SAASC,cAAc,QAAQ,iBAAc;AAmB7C,MAAMC,kBAAkB,GAAG,SAAS;AACpC,MAAMC,qBAAqB,GAAG,SAAS;AACvC,MAAMC,wBAAwB,GAAG,SAAS;AAE1C,MAAMC,eAA2C,GAAGC,MAAM,CAACC,MAAM,CAAC;EAChEC,MAAM,EAAE;IACNC,KAAK,EAAEC;EACT,CAAC;EACDC,EAAE,EAAE;IACFF,KAAK,EAAEC;EACT,CAAC;EACDE,IAAI,EAAE;IACJH,KAAK,EAAET,YAAY,CAACE,kBAAkB,CAAE;IACxCW,SAAS,EAAE;EACb,CAAC;EACDC,OAAO,EAAE;IACPL,KAAK,EAAET,YAAY,CAACG,qBAAqB,CAAE;IAC3CY,eAAe,EAAEf,YAAY,CAACI,wBAAwB;EACxD;AACF,CAAC,CAAC;AAEF,IAAIY,WAA2C;AAC/C,IAAIC,YAAoD;AAExD,OAAO,MAAMC,2BAA2B,GACtCC,KAA0B,IACK;EAC/B,IAAI,CAACA,KAAK,IAAIb,MAAM,CAACc,IAAI,CAACD,KAAK,CAAC,CAACE,MAAM,KAAK,CAAC,EAAE;IAC7C,OAAOhB,eAAe;EACxB;EAEA,IAAIc,KAAK,KAAKH,WAAW,IAAIC,YAAY,EAAE;IACzC,OAAOA,YAAY;EACrB;EAEA,MAAMK,MAAkC,GAAG;IACzCd,MAAM,EAAE;MACNC,KAAK,EAAER,cAAc,CAACkB,KAAK,CAACX,MAAM,EAAEC,KAAK;IAC3C,CAAC;IACDE,EAAE,EAAE;MACFF,KAAK,EAAER,cAAc,CAACkB,KAAK,CAACR,EAAE,EAAEF,KAAK;IACvC,CAAC;IACDG,IAAI,EAAE;MACJH,KAAK,EAAER,cAAc,CAACkB,KAAK,CAACP,IAAI,EAAEH,KAAK,CAAC,IAAIJ,eAAe,CAACO,IAAI,CAACH,KAAK;MACtEI,SAAS,EAAEM,KAAK,CAACP,IAAI,EAAEC,SAAS,IAAIR,eAAe,CAACO,IAAI,CAACC;IAC3D,CAAC;IACDC,OAAO,EAAE;MACPL,KAAK,EACHR,cAAc,CAACkB,KAAK,CAACL,OAAO,EAAEL,KAAK,CAAC,IAAIJ,eAAe,CAACS,OAAO,CAACL,KAAK;MACvEM,eAAe,EACbd,cAAc,CAACkB,KAAK,CAACL,OAAO,EAAEC,eAAe,CAAC,IAC9CV,eAAe,CAACS,OAAO,CAACC;IAC5B;EACF,CAAC;EAEDC,WAAW,GAAGG,KAAK;EACnBF,YAAY,GAAGK,MAAM;EACrB,OAAOA,MAAM;AACf,CAAC","ignoreList":[]} diff --git a/lib/module/normalizeMarkdownStyle.js b/lib/module/normalizeMarkdownStyle.js new file mode 100644 index 00000000..5a67813c --- /dev/null +++ b/lib/module/normalizeMarkdownStyle.js @@ -0,0 +1,324 @@ +"use strict"; + +import { Platform } from 'react-native'; +import { isStyleEqual, normalizeColor, mergeSubStyle } from "./styleUtils.js"; +const getSystemFont = () => Platform.select({ + ios: 'System', + android: 'sans-serif', + web: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif', + default: 'sans-serif' +}); +const getMonospaceFont = () => Platform.select({ + ios: 'Menlo', + android: 'monospace', + web: 'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace', + default: 'monospace' +}); +const defaultTextColor = normalizeColor('#1F2937'); +const defaultHeadingColor = normalizeColor('#111827'); + +// Explicit type annotation needed: Object.freeze breaks contextual typing, so +// TypeScript widens literal 'auto' to `string` instead of `BlockTextAlign`. +const baseHeader = { + fontFamily: getSystemFont(), + fontWeight: '', + marginTop: 0, + marginBottom: 8, + textAlign: 'auto' +}; +const DEFAULT_NORMALIZED_STYLE = Object.freeze({ + paragraph: { + fontSize: 16, + fontFamily: getSystemFont(), + fontWeight: '', + color: defaultTextColor, + lineHeight: Platform.select({ + ios: 24, + android: 26, + default: 26 + }), + marginTop: 0, + marginBottom: 16, + textAlign: 'auto' + }, + h1: { + ...baseHeader, + fontSize: 30, + color: defaultHeadingColor, + lineHeight: Platform.select({ + ios: 36, + android: 38, + default: 38 + }) + }, + h2: { + ...baseHeader, + fontSize: 24, + color: defaultHeadingColor, + lineHeight: Platform.select({ + ios: 30, + android: 32, + default: 32 + }) + }, + h3: { + ...baseHeader, + fontSize: 20, + color: defaultHeadingColor, + lineHeight: Platform.select({ + ios: 26, + android: 28, + default: 28 + }) + }, + h4: { + ...baseHeader, + fontSize: 18, + color: defaultHeadingColor, + lineHeight: Platform.select({ + ios: 24, + android: 26, + default: 26 + }) + }, + h5: { + ...baseHeader, + fontSize: 16, + color: normalizeColor('#374151'), + lineHeight: Platform.select({ + ios: 22, + android: 24, + default: 24 + }) + }, + h6: { + ...baseHeader, + fontSize: 14, + color: normalizeColor('#4B5563'), + lineHeight: Platform.select({ + ios: 20, + android: 22, + default: 22 + }) + }, + blockquote: { + fontSize: 16, + fontFamily: getSystemFont(), + fontWeight: '', + color: normalizeColor('#4B5563'), + lineHeight: Platform.select({ + ios: 24, + android: 26, + default: 26 + }), + marginTop: 0, + marginBottom: 16, + borderColor: normalizeColor('#D1D5DB'), + borderWidth: 3, + gapWidth: 16, + backgroundColor: normalizeColor('#F9FAFB') + }, + list: { + fontSize: 16, + fontFamily: getSystemFont(), + fontWeight: '', + color: defaultTextColor, + lineHeight: Platform.select({ + ios: 22, + android: 26, + default: 26 + }), + marginTop: 0, + marginBottom: 16, + bulletColor: normalizeColor('#6B7280'), + bulletSize: 6, + markerColor: normalizeColor('#6B7280'), + markerFontWeight: '500', + gapWidth: 12, + marginLeft: 24 + }, + codeBlock: { + fontSize: 14, + fontFamily: getMonospaceFont(), + fontWeight: '', + color: normalizeColor('#F3F4F6'), + lineHeight: Platform.select({ + ios: 20, + android: 22, + default: 22 + }), + marginTop: 0, + marginBottom: 16, + backgroundColor: normalizeColor('#1F2937'), + borderColor: normalizeColor('#374151'), + borderRadius: 8, + borderWidth: 1, + padding: 16 + }, + link: { + fontFamily: '', + color: normalizeColor('#2563EB'), + underline: true + }, + strong: { + fontFamily: '', + fontWeight: 'bold', + color: undefined + }, + em: { + fontFamily: '', + fontStyle: 'italic', + color: undefined + }, + strikethrough: { + color: normalizeColor('#9CA3AF') + }, + underline: { + color: defaultTextColor + }, + code: { + // Native uses '' (inherit); web needs an explicit monospace stack so inline + // code doesn't fall back to the browser's default proportional font. + fontFamily: Platform.select({ + web: getMonospaceFont(), + default: '' + }), + fontSize: 0, + color: normalizeColor('#E01E5A'), + backgroundColor: normalizeColor('#FDF2F4'), + borderColor: normalizeColor('#F8D7DA') + }, + image: { + height: 200, + borderRadius: 8, + marginTop: 0, + marginBottom: 16 + }, + inlineImage: { + size: 20 + }, + thematicBreak: { + color: normalizeColor('#E5E7EB'), + height: 1, + marginTop: 24, + marginBottom: 24 + }, + table: { + fontSize: 14, + fontFamily: getSystemFont(), + fontWeight: '', + color: defaultTextColor, + marginTop: 0, + marginBottom: 16, + lineHeight: Platform.select({ + ios: 20, + android: 22, + default: 22 + }), + headerFontFamily: '', + headerBackgroundColor: normalizeColor('#F3F4F6'), + headerTextColor: normalizeColor('#111827'), + rowEvenBackgroundColor: normalizeColor('#FFFFFF'), + rowOddBackgroundColor: normalizeColor('#F9FAFB'), + borderColor: normalizeColor('#E5E7EB'), + borderWidth: 1, + borderRadius: 6, + cellPaddingHorizontal: 12, + cellPaddingVertical: 8 + }, + math: { + fontSize: 20, + color: defaultTextColor, + backgroundColor: normalizeColor('#F3F4F6'), + padding: 12, + marginTop: 0, + marginBottom: 16, + textAlign: 'center' + }, + inlineMath: { + color: defaultTextColor + }, + taskList: { + checkedColor: Platform.select({ + ios: normalizeColor('#007AFF'), + android: normalizeColor('#2196F3'), + default: normalizeColor('#007AFF') + }), + borderColor: normalizeColor('#9E9E9E'), + checkboxSize: 14, + checkboxBorderRadius: 3, + checkmarkColor: normalizeColor('#FFFFFF'), + checkedTextColor: normalizeColor('#000000'), + checkedStrikethrough: false + }, + spoiler: { + color: normalizeColor('#374151'), + particles: { + density: 8, + speed: 20 + }, + solid: { + borderRadius: 4 + } + }, + mention: { + color: normalizeColor('#1D4ED8'), + backgroundColor: normalizeColor('#DBEAFE'), + borderColor: normalizeColor('#BFDBFE'), + borderWidth: 0, + borderRadius: 999, + paddingHorizontal: 6, + paddingVertical: 1, + fontFamily: '', + fontWeight: '500', + fontSize: 0, + pressedOpacity: 0.6 + }, + citation: { + color: normalizeColor('#2563EB'), + fontSizeMultiplier: 0.7, + baselineOffsetPx: 0, + fontWeight: '', + underline: false, + backgroundColor: 'transparent', + paddingHorizontal: 0, + paddingVertical: 0, + borderColor: 'transparent', + borderWidth: 0, + borderRadius: 999 + } +}); +const refCache = new WeakMap(); +const structuralCache = []; +const LRU_MAX = 8; +const styleReferenceKeys = Object.keys(DEFAULT_NORMALIZED_STYLE); +export const normalizeMarkdownStyle = style => { + if (!style || Object.keys(style).length === 0) return DEFAULT_NORMALIZED_STYLE; + const refHit = refCache.get(style); + if (refHit) return refHit; + const structIdx = structuralCache.findIndex(e => isStyleEqual(e.style, style, styleReferenceKeys)); + if (structIdx !== -1) { + const entry = structuralCache.splice(structIdx, 1)[0]; + structuralCache.unshift(entry); + refCache.set(style, entry.result); + return entry.result; + } + const result = {}; + Object.keys(DEFAULT_NORMALIZED_STYLE).forEach(key => { + const userValue = style[key]; + result[key] = mergeSubStyle(DEFAULT_NORMALIZED_STYLE[key], userValue); + }); + if (style.taskList?.checkboxSize === undefined) { + const listSize = result.list.fontSize; + result.taskList.checkboxSize = Math.round(listSize * 0.9); + } + const finalResult = Object.freeze(result); + refCache.set(style, finalResult); + structuralCache.unshift({ + style, + result: finalResult + }); + if (structuralCache.length > LRU_MAX) structuralCache.pop(); + return finalResult; +}; +//# sourceMappingURL=normalizeMarkdownStyle.js.map \ No newline at end of file diff --git a/lib/module/normalizeMarkdownStyle.js.map b/lib/module/normalizeMarkdownStyle.js.map new file mode 100644 index 00000000..63fd7bfd --- /dev/null +++ b/lib/module/normalizeMarkdownStyle.js.map @@ -0,0 +1 @@ +{"version":3,"names":["Platform","isStyleEqual","normalizeColor","mergeSubStyle","getSystemFont","select","ios","android","web","default","getMonospaceFont","defaultTextColor","defaultHeadingColor","baseHeader","fontFamily","fontWeight","marginTop","marginBottom","textAlign","DEFAULT_NORMALIZED_STYLE","Object","freeze","paragraph","fontSize","color","lineHeight","h1","h2","h3","h4","h5","h6","blockquote","borderColor","borderWidth","gapWidth","backgroundColor","list","bulletColor","bulletSize","markerColor","markerFontWeight","marginLeft","codeBlock","borderRadius","padding","link","underline","strong","undefined","em","fontStyle","strikethrough","code","image","height","inlineImage","size","thematicBreak","table","headerFontFamily","headerBackgroundColor","headerTextColor","rowEvenBackgroundColor","rowOddBackgroundColor","cellPaddingHorizontal","cellPaddingVertical","math","inlineMath","taskList","checkedColor","checkboxSize","checkboxBorderRadius","checkmarkColor","checkedTextColor","checkedStrikethrough","spoiler","particles","density","speed","solid","mention","paddingHorizontal","paddingVertical","pressedOpacity","citation","fontSizeMultiplier","baselineOffsetPx","refCache","WeakMap","structuralCache","LRU_MAX","styleReferenceKeys","keys","normalizeMarkdownStyle","style","length","refHit","get","structIdx","findIndex","e","entry","splice","unshift","set","result","forEach","key","userValue","listSize","Math","round","finalResult","pop"],"sourceRoot":"../../src","sources":["normalizeMarkdownStyle.ts"],"mappings":";;AAAA,SAASA,QAAQ,QAAQ,cAAc;AAOvC,SAASC,YAAY,EAAEC,cAAc,EAAEC,aAAa,QAAQ,iBAAc;AAE1E,MAAMC,aAAa,GAAGA,CAAA,KACpBJ,QAAQ,CAACK,MAAM,CAAC;EACdC,GAAG,EAAE,QAAQ;EACbC,OAAO,EAAE,YAAY;EACrBC,GAAG,EAAE,8EAA8E;EACnFC,OAAO,EAAE;AACX,CAAC,CAAE;AAEL,MAAMC,gBAAgB,GAAGA,CAAA,KACvBV,QAAQ,CAACK,MAAM,CAAC;EACdC,GAAG,EAAE,OAAO;EACZC,OAAO,EAAE,WAAW;EACpBC,GAAG,EAAE,kGAAkG;EACvGC,OAAO,EAAE;AACX,CAAC,CAAE;AAEL,MAAME,gBAAgB,GAAGT,cAAc,CAAC,SAAS,CAAE;AACnD,MAAMU,mBAAmB,GAAGV,cAAc,CAAC,SAAS,CAAE;;AAEtD;AACA;AACA,MAAMW,UAML,GAAG;EACFC,UAAU,EAAEV,aAAa,CAAC,CAAC;EAC3BW,UAAU,EAAE,EAAE;EACdC,SAAS,EAAE,CAAC;EACZC,YAAY,EAAE,CAAC;EACfC,SAAS,EAAE;AACb,CAAC;AAED,MAAMC,wBAAwB,GAAGC,MAAM,CAACC,MAAM,CAAC;EAC7CC,SAAS,EAAE;IACTC,QAAQ,EAAE,EAAE;IACZT,UAAU,EAAEV,aAAa,CAAC,CAAC;IAC3BW,UAAU,EAAE,EAAE;IACdS,KAAK,EAAEb,gBAAgB;IACvBc,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC,CAAE;IACnEO,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBC,SAAS,EAAE;EACb,CAAC;EACDQ,EAAE,EAAE;IACF,GAAGb,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEZ,mBAAmB;IAC1Ba,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC;EACnE,CAAC;EACDkB,EAAE,EAAE;IACF,GAAGd,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEZ,mBAAmB;IAC1Ba,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC;EACnE,CAAC;EACDmB,EAAE,EAAE;IACF,GAAGf,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEZ,mBAAmB;IAC1Ba,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC;EACnE,CAAC;EACDoB,EAAE,EAAE;IACF,GAAGhB,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEZ,mBAAmB;IAC1Ba,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC;EACnE,CAAC;EACDqB,EAAE,EAAE;IACF,GAAGjB,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEtB,cAAc,CAAC,SAAS,CAAE;IACjCuB,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC;EACnE,CAAC;EACDsB,EAAE,EAAE;IACF,GAAGlB,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEtB,cAAc,CAAC,SAAS,CAAE;IACjCuB,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC;EACnE,CAAC;EACDuB,UAAU,EAAE;IACVT,QAAQ,EAAE,EAAE;IACZT,UAAU,EAAEV,aAAa,CAAC,CAAC;IAC3BW,UAAU,EAAE,EAAE;IACdS,KAAK,EAAEtB,cAAc,CAAC,SAAS,CAAE;IACjCuB,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC,CAAE;IACnEO,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBgB,WAAW,EAAE/B,cAAc,CAAC,SAAS,CAAE;IACvCgC,WAAW,EAAE,CAAC;IACdC,QAAQ,EAAE,EAAE;IACZC,eAAe,EAAElC,cAAc,CAAC,SAAS;EAC3C,CAAC;EACDmC,IAAI,EAAE;IACJd,QAAQ,EAAE,EAAE;IACZT,UAAU,EAAEV,aAAa,CAAC,CAAC;IAC3BW,UAAU,EAAE,EAAE;IACdS,KAAK,EAAEb,gBAAgB;IACvBc,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC,CAAE;IACnEO,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBqB,WAAW,EAAEpC,cAAc,CAAC,SAAS,CAAE;IACvCqC,UAAU,EAAE,CAAC;IACbC,WAAW,EAAEtC,cAAc,CAAC,SAAS,CAAE;IACvCuC,gBAAgB,EAAE,KAAK;IACvBN,QAAQ,EAAE,EAAE;IACZO,UAAU,EAAE;EACd,CAAC;EACDC,SAAS,EAAE;IACTpB,QAAQ,EAAE,EAAE;IACZT,UAAU,EAAEJ,gBAAgB,CAAC,CAAC;IAC9BK,UAAU,EAAE,EAAE;IACdS,KAAK,EAAEtB,cAAc,CAAC,SAAS,CAAE;IACjCuB,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC,CAAE;IACnEO,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBmB,eAAe,EAAElC,cAAc,CAAC,SAAS,CAAE;IAC3C+B,WAAW,EAAE/B,cAAc,CAAC,SAAS,CAAE;IACvC0C,YAAY,EAAE,CAAC;IACfV,WAAW,EAAE,CAAC;IACdW,OAAO,EAAE;EACX,CAAC;EACDC,IAAI,EAAE;IAAEhC,UAAU,EAAE,EAAE;IAAEU,KAAK,EAAEtB,cAAc,CAAC,SAAS,CAAE;IAAE6C,SAAS,EAAE;EAAK,CAAC;EAC5EC,MAAM,EAAE;IAAElC,UAAU,EAAE,EAAE;IAAEC,UAAU,EAAE,MAAM;IAAES,KAAK,EAAEyB;EAAU,CAAC;EAChEC,EAAE,EAAE;IACFpC,UAAU,EAAE,EAAE;IACdqC,SAAS,EAAE,QAA6B;IACxC3B,KAAK,EAAEyB;EACT,CAAC;EACDG,aAAa,EAAE;IAAE5B,KAAK,EAAEtB,cAAc,CAAC,SAAS;EAAG,CAAC;EACpD6C,SAAS,EAAE;IAAEvB,KAAK,EAAEb;EAAiB,CAAC;EACtC0C,IAAI,EAAE;IACJ;IACA;IACAvC,UAAU,EAAEd,QAAQ,CAACK,MAAM,CAAC;MAAEG,GAAG,EAAEE,gBAAgB,CAAC,CAAC;MAAED,OAAO,EAAE;IAAG,CAAC,CAAE;IACtEc,QAAQ,EAAE,CAAC;IACXC,KAAK,EAAEtB,cAAc,CAAC,SAAS,CAAE;IACjCkC,eAAe,EAAElC,cAAc,CAAC,SAAS,CAAE;IAC3C+B,WAAW,EAAE/B,cAAc,CAAC,SAAS;EACvC,CAAC;EACDoD,KAAK,EAAE;IAAEC,MAAM,EAAE,GAAG;IAAEX,YAAY,EAAE,CAAC;IAAE5B,SAAS,EAAE,CAAC;IAAEC,YAAY,EAAE;EAAG,CAAC;EACvEuC,WAAW,EAAE;IAAEC,IAAI,EAAE;EAAG,CAAC;EACzBC,aAAa,EAAE;IACblC,KAAK,EAAEtB,cAAc,CAAC,SAAS,CAAE;IACjCqD,MAAM,EAAE,CAAC;IACTvC,SAAS,EAAE,EAAE;IACbC,YAAY,EAAE;EAChB,CAAC;EACD0C,KAAK,EAAE;IACLpC,QAAQ,EAAE,EAAE;IACZT,UAAU,EAAEV,aAAa,CAAC,CAAC;IAC3BW,UAAU,EAAE,EAAE;IACdS,KAAK,EAAEb,gBAAgB;IACvBK,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBQ,UAAU,EAAEzB,QAAQ,CAACK,MAAM,CAAC;MAAEC,GAAG,EAAE,EAAE;MAAEC,OAAO,EAAE,EAAE;MAAEE,OAAO,EAAE;IAAG,CAAC,CAAE;IACnEmD,gBAAgB,EAAE,EAAE;IACpBC,qBAAqB,EAAE3D,cAAc,CAAC,SAAS,CAAE;IACjD4D,eAAe,EAAE5D,cAAc,CAAC,SAAS,CAAE;IAC3C6D,sBAAsB,EAAE7D,cAAc,CAAC,SAAS,CAAE;IAClD8D,qBAAqB,EAAE9D,cAAc,CAAC,SAAS,CAAE;IACjD+B,WAAW,EAAE/B,cAAc,CAAC,SAAS,CAAE;IACvCgC,WAAW,EAAE,CAAC;IACdU,YAAY,EAAE,CAAC;IACfqB,qBAAqB,EAAE,EAAE;IACzBC,mBAAmB,EAAE;EACvB,CAAC;EACDC,IAAI,EAAE;IACJ5C,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEb,gBAAgB;IACvByB,eAAe,EAAElC,cAAc,CAAC,SAAS,CAAE;IAC3C2C,OAAO,EAAE,EAAE;IACX7B,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBC,SAAS,EAAE;EACb,CAAC;EACDkD,UAAU,EAAE;IAAE5C,KAAK,EAAEb;EAAiB,CAAC;EACvC0D,QAAQ,EAAE;IACRC,YAAY,EAAEtE,QAAQ,CAACK,MAAM,CAAC;MAC5BC,GAAG,EAAEJ,cAAc,CAAC,SAAS,CAAE;MAC/BK,OAAO,EAAEL,cAAc,CAAC,SAAS,CAAE;MACnCO,OAAO,EAAEP,cAAc,CAAC,SAAS;IACnC,CAAC,CAAE;IACH+B,WAAW,EAAE/B,cAAc,CAAC,SAAS,CAAE;IACvCqE,YAAY,EAAE,EAAE;IAChBC,oBAAoB,EAAE,CAAC;IACvBC,cAAc,EAAEvE,cAAc,CAAC,SAAS,CAAE;IAC1CwE,gBAAgB,EAAExE,cAAc,CAAC,SAAS,CAAE;IAC5CyE,oBAAoB,EAAE;EACxB,CAAC;EACDC,OAAO,EAAE;IACPpD,KAAK,EAAEtB,cAAc,CAAC,SAAS,CAAE;IACjC2E,SAAS,EAAE;MAAEC,OAAO,EAAE,CAAC;MAAEC,KAAK,EAAE;IAAG,CAAC;IACpCC,KAAK,EAAE;MAAEpC,YAAY,EAAE;IAAE;EAC3B,CAAC;EACDqC,OAAO,EAAE;IACPzD,KAAK,EAAEtB,cAAc,CAAC,SAAS,CAAE;IACjCkC,eAAe,EAAElC,cAAc,CAAC,SAAS,CAAE;IAC3C+B,WAAW,EAAE/B,cAAc,CAAC,SAAS,CAAE;IACvCgC,WAAW,EAAE,CAAC;IACdU,YAAY,EAAE,GAAG;IACjBsC,iBAAiB,EAAE,CAAC;IACpBC,eAAe,EAAE,CAAC;IAClBrE,UAAU,EAAE,EAAE;IACdC,UAAU,EAAE,KAAK;IACjBQ,QAAQ,EAAE,CAAC;IACX6D,cAAc,EAAE;EAClB,CAAC;EACDC,QAAQ,EAAE;IACR7D,KAAK,EAAEtB,cAAc,CAAC,SAAS,CAAE;IACjCoF,kBAAkB,EAAE,GAAG;IACvBC,gBAAgB,EAAE,CAAC;IACnBxE,UAAU,EAAE,EAAE;IACdgC,SAAS,EAAE,KAAK;IAChBX,eAAe,EAAE,aAAa;IAC9B8C,iBAAiB,EAAE,CAAC;IACpBC,eAAe,EAAE,CAAC;IAClBlD,WAAW,EAAE,aAAa;IAC1BC,WAAW,EAAE,CAAC;IACdU,YAAY,EAAE;EAChB;AACF,CAAC,CAA0B;AAE3B,MAAM4C,QAAQ,GAAG,IAAIC,OAAO,CAAuC,CAAC;AACpE,MAAMC,eAGH,GAAG,EAAE;AACR,MAAMC,OAAO,GAAG,CAAC;AAEjB,MAAMC,kBAAkB,GAAGxE,MAAM,CAACyE,IAAI,CAAC1E,wBAAwB,CAAC;AAEhE,OAAO,MAAM2E,sBAAsB,GACjCC,KAAoB,IACM;EAC1B,IAAI,CAACA,KAAK,IAAI3E,MAAM,CAACyE,IAAI,CAACE,KAAK,CAAC,CAACC,MAAM,KAAK,CAAC,EAC3C,OAAO7E,wBAAwB;EAEjC,MAAM8E,MAAM,GAAGT,QAAQ,CAACU,GAAG,CAACH,KAAK,CAAC;EAClC,IAAIE,MAAM,EAAE,OAAOA,MAAM;EAEzB,MAAME,SAAS,GAAGT,eAAe,CAACU,SAAS,CAAEC,CAAC,IAC5CpG,YAAY,CAACoG,CAAC,CAACN,KAAK,EAAEA,KAAK,EAAEH,kBAAkB,CACjD,CAAC;EACD,IAAIO,SAAS,KAAK,CAAC,CAAC,EAAE;IACpB,MAAMG,KAAK,GAAGZ,eAAe,CAACa,MAAM,CAACJ,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE;IACtDT,eAAe,CAACc,OAAO,CAACF,KAAK,CAAC;IAC9Bd,QAAQ,CAACiB,GAAG,CAACV,KAAK,EAAEO,KAAK,CAACI,MAAM,CAAC;IACjC,OAAOJ,KAAK,CAACI,MAAM;EACrB;EAEA,MAAMA,MAA+B,GAAG,CAAC,CAAC;EAExCtF,MAAM,CAACyE,IAAI,CAAC1E,wBAAwB,CAAC,CACrCwF,OAAO,CAAEC,GAAG,IAAK;IACjB,MAAMC,SAAS,GAAGd,KAAK,CAACa,GAAG,CAEd;IACbF,MAAM,CAACE,GAAG,CAAC,GAAGzG,aAAa,CACzBgB,wBAAwB,CAACyF,GAAG,CAAC,EAC7BC,SACF,CAAC;EACH,CAAC,CAAC;EAEF,IAAId,KAAK,CAAC1B,QAAQ,EAAEE,YAAY,KAAKtB,SAAS,EAAE;IAC9C,MAAM6D,QAAQ,GAAIJ,MAAM,CAACrE,IAAI,CAA0Bd,QAAQ;IAC9DmF,MAAM,CAACrC,QAAQ,CAA8BE,YAAY,GAAGwC,IAAI,CAACC,KAAK,CACrEF,QAAQ,GAAG,GACb,CAAC;EACH;EAEA,MAAMG,WAAW,GAAG7F,MAAM,CAACC,MAAM,CAACqF,MAAM,CAAqC;EAC7ElB,QAAQ,CAACiB,GAAG,CAACV,KAAK,EAAEkB,WAAW,CAAC;EAChCvB,eAAe,CAACc,OAAO,CAAC;IAAET,KAAK;IAAEW,MAAM,EAAEO;EAAY,CAAC,CAAC;EACvD,IAAIvB,eAAe,CAACM,MAAM,GAAGL,OAAO,EAAED,eAAe,CAACwB,GAAG,CAAC,CAAC;EAE3D,OAAOD,WAAW;AACpB,CAAC","ignoreList":[]} diff --git a/lib/module/normalizeMarkdownStyle.web.js b/lib/module/normalizeMarkdownStyle.web.js new file mode 100644 index 00000000..3e32955b --- /dev/null +++ b/lib/module/normalizeMarkdownStyle.web.js @@ -0,0 +1,258 @@ +"use strict"; + +import { isStyleEqual, mergeSubStyle } from "./styleUtils.js"; +const SYSTEM_FONT = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'; +const MONOSPACE_FONT = 'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace'; +const defaultTextColor = '#1F2937'; +const defaultHeadingColor = '#111827'; +const baseHeader = { + fontFamily: SYSTEM_FONT, + fontWeight: '', + marginTop: 0, + marginBottom: 8, + textAlign: 'auto' +}; +const DEFAULT_NORMALIZED_STYLE = Object.freeze({ + paragraph: { + fontSize: 16, + fontFamily: SYSTEM_FONT, + fontWeight: '', + color: defaultTextColor, + lineHeight: 26, + marginTop: 0, + marginBottom: 16, + textAlign: 'auto' + }, + h1: { + ...baseHeader, + fontSize: 30, + color: defaultHeadingColor, + lineHeight: 38 + }, + h2: { + ...baseHeader, + fontSize: 24, + color: defaultHeadingColor, + lineHeight: 32 + }, + h3: { + ...baseHeader, + fontSize: 20, + color: defaultHeadingColor, + lineHeight: 28 + }, + h4: { + ...baseHeader, + fontSize: 18, + color: defaultHeadingColor, + lineHeight: 26 + }, + h5: { + ...baseHeader, + fontSize: 16, + color: '#374151', + lineHeight: 24 + }, + h6: { + ...baseHeader, + fontSize: 14, + color: '#4B5563', + lineHeight: 22 + }, + blockquote: { + fontSize: 16, + fontFamily: SYSTEM_FONT, + fontWeight: '', + color: '#4B5563', + lineHeight: 26, + marginTop: 0, + marginBottom: 16, + borderColor: '#D1D5DB', + borderWidth: 3, + gapWidth: 16, + backgroundColor: '#F9FAFB' + }, + list: { + fontSize: 16, + fontFamily: SYSTEM_FONT, + fontWeight: '', + color: defaultTextColor, + lineHeight: 26, + marginTop: 0, + marginBottom: 16, + bulletColor: '#6B7280', + bulletSize: 6, + markerColor: '#6B7280', + markerFontWeight: '500', + gapWidth: 12, + marginLeft: 24 + }, + codeBlock: { + fontSize: 14, + fontFamily: MONOSPACE_FONT, + fontWeight: '', + color: '#F3F4F6', + lineHeight: 22, + marginTop: 0, + marginBottom: 16, + backgroundColor: '#1F2937', + borderColor: '#374151', + borderRadius: 8, + borderWidth: 1, + padding: 16 + }, + link: { + fontFamily: '', + color: '#2563EB', + underline: true + }, + strong: { + fontFamily: '', + fontWeight: 'bold', + color: undefined + }, + em: { + fontFamily: '', + fontStyle: 'italic', + color: undefined + }, + strikethrough: { + color: '#9CA3AF' + }, + underline: { + color: defaultTextColor + }, + code: { + fontFamily: MONOSPACE_FONT, + fontSize: 0, + color: '#E01E5A', + backgroundColor: '#FDF2F4', + borderColor: '#F8D7DA' + }, + image: { + height: 200, + borderRadius: 8, + marginTop: 0, + marginBottom: 16 + }, + inlineImage: { + size: 20 + }, + thematicBreak: { + color: '#E5E7EB', + height: 1, + marginTop: 24, + marginBottom: 24 + }, + table: { + fontSize: 14, + fontFamily: SYSTEM_FONT, + fontWeight: '', + color: defaultTextColor, + marginTop: 0, + marginBottom: 16, + lineHeight: 22, + headerFontFamily: '', + headerBackgroundColor: '#F3F4F6', + headerTextColor: '#111827', + rowEvenBackgroundColor: '#FFFFFF', + rowOddBackgroundColor: '#F9FAFB', + borderColor: '#E5E7EB', + borderWidth: 1, + borderRadius: 6, + cellPaddingHorizontal: 12, + cellPaddingVertical: 8 + }, + math: { + fontSize: 20, + color: defaultTextColor, + backgroundColor: '#F3F4F6', + padding: 12, + marginTop: 0, + marginBottom: 16, + textAlign: 'center' + }, + inlineMath: { + color: defaultTextColor + }, + taskList: { + checkedColor: '#007AFF', + borderColor: '#9E9E9E', + checkboxSize: 14, + checkboxBorderRadius: 3, + checkmarkColor: '#FFFFFF', + checkedTextColor: '#000000', + checkedStrikethrough: false + }, + // Spoiler rendering is not supported on web yet — defaults kept for type compatibility. + spoiler: { + color: '#374151', + particles: { + density: 8, + speed: 20 + }, + solid: { + borderRadius: 4 + } + }, + mention: { + color: '#1D4ED8', + backgroundColor: '#DBEAFE', + borderColor: '#BFDBFE', + borderWidth: 0, + borderRadius: 999, + paddingHorizontal: 6, + paddingVertical: 1, + fontFamily: '', + fontWeight: '500', + fontSize: 0, + pressedOpacity: 0.6 + }, + citation: { + color: '#2563EB', + fontSizeMultiplier: 0.7, + baselineOffsetPx: 0, + fontWeight: '', + underline: false, + backgroundColor: 'transparent', + paddingHorizontal: 0, + paddingVertical: 0, + borderColor: 'transparent', + borderWidth: 0, + borderRadius: 999 + } +}); +const refCache = new WeakMap(); +const structuralCache = []; +const LRU_MAX = 8; +const styleReferenceKeys = Object.keys(DEFAULT_NORMALIZED_STYLE); +export const normalizeMarkdownStyle = style => { + if (!style || Object.keys(style).length === 0) return DEFAULT_NORMALIZED_STYLE; + const refHit = refCache.get(style); + if (refHit) return refHit; + const structIdx = structuralCache.findIndex(e => isStyleEqual(e.style, style, styleReferenceKeys)); + if (structIdx !== -1) { + const entry = structuralCache.splice(structIdx, 1)[0]; + structuralCache.unshift(entry); + refCache.set(style, entry.result); + return entry.result; + } + const result = {}; + Object.keys(DEFAULT_NORMALIZED_STYLE).forEach(key => { + const userValue = style[key]; + result[key] = mergeSubStyle(DEFAULT_NORMALIZED_STYLE[key], userValue); + }); + if (style.taskList?.checkboxSize === undefined) { + const listSize = result.list.fontSize; + result.taskList.checkboxSize = Math.round(listSize * 0.9); + } + const finalResult = Object.freeze(result); + refCache.set(style, finalResult); + structuralCache.unshift({ + style, + result: finalResult + }); + if (structuralCache.length > LRU_MAX) structuralCache.pop(); + return finalResult; +}; +//# sourceMappingURL=normalizeMarkdownStyle.web.js.map \ No newline at end of file diff --git a/lib/module/normalizeMarkdownStyle.web.js.map b/lib/module/normalizeMarkdownStyle.web.js.map new file mode 100644 index 00000000..9b29bbe1 --- /dev/null +++ b/lib/module/normalizeMarkdownStyle.web.js.map @@ -0,0 +1 @@ +{"version":3,"names":["isStyleEqual","mergeSubStyle","SYSTEM_FONT","MONOSPACE_FONT","defaultTextColor","defaultHeadingColor","baseHeader","fontFamily","fontWeight","marginTop","marginBottom","textAlign","DEFAULT_NORMALIZED_STYLE","Object","freeze","paragraph","fontSize","color","lineHeight","h1","h2","h3","h4","h5","h6","blockquote","borderColor","borderWidth","gapWidth","backgroundColor","list","bulletColor","bulletSize","markerColor","markerFontWeight","marginLeft","codeBlock","borderRadius","padding","link","underline","strong","undefined","em","fontStyle","strikethrough","code","image","height","inlineImage","size","thematicBreak","table","headerFontFamily","headerBackgroundColor","headerTextColor","rowEvenBackgroundColor","rowOddBackgroundColor","cellPaddingHorizontal","cellPaddingVertical","math","inlineMath","taskList","checkedColor","checkboxSize","checkboxBorderRadius","checkmarkColor","checkedTextColor","checkedStrikethrough","spoiler","particles","density","speed","solid","mention","paddingHorizontal","paddingVertical","pressedOpacity","citation","fontSizeMultiplier","baselineOffsetPx","refCache","WeakMap","structuralCache","LRU_MAX","styleReferenceKeys","keys","normalizeMarkdownStyle","style","length","refHit","get","structIdx","findIndex","e","entry","splice","unshift","set","result","forEach","key","userValue","listSize","Math","round","finalResult","pop"],"sourceRoot":"../../src","sources":["normalizeMarkdownStyle.web.ts"],"mappings":";;AAMA,SAASA,YAAY,EAAEC,aAAa,QAAQ,iBAAc;AAE1D,MAAMC,WAAW,GACf,8EAA8E;AAChF,MAAMC,cAAc,GAClB,kGAAkG;AAEpG,MAAMC,gBAAgB,GAAG,SAAS;AAClC,MAAMC,mBAAmB,GAAG,SAAS;AAErC,MAAMC,UAML,GAAG;EACFC,UAAU,EAAEL,WAAW;EACvBM,UAAU,EAAE,EAAE;EACdC,SAAS,EAAE,CAAC;EACZC,YAAY,EAAE,CAAC;EACfC,SAAS,EAAE;AACb,CAAC;AAED,MAAMC,wBAA+C,GAAGC,MAAM,CAACC,MAAM,CAAC;EACpEC,SAAS,EAAE;IACTC,QAAQ,EAAE,EAAE;IACZT,UAAU,EAAEL,WAAW;IACvBM,UAAU,EAAE,EAAE;IACdS,KAAK,EAAEb,gBAAgB;IACvBc,UAAU,EAAE,EAAE;IACdT,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBC,SAAS,EAAE;EACb,CAAC;EACDQ,EAAE,EAAE;IACF,GAAGb,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEZ,mBAAmB;IAC1Ba,UAAU,EAAE;EACd,CAAC;EACDE,EAAE,EAAE;IACF,GAAGd,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEZ,mBAAmB;IAC1Ba,UAAU,EAAE;EACd,CAAC;EACDG,EAAE,EAAE;IACF,GAAGf,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEZ,mBAAmB;IAC1Ba,UAAU,EAAE;EACd,CAAC;EACDI,EAAE,EAAE;IACF,GAAGhB,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEZ,mBAAmB;IAC1Ba,UAAU,EAAE;EACd,CAAC;EACDK,EAAE,EAAE;IACF,GAAGjB,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAE,SAAS;IAChBC,UAAU,EAAE;EACd,CAAC;EACDM,EAAE,EAAE;IACF,GAAGlB,UAAU;IACbU,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAE,SAAS;IAChBC,UAAU,EAAE;EACd,CAAC;EACDO,UAAU,EAAE;IACVT,QAAQ,EAAE,EAAE;IACZT,UAAU,EAAEL,WAAW;IACvBM,UAAU,EAAE,EAAE;IACdS,KAAK,EAAE,SAAS;IAChBC,UAAU,EAAE,EAAE;IACdT,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBgB,WAAW,EAAE,SAAS;IACtBC,WAAW,EAAE,CAAC;IACdC,QAAQ,EAAE,EAAE;IACZC,eAAe,EAAE;EACnB,CAAC;EACDC,IAAI,EAAE;IACJd,QAAQ,EAAE,EAAE;IACZT,UAAU,EAAEL,WAAW;IACvBM,UAAU,EAAE,EAAE;IACdS,KAAK,EAAEb,gBAAgB;IACvBc,UAAU,EAAE,EAAE;IACdT,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBqB,WAAW,EAAE,SAAS;IACtBC,UAAU,EAAE,CAAC;IACbC,WAAW,EAAE,SAAS;IACtBC,gBAAgB,EAAE,KAAK;IACvBN,QAAQ,EAAE,EAAE;IACZO,UAAU,EAAE;EACd,CAAC;EACDC,SAAS,EAAE;IACTpB,QAAQ,EAAE,EAAE;IACZT,UAAU,EAAEJ,cAAc;IAC1BK,UAAU,EAAE,EAAE;IACdS,KAAK,EAAE,SAAS;IAChBC,UAAU,EAAE,EAAE;IACdT,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBmB,eAAe,EAAE,SAAS;IAC1BH,WAAW,EAAE,SAAS;IACtBW,YAAY,EAAE,CAAC;IACfV,WAAW,EAAE,CAAC;IACdW,OAAO,EAAE;EACX,CAAC;EACDC,IAAI,EAAE;IAAEhC,UAAU,EAAE,EAAE;IAAEU,KAAK,EAAE,SAAS;IAAEuB,SAAS,EAAE;EAAK,CAAC;EAC3DC,MAAM,EAAE;IAAElC,UAAU,EAAE,EAAE;IAAEC,UAAU,EAAE,MAAM;IAAES,KAAK,EAAEyB;EAAU,CAAC;EAChEC,EAAE,EAAE;IACFpC,UAAU,EAAE,EAAE;IACdqC,SAAS,EAAE,QAA6B;IACxC3B,KAAK,EAAEyB;EACT,CAAC;EACDG,aAAa,EAAE;IAAE5B,KAAK,EAAE;EAAU,CAAC;EACnCuB,SAAS,EAAE;IAAEvB,KAAK,EAAEb;EAAiB,CAAC;EACtC0C,IAAI,EAAE;IACJvC,UAAU,EAAEJ,cAAc;IAC1Ba,QAAQ,EAAE,CAAC;IACXC,KAAK,EAAE,SAAS;IAChBY,eAAe,EAAE,SAAS;IAC1BH,WAAW,EAAE;EACf,CAAC;EACDqB,KAAK,EAAE;IAAEC,MAAM,EAAE,GAAG;IAAEX,YAAY,EAAE,CAAC;IAAE5B,SAAS,EAAE,CAAC;IAAEC,YAAY,EAAE;EAAG,CAAC;EACvEuC,WAAW,EAAE;IAAEC,IAAI,EAAE;EAAG,CAAC;EACzBC,aAAa,EAAE;IACblC,KAAK,EAAE,SAAS;IAChB+B,MAAM,EAAE,CAAC;IACTvC,SAAS,EAAE,EAAE;IACbC,YAAY,EAAE;EAChB,CAAC;EACD0C,KAAK,EAAE;IACLpC,QAAQ,EAAE,EAAE;IACZT,UAAU,EAAEL,WAAW;IACvBM,UAAU,EAAE,EAAE;IACdS,KAAK,EAAEb,gBAAgB;IACvBK,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBQ,UAAU,EAAE,EAAE;IACdmC,gBAAgB,EAAE,EAAE;IACpBC,qBAAqB,EAAE,SAAS;IAChCC,eAAe,EAAE,SAAS;IAC1BC,sBAAsB,EAAE,SAAS;IACjCC,qBAAqB,EAAE,SAAS;IAChC/B,WAAW,EAAE,SAAS;IACtBC,WAAW,EAAE,CAAC;IACdU,YAAY,EAAE,CAAC;IACfqB,qBAAqB,EAAE,EAAE;IACzBC,mBAAmB,EAAE;EACvB,CAAC;EACDC,IAAI,EAAE;IACJ5C,QAAQ,EAAE,EAAE;IACZC,KAAK,EAAEb,gBAAgB;IACvByB,eAAe,EAAE,SAAS;IAC1BS,OAAO,EAAE,EAAE;IACX7B,SAAS,EAAE,CAAC;IACZC,YAAY,EAAE,EAAE;IAChBC,SAAS,EAAE;EACb,CAAC;EACDkD,UAAU,EAAE;IAAE5C,KAAK,EAAEb;EAAiB,CAAC;EACvC0D,QAAQ,EAAE;IACRC,YAAY,EAAE,SAAS;IACvBrC,WAAW,EAAE,SAAS;IACtBsC,YAAY,EAAE,EAAE;IAChBC,oBAAoB,EAAE,CAAC;IACvBC,cAAc,EAAE,SAAS;IACzBC,gBAAgB,EAAE,SAAS;IAC3BC,oBAAoB,EAAE;EACxB,CAAC;EACD;EACAC,OAAO,EAAE;IACPpD,KAAK,EAAE,SAAS;IAChBqD,SAAS,EAAE;MAAEC,OAAO,EAAE,CAAC;MAAEC,KAAK,EAAE;IAAG,CAAC;IACpCC,KAAK,EAAE;MAAEpC,YAAY,EAAE;IAAE;EAC3B,CAAC;EACDqC,OAAO,EAAE;IACPzD,KAAK,EAAE,SAAS;IAChBY,eAAe,EAAE,SAAS;IAC1BH,WAAW,EAAE,SAAS;IACtBC,WAAW,EAAE,CAAC;IACdU,YAAY,EAAE,GAAG;IACjBsC,iBAAiB,EAAE,CAAC;IACpBC,eAAe,EAAE,CAAC;IAClBrE,UAAU,EAAE,EAAE;IACdC,UAAU,EAAE,KAAK;IACjBQ,QAAQ,EAAE,CAAC;IACX6D,cAAc,EAAE;EAClB,CAAC;EACDC,QAAQ,EAAE;IACR7D,KAAK,EAAE,SAAS;IAChB8D,kBAAkB,EAAE,GAAG;IACvBC,gBAAgB,EAAE,CAAC;IACnBxE,UAAU,EAAE,EAAE;IACdgC,SAAS,EAAE,KAAK;IAChBX,eAAe,EAAE,aAAa;IAC9B8C,iBAAiB,EAAE,CAAC;IACpBC,eAAe,EAAE,CAAC;IAClBlD,WAAW,EAAE,aAAa;IAC1BC,WAAW,EAAE,CAAC;IACdU,YAAY,EAAE;EAChB;AACF,CAAC,CAAC;AAEF,MAAM4C,QAAQ,GAAG,IAAIC,OAAO,CAAuC,CAAC;AACpE,MAAMC,eAGH,GAAG,EAAE;AACR,MAAMC,OAAO,GAAG,CAAC;AAEjB,MAAMC,kBAAkB,GAAGxE,MAAM,CAACyE,IAAI,CAAC1E,wBAAwB,CAAC;AAEhE,OAAO,MAAM2E,sBAAsB,GACjCC,KAAoB,IACM;EAC1B,IAAI,CAACA,KAAK,IAAI3E,MAAM,CAACyE,IAAI,CAACE,KAAK,CAAC,CAACC,MAAM,KAAK,CAAC,EAC3C,OAAO7E,wBAAwB;EAEjC,MAAM8E,MAAM,GAAGT,QAAQ,CAACU,GAAG,CAACH,KAAK,CAAC;EAClC,IAAIE,MAAM,EAAE,OAAOA,MAAM;EAEzB,MAAME,SAAS,GAAGT,eAAe,CAACU,SAAS,CAAEC,CAAC,IAC5C9F,YAAY,CAAC8F,CAAC,CAACN,KAAK,EAAEA,KAAK,EAAEH,kBAAkB,CACjD,CAAC;EACD,IAAIO,SAAS,KAAK,CAAC,CAAC,EAAE;IACpB,MAAMG,KAAK,GAAGZ,eAAe,CAACa,MAAM,CAACJ,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE;IACtDT,eAAe,CAACc,OAAO,CAACF,KAAK,CAAC;IAC9Bd,QAAQ,CAACiB,GAAG,CAACV,KAAK,EAAEO,KAAK,CAACI,MAAM,CAAC;IACjC,OAAOJ,KAAK,CAACI,MAAM;EACrB;EAEA,MAAMA,MAA+B,GAAG,CAAC,CAAC;EAExCtF,MAAM,CAACyE,IAAI,CAAC1E,wBAAwB,CAAC,CACrCwF,OAAO,CAAEC,GAAG,IAAK;IACjB,MAAMC,SAAS,GAAGd,KAAK,CAACa,GAAG,CAEd;IACbF,MAAM,CAACE,GAAG,CAAC,GAAGpG,aAAa,CACzBW,wBAAwB,CAACyF,GAAG,CAAC,EAC7BC,SACF,CAAC;EACH,CAAC,CAAC;EAEF,IAAId,KAAK,CAAC1B,QAAQ,EAAEE,YAAY,KAAKtB,SAAS,EAAE;IAC9C,MAAM6D,QAAQ,GAAIJ,MAAM,CAACrE,IAAI,CAA0Bd,QAAQ;IAC9DmF,MAAM,CAACrC,QAAQ,CAA8BE,YAAY,GAAGwC,IAAI,CAACC,KAAK,CACrEF,QAAQ,GAAG,GACb,CAAC;EACH;EAEA,MAAMG,WAAW,GAAG7F,MAAM,CAACC,MAAM,CAACqF,MAAM,CAAqC;EAC7ElB,QAAQ,CAACiB,GAAG,CAACV,KAAK,EAAEkB,WAAW,CAAC;EAChCvB,eAAe,CAACc,OAAO,CAAC;IAAET,KAAK;IAAEW,MAAM,EAAEO;EAAY,CAAC,CAAC;EACvD,IAAIvB,eAAe,CAACM,MAAM,GAAGL,OAAO,EAAED,eAAe,CAACwB,GAAG,CAAC,CAAC;EAE3D,OAAOD,WAAW;AACpB,CAAC","ignoreList":[]} diff --git a/lib/module/package.json b/lib/module/package.json new file mode 100644 index 00000000..089153bc --- /dev/null +++ b/lib/module/package.json @@ -0,0 +1 @@ +{"type":"module"} diff --git a/lib/module/plugin/withAndroidMath.js b/lib/module/plugin/withAndroidMath.js new file mode 100644 index 00000000..3fa84abc --- /dev/null +++ b/lib/module/plugin/withAndroidMath.js @@ -0,0 +1,23 @@ +"use strict"; + +import configPlugins from '@expo/config-plugins'; +const { + withGradleProperties +} = configPlugins; +export const withAndroidMath = (config, { + enableMath = true +}) => { + if (enableMath) { + return config; + } + return withGradleProperties(config, gradleConfig => { + gradleConfig.modResults = gradleConfig.modResults.filter(prop => prop.type !== 'property' || prop.key !== 'enrichedMarkdown.enableMath'); + gradleConfig.modResults.push({ + type: 'property', + key: 'enrichedMarkdown.enableMath', + value: 'false' + }); + return gradleConfig; + }); +}; +//# sourceMappingURL=withAndroidMath.js.map \ No newline at end of file diff --git a/lib/module/plugin/withAndroidMath.js.map b/lib/module/plugin/withAndroidMath.js.map new file mode 100644 index 00000000..9c68b60d --- /dev/null +++ b/lib/module/plugin/withAndroidMath.js.map @@ -0,0 +1 @@ +{"version":3,"names":["configPlugins","withGradleProperties","withAndroidMath","config","enableMath","gradleConfig","modResults","filter","prop","type","key","push","value"],"sourceRoot":"../../../src","sources":["plugin/withAndroidMath.ts"],"mappings":";;AAAA,OAAOA,aAAa,MAA6B,sBAAsB;AAEvE,MAAM;EAAEC;AAAqB,CAAC,GAAGD,aAAa;AAE9C,OAAO,MAAME,eAAuD,GAAGA,CACrEC,MAAM,EACN;EAAEC,UAAU,GAAG;AAAK,CAAC,KAClB;EACH,IAAIA,UAAU,EAAE;IACd,OAAOD,MAAM;EACf;EACA,OAAOF,oBAAoB,CAACE,MAAM,EAAGE,YAAY,IAAK;IACpDA,YAAY,CAACC,UAAU,GAAGD,YAAY,CAACC,UAAU,CAACC,MAAM,CACrDC,IAAI,IACHA,IAAI,CAACC,IAAI,KAAK,UAAU,IAAID,IAAI,CAACE,GAAG,KAAK,6BAC7C,CAAC;IAEDL,YAAY,CAACC,UAAU,CAACK,IAAI,CAAC;MAC3BF,IAAI,EAAE,UAAU;MAChBC,GAAG,EAAE,6BAA6B;MAClCE,KAAK,EAAE;IACT,CAAC,CAAC;IAEF,OAAOP,YAAY;EACrB,CAAC,CAAC;AACJ,CAAC","ignoreList":[]} diff --git a/lib/module/plugin/withIosMath.js b/lib/module/plugin/withIosMath.js new file mode 100644 index 00000000..9c1124d9 --- /dev/null +++ b/lib/module/plugin/withIosMath.js @@ -0,0 +1,26 @@ +"use strict"; + +import configPlugins from '@expo/config-plugins'; +import fs from 'fs'; +import path from 'path'; +const { + withDangerousMod +} = configPlugins; +const IOS_MATH_OPTION = "ENV['ENRICHED_MARKDOWN_ENABLE_MATH'] = '0'"; +export const withIosMath = (config, { + enableMath = true +}) => { + if (enableMath) { + return config; + } + return withDangerousMod(config, ['ios', async modConfig => { + const file = path.join(modConfig.modRequest.platformProjectRoot, 'Podfile'); + const contents = fs.readFileSync(file, 'utf8'); + const lines = contents.split('\n'); + const filteredLines = lines.filter(line => !line.includes('ENRICHED_MARKDOWN_ENABLE_MATH')); + filteredLines.unshift(IOS_MATH_OPTION); + fs.writeFileSync(file, filteredLines.join('\n')); + return modConfig; + }]); +}; +//# sourceMappingURL=withIosMath.js.map \ No newline at end of file diff --git a/lib/module/plugin/withIosMath.js.map b/lib/module/plugin/withIosMath.js.map new file mode 100644 index 00000000..3d7ce7c8 --- /dev/null +++ b/lib/module/plugin/withIosMath.js.map @@ -0,0 +1 @@ +{"version":3,"names":["configPlugins","fs","path","withDangerousMod","IOS_MATH_OPTION","withIosMath","config","enableMath","modConfig","file","join","modRequest","platformProjectRoot","contents","readFileSync","lines","split","filteredLines","filter","line","includes","unshift","writeFileSync"],"sourceRoot":"../../../src","sources":["plugin/withIosMath.ts"],"mappings":";;AAAA,OAAOA,aAAa,MAA6B,sBAAsB;AACvE,OAAOC,EAAE,MAAM,IAAI;AACnB,OAAOC,IAAI,MAAM,MAAM;AAEvB,MAAM;EAAEC;AAAiB,CAAC,GAAGH,aAAa;AAE1C,MAAMI,eAAe,GAAG,4CAA4C;AAEpE,OAAO,MAAMC,WAAmD,GAAGA,CACjEC,MAAM,EACN;EAAEC,UAAU,GAAG;AAAK,CAAC,KAClB;EACH,IAAIA,UAAU,EAAE;IACd,OAAOD,MAAM;EACf;EACA,OAAOH,gBAAgB,CAACG,MAAM,EAAE,CAC9B,KAAK,EACL,MAAOE,SAAS,IAAK;IACnB,MAAMC,IAAI,GAAGP,IAAI,CAACQ,IAAI,CACpBF,SAAS,CAACG,UAAU,CAACC,mBAAmB,EACxC,SACF,CAAC;IACD,MAAMC,QAAQ,GAAGZ,EAAE,CAACa,YAAY,CAACL,IAAI,EAAE,MAAM,CAAC;IAE9C,MAAMM,KAAK,GAAGF,QAAQ,CAACG,KAAK,CAAC,IAAI,CAAC;IAClC,MAAMC,aAAa,GAAGF,KAAK,CAACG,MAAM,CAC/BC,IAAI,IAAK,CAACA,IAAI,CAACC,QAAQ,CAAC,+BAA+B,CAC1D,CAAC;IAEDH,aAAa,CAACI,OAAO,CAACjB,eAAe,CAAC;IAEtCH,EAAE,CAACqB,aAAa,CAACb,IAAI,EAAEQ,aAAa,CAACP,IAAI,CAAC,IAAI,CAAC,CAAC;IAEhD,OAAOF,SAAS;EAClB,CAAC,CACF,CAAC;AACJ,CAAC","ignoreList":[]} diff --git a/lib/module/plugin/withReactNativeEnrichedMarkdown.js b/lib/module/plugin/withReactNativeEnrichedMarkdown.js new file mode 100644 index 00000000..8f56559e --- /dev/null +++ b/lib/module/plugin/withReactNativeEnrichedMarkdown.js @@ -0,0 +1,16 @@ +"use strict"; + +import { withIosMath } from "./withIosMath.js"; +import { withAndroidMath } from "./withAndroidMath.js"; +const withEnrichedMarkdown = (config, props) => { + const enableMath = props?.enableMath !== false; + config = withAndroidMath(config, { + enableMath + }); + config = withIosMath(config, { + enableMath + }); + return config; +}; +export default withEnrichedMarkdown; +//# sourceMappingURL=withReactNativeEnrichedMarkdown.js.map \ No newline at end of file diff --git a/lib/module/plugin/withReactNativeEnrichedMarkdown.js.map b/lib/module/plugin/withReactNativeEnrichedMarkdown.js.map new file mode 100644 index 00000000..da36fa62 --- /dev/null +++ b/lib/module/plugin/withReactNativeEnrichedMarkdown.js.map @@ -0,0 +1 @@ +{"version":3,"names":["withIosMath","withAndroidMath","withEnrichedMarkdown","config","props","enableMath"],"sourceRoot":"../../../src","sources":["plugin/withReactNativeEnrichedMarkdown.ts"],"mappings":";;AACA,SAASA,WAAW,QAAQ,kBAAe;AAC3C,SAASC,eAAe,QAAQ,sBAAmB;AAEnD,MAAMC,oBAAmE,GAAGA,CAC1EC,MAAM,EACNC,KAAK,KACF;EACH,MAAMC,UAAU,GAAGD,KAAK,EAAEC,UAAU,KAAK,KAAK;EAE9CF,MAAM,GAAGF,eAAe,CAACE,MAAM,EAAE;IAAEE;EAAW,CAAC,CAAC;EAChDF,MAAM,GAAGH,WAAW,CAACG,MAAM,EAAE;IAAEE;EAAW,CAAC,CAAC;EAE5C,OAAOF,MAAM;AACf,CAAC;AAED,eAAeD,oBAAoB","ignoreList":[]} diff --git a/lib/module/styleUtils.js b/lib/module/styleUtils.js new file mode 100644 index 00000000..818311d1 --- /dev/null +++ b/lib/module/styleUtils.js @@ -0,0 +1,64 @@ +"use strict"; + +import { Platform, processColor } from 'react-native'; +export const normalizeColor = color => { + if (!color) return undefined; + if (Platform.OS === 'web') return color; + return processColor(color) ?? undefined; +}; +export function mergeSubStyle(defaultStyle, userStyle) { + if (!userStyle) return defaultStyle; + const result = { + ...defaultStyle, + ...userStyle + }; + for (const key in result) { + const defaultValue = defaultStyle[key]; + const userValue = userStyle[key]; + if (typeof defaultValue === 'object' && defaultValue !== null && !Array.isArray(defaultValue) && typeof userValue === 'object' && userValue !== null && !Array.isArray(userValue)) { + result[key] = { + ...defaultValue, + ...userValue + }; + } + if (key.toLowerCase().includes('color') && typeof result[key] === 'string') { + result[key] = normalizeColor(result[key]); + } + } + return result; +} +function isSubStyleEqual(a, b) { + const keys = Object.keys(a); + if (keys.length !== Object.keys(b).length) return false; + for (const key of keys) { + const valueA = a[key]; + const valueB = b[key]; + if (valueA === valueB) continue; + if (typeof valueA === 'object' && valueA !== null && typeof valueB === 'object' && valueB !== null) { + const nestedKeysA = Object.keys(valueA); + const nestedKeysB = Object.keys(valueB); + if (nestedKeysA.length !== nestedKeysB.length) return false; + for (const nestedKey of nestedKeysA) { + if (valueA[nestedKey] !== valueB[nestedKey]) { + return false; + } + } + continue; + } + return false; + } + return true; +} +export function isStyleEqual(a, b, referenceKeys) { + for (const key of referenceKeys) { + const subA = a[key]; + const subB = b[key]; + if (subA === subB) continue; + if (!subA || !subB) return false; + if (!isSubStyleEqual(subA, subB)) { + return false; + } + } + return true; +} +//# sourceMappingURL=styleUtils.js.map \ No newline at end of file diff --git a/lib/module/styleUtils.js.map b/lib/module/styleUtils.js.map new file mode 100644 index 00000000..a88a2695 --- /dev/null +++ b/lib/module/styleUtils.js.map @@ -0,0 +1 @@ +{"version":3,"names":["Platform","processColor","normalizeColor","color","undefined","OS","mergeSubStyle","defaultStyle","userStyle","result","key","defaultValue","userValue","Array","isArray","toLowerCase","includes","isSubStyleEqual","a","b","keys","Object","length","valueA","valueB","nestedKeysA","nestedKeysB","nestedKey","isStyleEqual","referenceKeys","subA","subB"],"sourceRoot":"../../src","sources":["styleUtils.ts"],"mappings":";;AAAA,SAASA,QAAQ,EAAEC,YAAY,QAAyB,cAAc;AAGtE,OAAO,MAAMC,cAAc,GACzBC,KAAyB,IACE;EAC3B,IAAI,CAACA,KAAK,EAAE,OAAOC,SAAS;EAC5B,IAAIJ,QAAQ,CAACK,EAAE,KAAK,KAAK,EAAE,OAAOF,KAAK;EACvC,OAAOF,YAAY,CAACE,KAAK,CAAC,IAAIC,SAAS;AACzC,CAAC;AAED,OAAO,SAASE,aAAaA,CAC3BC,YAAe,EACfC,SAAsB,EACnB;EACH,IAAI,CAACA,SAAS,EAAE,OAAOD,YAAY;EACnC,MAAME,MAA+B,GAAG;IAAE,GAAGF,YAAY;IAAE,GAAGC;EAAU,CAAC;EACzE,KAAK,MAAME,GAAG,IAAID,MAAM,EAAE;IACxB,MAAME,YAAY,GAAGJ,YAAY,CAACG,GAAG,CAAC;IACtC,MAAME,SAAS,GAAGJ,SAAS,CAACE,GAAG,CAAC;IAChC,IACE,OAAOC,YAAY,KAAK,QAAQ,IAChCA,YAAY,KAAK,IAAI,IACrB,CAACE,KAAK,CAACC,OAAO,CAACH,YAAY,CAAC,IAC5B,OAAOC,SAAS,KAAK,QAAQ,IAC7BA,SAAS,KAAK,IAAI,IAClB,CAACC,KAAK,CAACC,OAAO,CAACF,SAAS,CAAC,EACzB;MACAH,MAAM,CAACC,GAAG,CAAC,GAAG;QACZ,GAAIC,YAAwC;QAC5C,GAAIC;MACN,CAAC;IACH;IACA,IACEF,GAAG,CAACK,WAAW,CAAC,CAAC,CAACC,QAAQ,CAAC,OAAO,CAAC,IACnC,OAAOP,MAAM,CAACC,GAAG,CAAC,KAAK,QAAQ,EAC/B;MACAD,MAAM,CAACC,GAAG,CAAC,GAAGR,cAAc,CAACO,MAAM,CAACC,GAAG,CAAW,CAAC;IACrD;EACF;EACA,OAAOD,MAAM;AACf;AAEA,SAASQ,eAAeA,CACtBC,CAA0B,EAC1BC,CAA0B,EACjB;EACT,MAAMC,IAAI,GAAGC,MAAM,CAACD,IAAI,CAACF,CAAC,CAAC;EAC3B,IAAIE,IAAI,CAACE,MAAM,KAAKD,MAAM,CAACD,IAAI,CAACD,CAAC,CAAC,CAACG,MAAM,EAAE,OAAO,KAAK;EACvD,KAAK,MAAMZ,GAAG,IAAIU,IAAI,EAAE;IACtB,MAAMG,MAAM,GAAGL,CAAC,CAACR,GAAG,CAAC;IACrB,MAAMc,MAAM,GAAGL,CAAC,CAACT,GAAG,CAAC;IACrB,IAAIa,MAAM,KAAKC,MAAM,EAAE;IACvB,IACE,OAAOD,MAAM,KAAK,QAAQ,IAC1BA,MAAM,KAAK,IAAI,IACf,OAAOC,MAAM,KAAK,QAAQ,IAC1BA,MAAM,KAAK,IAAI,EACf;MACA,MAAMC,WAAW,GAAGJ,MAAM,CAACD,IAAI,CAACG,MAAM,CAAC;MACvC,MAAMG,WAAW,GAAGL,MAAM,CAACD,IAAI,CAACI,MAAM,CAAC;MACvC,IAAIC,WAAW,CAACH,MAAM,KAAKI,WAAW,CAACJ,MAAM,EAAE,OAAO,KAAK;MAC3D,KAAK,MAAMK,SAAS,IAAIF,WAAW,EAAE;QACnC,IACGF,MAAM,CAA6BI,SAAS,CAAC,KAC7CH,MAAM,CAA6BG,SAAS,CAAC,EAC9C;UACA,OAAO,KAAK;QACd;MACF;MACA;IACF;IACA,OAAO,KAAK;EACd;EACA,OAAO,IAAI;AACb;AAEA,OAAO,SAASC,YAAYA,CAC1BV,CAAgB,EAChBC,CAAgB,EAChBU,aAAgC,EACvB;EACT,KAAK,MAAMnB,GAAG,IAAImB,aAAa,EAAE;IAC/B,MAAMC,IAAI,GAAGZ,CAAC,CAACR,GAAG,CAAwB;IAC1C,MAAMqB,IAAI,GAAGZ,CAAC,CAACT,GAAG,CAAwB;IAC1C,IAAIoB,IAAI,KAAKC,IAAI,EAAE;IACnB,IAAI,CAACD,IAAI,IAAI,CAACC,IAAI,EAAE,OAAO,KAAK;IAChC,IACE,CAACd,eAAe,CACda,IAAI,EACJC,IACF,CAAC,EACD;MACA,OAAO,KAAK;IACd;EACF;EACA,OAAO,IAAI;AACb","ignoreList":[]} diff --git a/lib/module/types/MarkdownStyle.js b/lib/module/types/MarkdownStyle.js new file mode 100644 index 00000000..57d8d522 --- /dev/null +++ b/lib/module/types/MarkdownStyle.js @@ -0,0 +1,2 @@ +"use strict"; +//# sourceMappingURL=MarkdownStyle.js.map \ No newline at end of file diff --git a/lib/module/types/MarkdownStyle.js.map b/lib/module/types/MarkdownStyle.js.map new file mode 100644 index 00000000..af7e2d76 --- /dev/null +++ b/lib/module/types/MarkdownStyle.js.map @@ -0,0 +1 @@ +{"version":3,"names":[],"sourceRoot":"../../../src","sources":["types/MarkdownStyle.ts"],"mappings":"","ignoreList":[]} diff --git a/lib/module/types/MarkdownStyleInternal.js b/lib/module/types/MarkdownStyleInternal.js new file mode 100644 index 00000000..bf53fb01 --- /dev/null +++ b/lib/module/types/MarkdownStyleInternal.js @@ -0,0 +1,2 @@ +"use strict"; +//# sourceMappingURL=MarkdownStyleInternal.js.map \ No newline at end of file diff --git a/lib/module/types/MarkdownStyleInternal.js.map b/lib/module/types/MarkdownStyleInternal.js.map new file mode 100644 index 00000000..c90a55f2 --- /dev/null +++ b/lib/module/types/MarkdownStyleInternal.js.map @@ -0,0 +1 @@ +{"version":3,"names":[],"sourceRoot":"../../../src","sources":["types/MarkdownStyleInternal.ts"],"mappings":"","ignoreList":[]} diff --git a/lib/module/types/MarkdownTextProps.js b/lib/module/types/MarkdownTextProps.js new file mode 100644 index 00000000..637d6585 --- /dev/null +++ b/lib/module/types/MarkdownTextProps.js @@ -0,0 +1,4 @@ +"use strict"; + +export {}; +//# sourceMappingURL=MarkdownTextProps.js.map \ No newline at end of file diff --git a/lib/module/types/MarkdownTextProps.js.map b/lib/module/types/MarkdownTextProps.js.map new file mode 100644 index 00000000..d01c66b2 --- /dev/null +++ b/lib/module/types/MarkdownTextProps.js.map @@ -0,0 +1 @@ +{"version":3,"names":[],"sourceRoot":"../../../src","sources":["types/MarkdownTextProps.ts"],"mappings":"","ignoreList":[]} diff --git a/lib/module/types/MarkdownTextProps.web.js b/lib/module/types/MarkdownTextProps.web.js new file mode 100644 index 00000000..a36933a4 --- /dev/null +++ b/lib/module/types/MarkdownTextProps.web.js @@ -0,0 +1,4 @@ +"use strict"; + +export {}; +//# sourceMappingURL=MarkdownTextProps.web.js.map \ No newline at end of file diff --git a/lib/module/types/MarkdownTextProps.web.js.map b/lib/module/types/MarkdownTextProps.web.js.map new file mode 100644 index 00000000..86759388 --- /dev/null +++ b/lib/module/types/MarkdownTextProps.web.js.map @@ -0,0 +1 @@ +{"version":3,"names":[],"sourceRoot":"../../../src","sources":["types/MarkdownTextProps.web.ts"],"mappings":"","ignoreList":[]} diff --git a/lib/module/types/events.js b/lib/module/types/events.js new file mode 100644 index 00000000..99f35993 --- /dev/null +++ b/lib/module/types/events.js @@ -0,0 +1,2 @@ +"use strict"; +//# sourceMappingURL=events.js.map \ No newline at end of file diff --git a/lib/module/types/events.js.map b/lib/module/types/events.js.map new file mode 100644 index 00000000..e2cbebbe --- /dev/null +++ b/lib/module/types/events.js.map @@ -0,0 +1 @@ +{"version":3,"names":[],"sourceRoot":"../../../src","sources":["types/events.ts"],"mappings":"","ignoreList":[]} diff --git a/lib/module/utils/regexParser.js b/lib/module/utils/regexParser.js new file mode 100644 index 00000000..6aa8f78d --- /dev/null +++ b/lib/module/utils/regexParser.js @@ -0,0 +1,43 @@ +"use strict"; + +const DISABLED_REGEX = { + pattern: '', + caseInsensitive: false, + dotAll: false, + isDisabled: true, + isDefault: false +}; +const DEFAULT_REGEX = { + pattern: '', + caseInsensitive: false, + dotAll: false, + isDisabled: false, + isDefault: true +}; +export const toNativeRegexConfig = regex => { + if (regex === null) { + return DISABLED_REGEX; + } + if (regex === undefined) { + return DEFAULT_REGEX; + } + const source = regex.source; + const hasLookbehind = source.includes('(?<=') || source.includes('(? { + const normalizedStyle = useMemo(() => normalizeMarkdownStyle(markdownStyle), [markdownStyle]); + const [ast, setAst] = useState(null); + const [katex, setKatex] = useState(null); + const [parseError, setParseError] = useState(false); + const { + underline = false, + latexMath = true + } = md4cFlags; + useEffect(() => { + let cancelled = false; + const katexPromise = latexMath ? loadKaTeX() : Promise.resolve(null); + Promise.all([parseMarkdown(markdown, { + underline, + latexMath + }), katexPromise]).then(([result, katexInstance]) => { + if (!cancelled) { + indexTaskItems(result); + markInlineImages(result); + setParseError(false); + setKatex(katexInstance); + setAst(result); + } + }).catch(error => { + if (!cancelled) { + if (__DEV__) { + console.error('[EnrichedMarkdownText] Parse failed:', error); + } + setParseError(true); + setAst(null); + setKatex(null); + } + }); + return () => { + cancelled = true; + }; + }, [markdown, underline, latexMath]); + const callbacks = useMemo(() => ({ + onLinkPress, + onLinkLongPress, + onTaskListItemPress, + onMentionPress, + onCitationPress + }), [onLinkPress, onLinkLongPress, onTaskListItemPress, onMentionPress, onCitationPress]); + const capabilities = useMemo(() => ({ + katex + }), [katex]); + const lastChildStyle = useMemo(() => allowTrailingMargin ? normalizedStyle : zeroTrailingMargins(normalizedStyle), [normalizedStyle, allowTrailingMargin]); + const styles = useMemo(() => buildStyles(normalizedStyle), [normalizedStyle]); + const lastChildStyles = useMemo(() => buildStyles(lastChildStyle), [lastChildStyle]); + const wrapperStyle = useMemo(() => { + const selectionBgVar = selectionColor ? normalizeColor(String(selectionColor)) : undefined; + return { + display: 'flex', + flexDirection: 'column', + ...containerStyle, + ...(selectable ? undefined : { + userSelect: 'none' + }), + ...(selectionBgVar != null ? { + ['--enrm-selection-bg']: selectionBgVar + } : null) + }; + }, [containerStyle, selectable, selectionColor]); + + // The browser's default copy picks up the text content of the selected + // DOM, which would include citation markers. Citations are reference + // metadata, not prose, so we rewrite the plain-text flavor to elide them + // while keeping the HTML flavor intact for rich-text destinations. + // + // DOM types aren't in the tsconfig lib list, so we narrow through + // locally-scoped interfaces to access only the few APIs we need. + const handleCopy = useCallback(event => { + const globals = globalThis; + const win = globals.window; + const doc = globals.document; + if (!win || !doc) return; + const selection = win.getSelection?.(); + if (!selection || selection.rangeCount === 0) return; + const range = selection.getRangeAt(0); + if (range.collapsed) return; + const container = doc.createElement('div'); + container.appendChild(range.cloneContents()); + for (const node of container.querySelectorAll(`.${CITATION_CLASS}`)) { + node.remove(); + } + const clipboardData = event.clipboardData; + clipboardData.setData('text/plain', container.textContent ?? ''); + clipboardData.setData('text/html', container.innerHTML); + event.preventDefault(); + }, []); + const selectionStyle = selectionColor ? /*#__PURE__*/_jsx("style", { + children: `[data-enriched-markdown-text] ::selection { + background-color: var(--enrm-selection-bg); +}` + }) : null; + if (parseError) { + return /*#__PURE__*/_jsxs(Fragment, { + children: [selectionStyle, /*#__PURE__*/_jsx("div", { + "data-enriched-markdown-text": true, + style: wrapperStyle, + dir: dir, + ...rest, + children: /*#__PURE__*/_jsx("pre", { + style: parseErrorFallbackStyle, + children: markdown + }) + })] + }); + } + if (!ast) return null; + const children = ast.children ?? []; + const lastIdx = children.length - 1; + return /*#__PURE__*/_jsxs(Fragment, { + children: [selectionStyle, /*#__PURE__*/_jsx("div", { + "data-enriched-markdown-text": true, + style: wrapperStyle, + dir: dir, + onCopy: handleCopy, + ...rest, + children: children.map((child, index) => /*#__PURE__*/_jsx(RenderNode, { + node: child, + style: index === lastIdx ? lastChildStyle : normalizedStyle, + styles: index === lastIdx ? lastChildStyles : styles, + callbacks: callbacks, + capabilities: capabilities + }, `${child.type}-${index}`)) + })] + }); +}; +export default EnrichedMarkdownText; +//# sourceMappingURL=EnrichedMarkdownText.js.map \ No newline at end of file diff --git a/lib/module/web/EnrichedMarkdownText.js.map b/lib/module/web/EnrichedMarkdownText.js.map new file mode 100644 index 00000000..f69223fe --- /dev/null +++ b/lib/module/web/EnrichedMarkdownText.js.map @@ -0,0 +1 @@ +{"version":3,"names":["useState","useEffect","useMemo","useCallback","Fragment","normalizeMarkdownStyle","zeroTrailingMargins","parseErrorFallbackStyle","buildStyles","parseMarkdown","RenderNode","CITATION_CLASS","indexTaskItems","markInlineImages","loadKaTeX","normalizeColor","jsx","_jsx","jsxs","_jsxs","EnrichedMarkdownText","markdown","markdownStyle","md4cFlags","onLinkPress","onLinkLongPress","onTaskListItemPress","onMentionPress","onCitationPress","allowTrailingMargin","containerStyle","selectable","dir","selectionColor","rest","normalizedStyle","ast","setAst","katex","setKatex","parseError","setParseError","underline","latexMath","cancelled","katexPromise","Promise","resolve","all","then","result","katexInstance","catch","error","__DEV__","console","callbacks","capabilities","lastChildStyle","styles","lastChildStyles","wrapperStyle","selectionBgVar","String","undefined","display","flexDirection","userSelect","handleCopy","event","globals","globalThis","win","window","doc","document","selection","getSelection","rangeCount","range","getRangeAt","collapsed","container","createElement","appendChild","cloneContents","node","querySelectorAll","remove","clipboardData","setData","textContent","innerHTML","preventDefault","selectionStyle","children","style","lastIdx","length","onCopy","map","child","index","type"],"sourceRoot":"../../../src","sources":["web/EnrichedMarkdownText.tsx"],"mappings":";;AAAA,SACEA,QAAQ,EACRC,SAAS,EACTC,OAAO,EACPC,WAAW,EACXC,QAAQ,QAGH,OAAO;AAEd,SAASC,sBAAsB,QAAQ,kCAA+B;AACtE,SACEC,mBAAmB,EACnBC,uBAAuB,EACvBC,WAAW,QACN,aAAU;AACjB,SAASC,aAAa,QAAQ,oBAAiB;AAC/C,SAASC,UAAU,QAAQ,sBAAa;AACxC,SAASC,cAAc,QAAQ,gCAA6B;AAE5D,SAASC,cAAc,EAAEC,gBAAgB,QAAQ,YAAS;AAC1D,SAASC,SAAS,QAAQ,YAAS;AAEnC,SAASC,cAAc,QAAQ,kBAAe;AAAC,SAAAC,GAAA,IAAAC,IAAA,EAAAC,IAAA,IAAAC,KAAA;AAE/C,OAAO,MAAMC,oBAAoB,GAAGA,CAAC;EACnCC,QAAQ;EACRC,aAAa,GAAG,CAAC,CAAC;EAClBC,SAAS,GAAG,CAAC,CAAC;EACdC,WAAW;EACXC,eAAe;EACfC,mBAAmB;EACnBC,cAAc;EACdC,eAAe;EACfC,mBAAmB,GAAG,KAAK;EAC3BC,cAAc;EACdC,UAAU,GAAG,IAAI;EACjBC,GAAG;EACHC,cAAc;EACd,GAAGC;AACsB,CAAC,KAAK;EAC/B,MAAMC,eAAe,GAAGjC,OAAO,CAC7B,MAAMG,sBAAsB,CAACiB,aAAa,CAAC,EAC3C,CAACA,aAAa,CAChB,CAAC;EAED,MAAM,CAACc,GAAG,EAAEC,MAAM,CAAC,GAAGrC,QAAQ,CAAiB,IAAI,CAAC;EACpD,MAAM,CAACsC,KAAK,EAAEC,QAAQ,CAAC,GAAGvC,QAAQ,CAAuB,IAAI,CAAC;EAC9D,MAAM,CAACwC,UAAU,EAAEC,aAAa,CAAC,GAAGzC,QAAQ,CAAU,KAAK,CAAC;EAE5D,MAAM;IAAE0C,SAAS,GAAG,KAAK;IAAEC,SAAS,GAAG;EAAK,CAAC,GAAGpB,SAAS;EAEzDtB,SAAS,CAAC,MAAM;IACd,IAAI2C,SAAS,GAAG,KAAK;IAErB,MAAMC,YAAY,GAAGF,SAAS,GAAG7B,SAAS,CAAC,CAAC,GAAGgC,OAAO,CAACC,OAAO,CAAC,IAAI,CAAC;IAEpED,OAAO,CAACE,GAAG,CAAC,CACVvC,aAAa,CAACY,QAAQ,EAAE;MAAEqB,SAAS;MAAEC;IAAU,CAAC,CAAC,EACjDE,YAAY,CACb,CAAC,CACCI,IAAI,CAAC,CAAC,CAACC,MAAM,EAAEC,aAAa,CAAC,KAAK;MACjC,IAAI,CAACP,SAAS,EAAE;QACdhC,cAAc,CAACsC,MAAM,CAAC;QACtBrC,gBAAgB,CAACqC,MAAM,CAAC;QAExBT,aAAa,CAAC,KAAK,CAAC;QACpBF,QAAQ,CAACY,aAAa,CAAC;QACvBd,MAAM,CAACa,MAAM,CAAC;MAChB;IACF,CAAC,CAAC,CACDE,KAAK,CAAEC,KAAK,IAAK;MAChB,IAAI,CAACT,SAAS,EAAE;QACd,IAAIU,OAAO,EAAE;UACXC,OAAO,CAACF,KAAK,CAAC,sCAAsC,EAAEA,KAAK,CAAC;QAC9D;QAEAZ,aAAa,CAAC,IAAI,CAAC;QACnBJ,MAAM,CAAC,IAAI,CAAC;QACZE,QAAQ,CAAC,IAAI,CAAC;MAChB;IACF,CAAC,CAAC;IAEJ,OAAO,MAAM;MACXK,SAAS,GAAG,IAAI;IAClB,CAAC;EACH,CAAC,EAAE,CAACvB,QAAQ,EAAEqB,SAAS,EAAEC,SAAS,CAAC,CAAC;EAEpC,MAAMa,SAAS,GAAGtD,OAAO,CACvB,OAAO;IACLsB,WAAW;IACXC,eAAe;IACfC,mBAAmB;IACnBC,cAAc;IACdC;EACF,CAAC,CAAC,EACF,CACEJ,WAAW,EACXC,eAAe,EACfC,mBAAmB,EACnBC,cAAc,EACdC,eAAe,CAEnB,CAAC;EAED,MAAM6B,YAAY,GAAGvD,OAAO,CAAqB,OAAO;IAAEoC;EAAM,CAAC,CAAC,EAAE,CAACA,KAAK,CAAC,CAAC;EAE5E,MAAMoB,cAAc,GAAGxD,OAAO,CAC5B,MACE2B,mBAAmB,GACfM,eAAe,GACf7B,mBAAmB,CAAC6B,eAAe,CAAC,EAC1C,CAACA,eAAe,EAAEN,mBAAmB,CACvC,CAAC;EAED,MAAM8B,MAAM,GAAGzD,OAAO,CAAC,MAAMM,WAAW,CAAC2B,eAAe,CAAC,EAAE,CAACA,eAAe,CAAC,CAAC;EAE7E,MAAMyB,eAAe,GAAG1D,OAAO,CAC7B,MAAMM,WAAW,CAACkD,cAAc,CAAC,EACjC,CAACA,cAAc,CACjB,CAAC;EAED,MAAMG,YAAY,GAAG3D,OAAO,CAAgB,MAAM;IAChD,MAAM4D,cAAc,GAAG7B,cAAc,GACjClB,cAAc,CAACgD,MAAM,CAAC9B,cAAc,CAAC,CAAC,GACtC+B,SAAS;IAEb,OAAO;MACLC,OAAO,EAAE,MAAM;MACfC,aAAa,EAAE,QAAQ;MACvB,GAAIpC,cAAgC;MACpC,IAAIC,UAAU,GAAGiC,SAAS,GAAG;QAAEG,UAAU,EAAE;MAAO,CAAC,CAAC;MACpD,IAAIL,cAAc,IAAI,IAAI,GACrB;QAAE,CAAC,qBAAqB,GAAGA;MAAe,CAAC,GAC5C,IAAI;IACV,CAAC;EACH,CAAC,EAAE,CAAChC,cAAc,EAAEC,UAAU,EAAEE,cAAc,CAAC,CAAC;;EAEhD;EACA;EACA;EACA;EACA;EACA;EACA;EACA,MAAMmC,UAAU,GAAGjE,WAAW,CAAEkE,KAA8B,IAAK;IACjE,MAAMC,OAAO,GAAGC,UAoBf;IAED,MAAMC,GAAG,GAAGF,OAAO,CAACG,MAAM;IAC1B,MAAMC,GAAG,GAAGJ,OAAO,CAACK,QAAQ;IAC5B,IAAI,CAACH,GAAG,IAAI,CAACE,GAAG,EAAE;IAElB,MAAME,SAAS,GAAGJ,GAAG,CAACK,YAAY,GAAG,CAAC;IACtC,IAAI,CAACD,SAAS,IAAIA,SAAS,CAACE,UAAU,KAAK,CAAC,EAAE;IAC9C,MAAMC,KAAK,GAAGH,SAAS,CAACI,UAAU,CAAC,CAAC,CAAC;IACrC,IAAID,KAAK,CAACE,SAAS,EAAE;IAErB,MAAMC,SAAS,GAAGR,GAAG,CAACS,aAAa,CAAC,KAAK,CAAC;IAC1CD,SAAS,CAACE,WAAW,CAACL,KAAK,CAACM,aAAa,CAAC,CAAC,CAAC;IAE5C,KAAK,MAAMC,IAAI,IAAIJ,SAAS,CAACK,gBAAgB,CAAC,IAAI5E,cAAc,EAAE,CAAC,EAAE;MACnE2E,IAAI,CAACE,MAAM,CAAC,CAAC;IACf;IAEA,MAAMC,aAAa,GACjBpB,KAAK,CAGLoB,aAAa;IACfA,aAAa,CAACC,OAAO,CAAC,YAAY,EAAER,SAAS,CAACS,WAAW,IAAI,EAAE,CAAC;IAChEF,aAAa,CAACC,OAAO,CAAC,WAAW,EAAER,SAAS,CAACU,SAAS,CAAC;IACvDvB,KAAK,CAACwB,cAAc,CAAC,CAAC;EACxB,CAAC,EAAE,EAAE,CAAC;EAEN,MAAMC,cAAc,GAAG7D,cAAc,gBACnChB,IAAA;IAAA8E,QAAA,EAAQ;AACZ;AACA;EAAE,CAAQ,CAAC,GACL,IAAI;EAER,IAAIvD,UAAU,EAAE;IACd,oBACErB,KAAA,CAACf,QAAQ;MAAA2F,QAAA,GACND,cAAc,eACf7E,IAAA;QACE,mCAA2B;QAC3B+E,KAAK,EAAEnC,YAAa;QACpB7B,GAAG,EAAEA,GAAI;QAAA,GACLE,IAAI;QAAA6D,QAAA,eAER9E,IAAA;UAAK+E,KAAK,EAAEzF,uBAAwB;UAAAwF,QAAA,EAAE1E;QAAQ,CAAM;MAAC,CAClD,CAAC;IAAA,CACE,CAAC;EAEf;EAEA,IAAI,CAACe,GAAG,EAAE,OAAO,IAAI;EAErB,MAAM2D,QAAQ,GAAG3D,GAAG,CAAC2D,QAAQ,IAAI,EAAE;EACnC,MAAME,OAAO,GAAGF,QAAQ,CAACG,MAAM,GAAG,CAAC;EAEnC,oBACE/E,KAAA,CAACf,QAAQ;IAAA2F,QAAA,GACND,cAAc,eACf7E,IAAA;MACE,mCAA2B;MAC3B+E,KAAK,EAAEnC,YAAa;MACpB7B,GAAG,EAAEA,GAAI;MACTmE,MAAM,EAAE/B,UAAW;MAAA,GACflC,IAAI;MAAA6D,QAAA,EAEPA,QAAQ,CAACK,GAAG,CAAC,CAACC,KAAK,EAAEC,KAAK,kBACzBrF,IAAA,CAACP,UAAU;QAET4E,IAAI,EAAEe,KAAM;QACZL,KAAK,EAAEM,KAAK,KAAKL,OAAO,GAAGvC,cAAc,GAAGvB,eAAgB;QAC5DwB,MAAM,EAAE2C,KAAK,KAAKL,OAAO,GAAGrC,eAAe,GAAGD,MAAO;QACrDH,SAAS,EAAEA,SAAU;QACrBC,YAAY,EAAEA;MAAa,GALtB,GAAG4C,KAAK,CAACE,IAAI,IAAID,KAAK,EAM5B,CACF;IAAC,CACC,CAAC;EAAA,CACE,CAAC;AAEf,CAAC;AAED,eAAelF,oBAAoB","ignoreList":[]} diff --git a/lib/module/web/katex.js b/lib/module/web/katex.js new file mode 100644 index 00000000..b0dbf6ae --- /dev/null +++ b/lib/module/web/katex.js @@ -0,0 +1,22 @@ +"use strict"; + +let katexLoadPromise = null; + +/** Lazily loads KaTeX. Resolves to null if not installed. */ +export function loadKaTeX() { + if (!katexLoadPromise) { + let instance = null; + try { + const mod = require('katex'); + const candidate = mod?.default ?? mod; + if (typeof candidate?.renderToString === 'function') { + instance = candidate; + } + } catch { + // katex not installed — math rendering will be skipped + } + katexLoadPromise = Promise.resolve(instance); + } + return katexLoadPromise; +} +//# sourceMappingURL=katex.js.map \ No newline at end of file diff --git a/lib/module/web/katex.js.map b/lib/module/web/katex.js.map new file mode 100644 index 00000000..c9ba41d0 --- /dev/null +++ b/lib/module/web/katex.js.map @@ -0,0 +1 @@ +{"version":3,"names":["katexLoadPromise","loadKaTeX","instance","mod","require","candidate","default","renderToString","Promise","resolve"],"sourceRoot":"../../../src","sources":["web/katex.ts"],"mappings":";;AAYA,IAAIA,gBAAsD,GAAG,IAAI;;AAEjE;AACA,OAAO,SAASC,SAASA,CAAA,EAAkC;EACzD,IAAI,CAACD,gBAAgB,EAAE;IACrB,IAAIE,QAA8B,GAAG,IAAI;IACzC,IAAI;MACF,MAAMC,GAAG,GAAGC,OAAO,CAAC,OAAO,CAAC;MAC5B,MAAMC,SAAS,GAAIF,GAAG,EAAEG,OAAO,IAAIH,GAA4B;MAC/D,IAAI,OAAOE,SAAS,EAAEE,cAAc,KAAK,UAAU,EAAE;QACnDL,QAAQ,GAAGG,SAAS;MACtB;IACF,CAAC,CAAC,MAAM;MACN;IAAA;IAEFL,gBAAgB,GAAGQ,OAAO,CAACC,OAAO,CAACP,QAAQ,CAAC;EAC9C;EACA,OAAOF,gBAAgB;AACzB","ignoreList":[]} diff --git a/lib/module/web/parseMarkdown.js b/lib/module/web/parseMarkdown.js new file mode 100644 index 00000000..7604c137 --- /dev/null +++ b/lib/module/web/parseMarkdown.js @@ -0,0 +1,32 @@ +"use strict"; + +// Caching the Promise (not the resolved value) means concurrent callers share +// a single WASM initialization — no duplicate loading. +let parserPromise = null; + +// SINGLE_FILE=1 inlines the WASM binary as base64 inside md4c.js, so no +// network fetch is needed — only a one-time decode + compile on first call. +function initializeParser() { + if (!parserPromise) { + parserPromise = import('./wasm/md4c').then(module => module.default()).then(wasmModule => wasmModule.cwrap('parseMarkdown', 'string', ['string', 'number', 'number'])).catch(error => { + parserPromise = null; + throw error; + }); + } + return parserPromise; +} +function isASTNode(value) { + return typeof value === 'object' && value !== null && 'type' in value && typeof value.type === 'string'; +} +export async function parseMarkdown(markdown, { + underline = false, + latexMath = true +} = {}) { + const parse = await initializeParser(); + const result = JSON.parse(parse(markdown, underline ? 1 : 0, latexMath ? 1 : 0)); + if (!isASTNode(result)) { + throw new Error('WASM parser returned invalid AST'); + } + return result; +} +//# sourceMappingURL=parseMarkdown.js.map \ No newline at end of file diff --git a/lib/module/web/parseMarkdown.js.map b/lib/module/web/parseMarkdown.js.map new file mode 100644 index 00000000..d47ca703 --- /dev/null +++ b/lib/module/web/parseMarkdown.js.map @@ -0,0 +1 @@ +{"version":3,"names":["parserPromise","initializeParser","then","module","default","wasmModule","cwrap","catch","error","isASTNode","value","type","parseMarkdown","markdown","underline","latexMath","parse","result","JSON","Error"],"sourceRoot":"../../../src","sources":["web/parseMarkdown.ts"],"mappings":";;AASA;AACA;AACA,IAAIA,aAAsC,GAAG,IAAI;;AAEjD;AACA;AACA,SAASC,gBAAgBA,CAAA,EAAqB;EAC5C,IAAI,CAACD,aAAa,EAAE;IAClBA,aAAa,GAAG,MAAM,CAAC,aAAa,CAAC,CAClCE,IAAI,CAAEC,MAAM,IAAKA,MAAM,CAACC,OAAO,CAAC,CAAC,CAAC,CAClCF,IAAI,CAAEG,UAAU,IACfA,UAAU,CAACC,KAAK,CAAC,eAAe,EAAE,QAAQ,EAAE,CAC1C,QAAQ,EACR,QAAQ,EACR,QAAQ,CACT,CACH,CAAC,CACAC,KAAK,CAAEC,KAAK,IAAK;MAChBR,aAAa,GAAG,IAAI;MACpB,MAAMQ,KAAK;IACb,CAAC,CAAqB;EAC1B;EACA,OAAOR,aAAa;AACtB;AAEA,SAASS,SAASA,CAACC,KAAc,EAAoB;EACnD,OACE,OAAOA,KAAK,KAAK,QAAQ,IACzBA,KAAK,KAAK,IAAI,IACd,MAAM,IAAIA,KAAK,IACf,OAAQA,KAAK,CAAaC,IAAI,KAAK,QAAQ;AAE/C;AAEA,OAAO,eAAeC,aAAaA,CACjCC,QAAgB,EAChB;EAAEC,SAAS,GAAG,KAAK;EAAEC,SAAS,GAAG;AAAgB,CAAC,GAAG,CAAC,CAAC,EACrC;EAClB,MAAMC,KAAK,GAAG,MAAMf,gBAAgB,CAAC,CAAC;EAEtC,MAAMgB,MAAe,GAAGC,IAAI,CAACF,KAAK,CAChCA,KAAK,CAACH,QAAQ,EAAEC,SAAS,GAAG,CAAC,GAAG,CAAC,EAAEC,SAAS,GAAG,CAAC,GAAG,CAAC,CACtD,CAAC;EAED,IAAI,CAACN,SAAS,CAACQ,MAAM,CAAC,EAAE;IACtB,MAAM,IAAIE,KAAK,CAAC,kCAAkC,CAAC;EACrD;EAEA,OAAOF,MAAM;AACf","ignoreList":[]} diff --git a/lib/module/web/renderers/BlockRenderers.js b/lib/module/web/renderers/BlockRenderers.js new file mode 100644 index 00000000..5bdaee98 --- /dev/null +++ b/lib/module/web/renderers/BlockRenderers.js @@ -0,0 +1,116 @@ +"use strict"; + +import { extractNodeText, filenameFromUrl } from "../utils.js"; +import { toHeadingLevel } from "../styles.js"; +import { KaTeXRenderer } from "./KaTeXRenderer.js"; +import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime"; +function ParagraphRenderer({ + node, + styles, + parentType, + renderChildren +}) { + const isImageOnly = node.children?.length === 1 && node.children[0]?.type === 'Image'; + if (isImageOnly) return /*#__PURE__*/_jsx(_Fragment, { + children: renderChildren(node) + }); + if (parentType === 'Blockquote') { + return /*#__PURE__*/_jsx("p", { + style: styles.paragraphInBlockquote, + children: renderChildren(node) + }); + } + if (parentType === 'ListItem') { + return /*#__PURE__*/_jsx("span", { + children: renderChildren(node) + }); + } + return /*#__PURE__*/_jsx("p", { + style: styles.paragraph, + children: renderChildren(node) + }); +} +function HeadingRenderer({ + node, + styles, + renderChildren +}) { + const Tag = toHeadingLevel(node.attributes?.level ?? '1'); + return /*#__PURE__*/_jsx(Tag, { + style: styles[Tag], + children: renderChildren(node) + }); +} +function BlockquoteRenderer({ + node, + styles, + renderChildren +}) { + return /*#__PURE__*/_jsx("blockquote", { + style: styles.blockquote, + children: renderChildren(node) + }); +} +function CodeBlockRenderer({ + node, + styles, + renderChildren +}) { + const language = node.attributes?.language; + const label = language ? `Code block: ${language}` : 'Code block'; + return /*#__PURE__*/_jsx("pre", { + style: styles.codeBlock, + "aria-label": label, + children: /*#__PURE__*/_jsx("code", { + style: styles.codeBlockFont, + children: renderChildren(node) + }) + }); +} +function ThematicBreakRenderer({ + styles +}) { + return /*#__PURE__*/_jsx("hr", { + style: styles.thematicBreak + }); +} +function ImageRenderer({ + node, + styles +}) { + const url = node.attributes?.url; + if (!url) return null; + const title = node.attributes?.title; + const alt = extractNodeText(node) || title || filenameFromUrl(url) || 'Image'; + const imgStyle = node.attributes?.isInline ? styles.inlineImage : styles.image; + return /*#__PURE__*/_jsx("img", { + src: url, + alt: alt, + title: title, + style: imgStyle + }); +} +function LatexMathDisplayRenderer({ + node, + styles, + capabilities +}) { + const content = extractNodeText(node); + return /*#__PURE__*/_jsx(KaTeXRenderer, { + content: content, + katex: capabilities.katex, + displayMode: true, + style: styles.mathDisplay, + fallbackTag: "pre" + }); +} +export const blockRenderers = { + Paragraph: ParagraphRenderer, + Heading: HeadingRenderer, + Blockquote: BlockquoteRenderer, + CodeBlock: CodeBlockRenderer, + ThematicBreak: ThematicBreakRenderer, + Image: ImageRenderer, + LatexMathDisplay: LatexMathDisplayRenderer +}; +//# sourceMappingURL=BlockRenderers.js.map \ No newline at end of file diff --git a/lib/module/web/renderers/BlockRenderers.js.map b/lib/module/web/renderers/BlockRenderers.js.map new file mode 100644 index 00000000..fb4b64ff --- /dev/null +++ b/lib/module/web/renderers/BlockRenderers.js.map @@ -0,0 +1 @@ +{"version":3,"names":["extractNodeText","filenameFromUrl","toHeadingLevel","KaTeXRenderer","Fragment","_Fragment","jsx","_jsx","ParagraphRenderer","node","styles","parentType","renderChildren","isImageOnly","children","length","type","style","paragraphInBlockquote","paragraph","HeadingRenderer","Tag","attributes","level","BlockquoteRenderer","blockquote","CodeBlockRenderer","language","label","codeBlock","codeBlockFont","ThematicBreakRenderer","thematicBreak","ImageRenderer","url","title","alt","imgStyle","isInline","inlineImage","image","src","LatexMathDisplayRenderer","capabilities","content","katex","displayMode","mathDisplay","fallbackTag","blockRenderers","Paragraph","Heading","Blockquote","CodeBlock","ThematicBreak","Image","LatexMathDisplay"],"sourceRoot":"../../../../src","sources":["web/renderers/BlockRenderers.tsx"],"mappings":";;AAAA,SAASA,eAAe,EAAEC,eAAe,QAAQ,aAAU;AAE3D,SAASC,cAAc,QAAQ,cAAW;AAC1C,SAASC,aAAa,QAAQ,oBAAiB;AAAC,SAAAC,QAAA,IAAAC,SAAA,EAAAC,GAAA,IAAAC,IAAA;AAEhD,SAASC,iBAAiBA,CAAC;EACzBC,IAAI;EACJC,MAAM;EACNC,UAAU;EACVC;AACa,CAAC,EAAE;EAChB,MAAMC,WAAW,GACfJ,IAAI,CAACK,QAAQ,EAAEC,MAAM,KAAK,CAAC,IAAIN,IAAI,CAACK,QAAQ,CAAC,CAAC,CAAC,EAAEE,IAAI,KAAK,OAAO;EACnE,IAAIH,WAAW,EAAE,oBAAON,IAAA,CAAAF,SAAA;IAAAS,QAAA,EAAGF,cAAc,CAACH,IAAI;EAAC,CAAG,CAAC;EAEnD,IAAIE,UAAU,KAAK,YAAY,EAAE;IAC/B,oBAAOJ,IAAA;MAAGU,KAAK,EAAEP,MAAM,CAACQ,qBAAsB;MAAAJ,QAAA,EAAEF,cAAc,CAACH,IAAI;IAAC,CAAI,CAAC;EAC3E;EAEA,IAAIE,UAAU,KAAK,UAAU,EAAE;IAC7B,oBAAOJ,IAAA;MAAAO,QAAA,EAAOF,cAAc,CAACH,IAAI;IAAC,CAAO,CAAC;EAC5C;EAEA,oBAAOF,IAAA;IAAGU,KAAK,EAAEP,MAAM,CAACS,SAAU;IAAAL,QAAA,EAAEF,cAAc,CAACH,IAAI;EAAC,CAAI,CAAC;AAC/D;AAEA,SAASW,eAAeA,CAAC;EAAEX,IAAI;EAAEC,MAAM;EAAEE;AAA8B,CAAC,EAAE;EACxE,MAAMS,GAAG,GAAGnB,cAAc,CAACO,IAAI,CAACa,UAAU,EAAEC,KAAK,IAAI,GAAG,CAAC;EACzD,oBAAOhB,IAAA,CAACc,GAAG;IAACJ,KAAK,EAAEP,MAAM,CAACW,GAAG,CAAE;IAAAP,QAAA,EAAEF,cAAc,CAACH,IAAI;EAAC,CAAM,CAAC;AAC9D;AAEA,SAASe,kBAAkBA,CAAC;EAAEf,IAAI;EAAEC,MAAM;EAAEE;AAA8B,CAAC,EAAE;EAC3E,oBACEL,IAAA;IAAYU,KAAK,EAAEP,MAAM,CAACe,UAAW;IAAAX,QAAA,EAAEF,cAAc,CAACH,IAAI;EAAC,CAAa,CAAC;AAE7E;AAEA,SAASiB,iBAAiBA,CAAC;EAAEjB,IAAI;EAAEC,MAAM;EAAEE;AAA8B,CAAC,EAAE;EAC1E,MAAMe,QAAQ,GAAGlB,IAAI,CAACa,UAAU,EAAEK,QAAQ;EAC1C,MAAMC,KAAK,GAAGD,QAAQ,GAAG,eAAeA,QAAQ,EAAE,GAAG,YAAY;EAEjE,oBACEpB,IAAA;IAAKU,KAAK,EAAEP,MAAM,CAACmB,SAAU;IAAC,cAAYD,KAAM;IAAAd,QAAA,eAC9CP,IAAA;MAAMU,KAAK,EAAEP,MAAM,CAACoB,aAAc;MAAAhB,QAAA,EAAEF,cAAc,CAACH,IAAI;IAAC,CAAO;EAAC,CAC7D,CAAC;AAEV;AAEA,SAASsB,qBAAqBA,CAAC;EAAErB;AAAsB,CAAC,EAAE;EACxD,oBAAOH,IAAA;IAAIU,KAAK,EAAEP,MAAM,CAACsB;EAAc,CAAE,CAAC;AAC5C;AAEA,SAASC,aAAaA,CAAC;EAAExB,IAAI;EAAEC;AAAsB,CAAC,EAAE;EACtD,MAAMwB,GAAG,GAAGzB,IAAI,CAACa,UAAU,EAAEY,GAAG;EAChC,IAAI,CAACA,GAAG,EAAE,OAAO,IAAI;EAErB,MAAMC,KAAK,GAAG1B,IAAI,CAACa,UAAU,EAAEa,KAAK;EACpC,MAAMC,GAAG,GAAGpC,eAAe,CAACS,IAAI,CAAC,IAAI0B,KAAK,IAAIlC,eAAe,CAACiC,GAAG,CAAC,IAAI,OAAO;EAC7E,MAAMG,QAAQ,GAAG5B,IAAI,CAACa,UAAU,EAAEgB,QAAQ,GACtC5B,MAAM,CAAC6B,WAAW,GAClB7B,MAAM,CAAC8B,KAAK;EAChB,oBAAOjC,IAAA;IAAKkC,GAAG,EAAEP,GAAI;IAACE,GAAG,EAAEA,GAAI;IAACD,KAAK,EAAEA,KAAM;IAAClB,KAAK,EAAEoB;EAAS,CAAE,CAAC;AACnE;AAEA,SAASK,wBAAwBA,CAAC;EAChCjC,IAAI;EACJC,MAAM;EACNiC;AACa,CAAC,EAAE;EAChB,MAAMC,OAAO,GAAG5C,eAAe,CAACS,IAAI,CAAC;EAErC,oBACEF,IAAA,CAACJ,aAAa;IACZyC,OAAO,EAAEA,OAAQ;IACjBC,KAAK,EAAEF,YAAY,CAACE,KAAM;IAC1BC,WAAW;IACX7B,KAAK,EAAEP,MAAM,CAACqC,WAAY;IAC1BC,WAAW,EAAC;EAAK,CAClB,CAAC;AAEN;AAEA,OAAO,MAAMC,cAA2B,GAAG;EACzCC,SAAS,EAAE1C,iBAAiB;EAC5B2C,OAAO,EAAE/B,eAAe;EACxBgC,UAAU,EAAE5B,kBAAkB;EAC9B6B,SAAS,EAAE3B,iBAAiB;EAC5B4B,aAAa,EAAEvB,qBAAqB;EACpCwB,KAAK,EAAEtB,aAAa;EACpBuB,gBAAgB,EAAEd;AACpB,CAAC","ignoreList":[]} diff --git a/lib/module/web/renderers/InlineRenderers.js b/lib/module/web/renderers/InlineRenderers.js new file mode 100644 index 00000000..ff63830d --- /dev/null +++ b/lib/module/web/renderers/InlineRenderers.js @@ -0,0 +1,214 @@ +"use strict"; + +import { extractNodeText } from "../utils.js"; +import { KaTeXRenderer } from "./KaTeXRenderer.js"; +import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; +const MENTION_SCHEME = 'mention://'; +const CITATION_SCHEME = 'citation://'; +const MENTION_CLASS = 'enriched-mention'; +export const CITATION_CLASS = 'enriched-citation'; + +// The `:active` rule honors the consumer-configured `pressedOpacity` via a CSS +// variable the mention span sets inline. Rendered as a `