react-native-jet-markdown 0.2.0-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/JetMarkdown.podspec +42 -0
  2. package/LICENSE +20 -0
  3. package/README.md +247 -0
  4. package/android/CMakeLists.txt +52 -0
  5. package/android/build.gradle +55 -0
  6. package/android/src/main/AndroidManifest.xml +2 -0
  7. package/android/src/main/cpp/JniBindings.cpp +243 -0
  8. package/android/src/main/java/com/jetmarkdown/JetMarkdownNative.kt +114 -0
  9. package/android/src/main/java/com/jetmarkdown/JetMarkdownPackage.kt +22 -0
  10. package/android/src/main/java/com/jetmarkdown/JetMarkdownView.kt +235 -0
  11. package/android/src/main/java/com/jetmarkdown/JetMarkdownViewManager.kt +75 -0
  12. package/android/src/main/java/com/jetmarkdown/editor/EditorSpans.kt +217 -0
  13. package/android/src/main/java/com/jetmarkdown/editor/JetMarkdownEditorManager.kt +224 -0
  14. package/android/src/main/java/com/jetmarkdown/editor/JetMarkdownEditorView.kt +1736 -0
  15. package/android/src/main/java/com/jetmarkdown/measure/MarkdownMeasurer.kt +54 -0
  16. package/android/src/main/java/com/jetmarkdown/parser/AstDecoder.kt +59 -0
  17. package/android/src/main/java/com/jetmarkdown/parser/MdNode.kt +43 -0
  18. package/android/src/main/java/com/jetmarkdown/render/ContentCache.kt +53 -0
  19. package/android/src/main/java/com/jetmarkdown/render/RenderedContent.kt +318 -0
  20. package/android/src/main/java/com/jetmarkdown/render/SpannableRenderer.kt +658 -0
  21. package/android/src/main/java/com/jetmarkdown/render/spans/ChipSpan.kt +17 -0
  22. package/android/src/main/java/com/jetmarkdown/render/spans/InteractiveSpans.kt +11 -0
  23. package/android/src/main/java/com/jetmarkdown/render/spans/MarkdownLineHeightSpan.kt +59 -0
  24. package/android/src/main/java/com/jetmarkdown/render/spans/RunSpan.kt +40 -0
  25. package/android/src/main/java/com/jetmarkdown/style/Fonts.kt +34 -0
  26. package/android/src/main/java/com/jetmarkdown/style/LayoutStyleSpec.kt +71 -0
  27. package/android/src/main/java/com/jetmarkdown/style/PlatformColorResolver.kt +77 -0
  28. package/android/src/main/java/com/jetmarkdown/style/StyleConfig.kt +80 -0
  29. package/android/src/main/java/com/jetmarkdown/style/TextStyleSpec.kt +61 -0
  30. package/android/src/main/java/com/jetmarkdown/views/BlockStackView.kt +292 -0
  31. package/android/src/main/java/com/jetmarkdown/views/BlockTextView.kt +378 -0
  32. package/android/src/main/java/com/jetmarkdown/views/BoxDrawing.kt +50 -0
  33. package/android/src/main/java/com/jetmarkdown/views/MarkdownHost.kt +12 -0
  34. package/android/src/main/java/com/jetmarkdown/views/MarkdownImageView.kt +113 -0
  35. package/android/src/main/java/com/jetmarkdown/views/NestedHorizontalScrollView.kt +87 -0
  36. package/android/src/main/java/com/jetmarkdown/views/TableBlockView.kt +139 -0
  37. package/cpp/core/Ast.h +85 -0
  38. package/cpp/core/AstJson.cpp +129 -0
  39. package/cpp/core/AstJson.h +12 -0
  40. package/cpp/core/AstSerializer.cpp +40 -0
  41. package/cpp/core/AstSerializer.h +21 -0
  42. package/cpp/core/AstToMarkdown.cpp +549 -0
  43. package/cpp/core/AstToMarkdown.h +16 -0
  44. package/cpp/core/EditorRuns.cpp +640 -0
  45. package/cpp/core/EditorRuns.h +94 -0
  46. package/cpp/core/EditorText.cpp +15 -0
  47. package/cpp/core/EditorText.h +18 -0
  48. package/cpp/core/InlineExtensions.cpp +305 -0
  49. package/cpp/core/InlineExtensions.h +17 -0
  50. package/cpp/core/Parser.cpp +470 -0
  51. package/cpp/core/Parser.h +16 -0
  52. package/cpp/core/Preprocess.cpp +181 -0
  53. package/cpp/core/Preprocess.h +14 -0
  54. package/cpp/md4c/LICENSE +22 -0
  55. package/cpp/md4c/VERSION +1 -0
  56. package/cpp/md4c/md4c.c +6462 -0
  57. package/cpp/md4c/md4c.h +407 -0
  58. package/cpp/react/JetMarkdownEditorShadowNode.cpp +54 -0
  59. package/cpp/react/JetMarkdownEditorShadowNode.h +55 -0
  60. package/cpp/react/JetMarkdownEditorState.h +35 -0
  61. package/cpp/react/JetMarkdownMeasurer.cpp +32 -0
  62. package/cpp/react/JetMarkdownMeasurer.h +41 -0
  63. package/cpp/react/JetMarkdownShadowNode.cpp +100 -0
  64. package/cpp/react/JetMarkdownShadowNode.h +53 -0
  65. package/cpp/react/JetMarkdownState.h +52 -0
  66. package/cpp/react/override/react/renderer/components/JetMarkdownViewSpec/ComponentDescriptors.h +21 -0
  67. package/cpp/tests/parser_tests.cpp +662 -0
  68. package/cpp/tests/run_tests.sh +25 -0
  69. package/ios/JetMarkdownView.h +14 -0
  70. package/ios/JetMarkdownView.mm +468 -0
  71. package/ios/editor/JetMarkdownEditor.h +12 -0
  72. package/ios/editor/JetMarkdownEditor.mm +2002 -0
  73. package/ios/measure/JMDMarkdownMeasurer.h +22 -0
  74. package/ios/measure/JMDMarkdownMeasurer.mm +38 -0
  75. package/ios/render/JMDBlock.h +97 -0
  76. package/ios/render/JMDBlock.m +28 -0
  77. package/ios/render/JMDBlockRenderer.h +17 -0
  78. package/ios/render/JMDBlockRenderer.mm +852 -0
  79. package/ios/render/JMDContentCache.h +17 -0
  80. package/ios/render/JMDContentCache.mm +42 -0
  81. package/ios/render/JMDRenderedContent.h +35 -0
  82. package/ios/render/JMDRenderedContent.mm +274 -0
  83. package/ios/style/JMDFontScale.h +33 -0
  84. package/ios/style/JMDLayoutStyle.h +37 -0
  85. package/ios/style/JMDLayoutStyle.mm +68 -0
  86. package/ios/style/JMDStyleConfig.h +39 -0
  87. package/ios/style/JMDStyleConfig.mm +111 -0
  88. package/ios/style/JMDTextStyle.h +25 -0
  89. package/ios/style/JMDTextStyle.mm +68 -0
  90. package/ios/views/JMDBlockStackView.h +18 -0
  91. package/ios/views/JMDBlockStackView.m +97 -0
  92. package/ios/views/JMDBlockTextView.h +24 -0
  93. package/ios/views/JMDBlockTextView.m +340 -0
  94. package/ios/views/JMDBoxViews.h +33 -0
  95. package/ios/views/JMDBoxViews.m +292 -0
  96. package/ios/views/JMDImageView.h +19 -0
  97. package/ios/views/JMDImageView.m +68 -0
  98. package/ios/views/JMDMarkdownHost.h +16 -0
  99. package/ios/views/JMDTableView.h +16 -0
  100. package/ios/views/JMDTableView.m +172 -0
  101. package/lib/module/JetMarkdownEditor.js +85 -0
  102. package/lib/module/JetMarkdownEditor.js.map +1 -0
  103. package/lib/module/JetMarkdownEditor.native.js +244 -0
  104. package/lib/module/JetMarkdownEditor.native.js.map +1 -0
  105. package/lib/module/JetMarkdownEditorNativeComponent.ts +173 -0
  106. package/lib/module/JetMarkdownView.js +19 -0
  107. package/lib/module/JetMarkdownView.js.map +1 -0
  108. package/lib/module/JetMarkdownView.native.js +44 -0
  109. package/lib/module/JetMarkdownView.native.js.map +1 -0
  110. package/lib/module/JetMarkdownViewNativeComponent.ts +35 -0
  111. package/lib/module/defaultStyles.js +122 -0
  112. package/lib/module/defaultStyles.js.map +1 -0
  113. package/lib/module/index.js +9 -0
  114. package/lib/module/index.js.map +1 -0
  115. package/lib/module/package.json +1 -0
  116. package/lib/module/serializeStyles.js +259 -0
  117. package/lib/module/serializeStyles.js.map +1 -0
  118. package/lib/module/types.js +4 -0
  119. package/lib/module/types.js.map +1 -0
  120. package/lib/module/useJetMarkdownEditor.js +41 -0
  121. package/lib/module/useJetMarkdownEditor.js.map +1 -0
  122. package/lib/typescript/package.json +1 -0
  123. package/lib/typescript/src/JetMarkdownEditor.d.ts +6 -0
  124. package/lib/typescript/src/JetMarkdownEditor.d.ts.map +1 -0
  125. package/lib/typescript/src/JetMarkdownEditor.native.d.ts +6 -0
  126. package/lib/typescript/src/JetMarkdownEditor.native.d.ts.map +1 -0
  127. package/lib/typescript/src/JetMarkdownEditorNativeComponent.d.ts +105 -0
  128. package/lib/typescript/src/JetMarkdownEditorNativeComponent.d.ts.map +1 -0
  129. package/lib/typescript/src/JetMarkdownView.d.ts +3 -0
  130. package/lib/typescript/src/JetMarkdownView.d.ts.map +1 -0
  131. package/lib/typescript/src/JetMarkdownView.native.d.ts +3 -0
  132. package/lib/typescript/src/JetMarkdownView.native.d.ts.map +1 -0
  133. package/lib/typescript/src/JetMarkdownViewNativeComponent.d.ts +28 -0
  134. package/lib/typescript/src/JetMarkdownViewNativeComponent.d.ts.map +1 -0
  135. package/lib/typescript/src/defaultStyles.d.ts +20 -0
  136. package/lib/typescript/src/defaultStyles.d.ts.map +1 -0
  137. package/lib/typescript/src/index.d.ts +7 -0
  138. package/lib/typescript/src/index.d.ts.map +1 -0
  139. package/lib/typescript/src/serializeStyles.d.ts +34 -0
  140. package/lib/typescript/src/serializeStyles.d.ts.map +1 -0
  141. package/lib/typescript/src/types.d.ts +352 -0
  142. package/lib/typescript/src/types.d.ts.map +1 -0
  143. package/lib/typescript/src/useJetMarkdownEditor.d.ts +36 -0
  144. package/lib/typescript/src/useJetMarkdownEditor.d.ts.map +1 -0
  145. package/package.json +150 -0
  146. package/react-native.config.js +11 -0
  147. package/src/JetMarkdownEditor.native.tsx +301 -0
  148. package/src/JetMarkdownEditor.tsx +90 -0
  149. package/src/JetMarkdownEditorNativeComponent.ts +173 -0
  150. package/src/JetMarkdownView.native.tsx +67 -0
  151. package/src/JetMarkdownView.tsx +16 -0
  152. package/src/JetMarkdownViewNativeComponent.ts +35 -0
  153. package/src/defaultStyles.ts +102 -0
  154. package/src/index.tsx +34 -0
  155. package/src/serializeStyles.ts +369 -0
  156. package/src/types.ts +438 -0
  157. package/src/useJetMarkdownEditor.ts +50 -0
@@ -0,0 +1,102 @@
1
+ import type { MarkdownStyles } from "./types";
2
+
3
+ /**
4
+ * The classic markdown look. The viewer renders fully plain when `styles`
5
+ * is omitted — pass `defaultStyles` for this look as-is, or
6
+ * `mergeStyles({...})` to override parts of it. Tune freely: this object is
7
+ * the single source of truth for the default appearance.
8
+ *
9
+ * Only semantics stay native and are not represented here: bold/italic/
10
+ * strikethrough runs, the monospace font family for code, list markers,
11
+ * table header-row bold, and the spoiler cover fallback.
12
+ */
13
+ export const defaultStyles: MarkdownStyles = {
14
+ blockQuote: {
15
+ borderLeftColor: "rgba(0, 0, 0, 0.2)",
16
+ borderLeftWidth: 3,
17
+ paddingLeft: 12,
18
+ },
19
+ codeBlock: {
20
+ backgroundColor: "rgba(0, 0, 0, 0.08)",
21
+ borderRadius: 6,
22
+ fontSize: 14,
23
+ padding: 12,
24
+ },
25
+ divider: {
26
+ color: "rgba(0, 0, 0, 0.13)",
27
+ height: 1,
28
+ },
29
+ gap: 12,
30
+ headings: {
31
+ h1: { fontSize: 32, fontWeight: "700" },
32
+ h2: { fontSize: 26, fontWeight: "700" },
33
+ h3: { fontSize: 22, fontWeight: "700" },
34
+ h4: { fontSize: 18, fontWeight: "700" },
35
+ h5: { fontSize: 16, fontWeight: "700" },
36
+ h6: { fontSize: 14, fontWeight: "700" },
37
+ },
38
+ image: {
39
+ backgroundColor: "rgba(0, 0, 0, 0.08)",
40
+ },
41
+ inlineCode: {
42
+ backgroundColor: "rgba(0, 0, 0, 0.08)",
43
+ },
44
+ link: {
45
+ color: "#007AFF",
46
+ },
47
+ listMarker: {
48
+ width: 24,
49
+ },
50
+ spoiler: {
51
+ backgroundColor: "#3F3F46",
52
+ borderRadius: 4,
53
+ },
54
+ table: {
55
+ maxColumnWidth: 320,
56
+ minColumnWidth: 44,
57
+ },
58
+ tableCell: {
59
+ padding: 8,
60
+ },
61
+ tableRow: {
62
+ borderBottomColor: "rgba(0, 0, 0, 0.12)",
63
+ borderBottomWidth: 1,
64
+ },
65
+ };
66
+
67
+ /**
68
+ * Deep-merges the given overrides over {@link defaultStyles}: element
69
+ * sections merge key-by-key, heading levels merge individually, and
70
+ * anything not overridden keeps its default. Hoist the result to module
71
+ * scope (or memoize) like any other `styles` value.
72
+ */
73
+ export function mergeStyles(overrides: MarkdownStyles = {}): MarkdownStyles {
74
+ const merged: Record<string, unknown> = { ...defaultStyles };
75
+ for (const [key, value] of Object.entries(overrides)) {
76
+ if (value == null) {
77
+ continue;
78
+ }
79
+ const base = (defaultStyles as Record<string, unknown>)[key];
80
+ if (key === "headings") {
81
+ const levels: Record<string, unknown> = {
82
+ ...defaultStyles.headings,
83
+ };
84
+ for (const [level, style] of Object.entries(value)) {
85
+ levels[level] = {
86
+ ...(levels[level] as object | undefined),
87
+ ...(style as object),
88
+ };
89
+ }
90
+ merged.headings = levels;
91
+ } else if (
92
+ base != null &&
93
+ typeof base === "object" &&
94
+ typeof value === "object"
95
+ ) {
96
+ merged[key] = { ...base, ...value };
97
+ } else {
98
+ merged[key] = value;
99
+ }
100
+ }
101
+ return merged as MarkdownStyles;
102
+ }
package/src/index.tsx ADDED
@@ -0,0 +1,34 @@
1
+ /** biome-ignore-all lint/performance/noBarrelFile: go away */
2
+
3
+ export { defaultStyles, mergeStyles } from "./defaultStyles";
4
+ export { JetMarkdownEditor } from "./JetMarkdownEditor";
5
+ export { JetMarkdownView } from "./JetMarkdownView";
6
+ export type {
7
+ FontVariant,
8
+ FontWeight,
9
+ JetMarkdownEditorProps,
10
+ JetMarkdownEditorRef,
11
+ JetMarkdownViewProps,
12
+ MarkdownContainerStyle,
13
+ MarkdownDividerStyle,
14
+ MarkdownEditorState,
15
+ MarkdownHeadingLevel,
16
+ MarkdownImageData,
17
+ MarkdownImageEvent,
18
+ MarkdownImageStyle,
19
+ MarkdownInlineCodeStyle,
20
+ MarkdownLayoutStyle,
21
+ MarkdownListMarkerStyle,
22
+ MarkdownListStyle,
23
+ MarkdownMentionEvent,
24
+ MarkdownMentionQueryEvent,
25
+ MarkdownMentionStyle,
26
+ MarkdownPasteEvent,
27
+ MarkdownSelection,
28
+ MarkdownSpoilerStyle,
29
+ MarkdownStyles,
30
+ MarkdownTableStyle,
31
+ MarkdownTextStyle,
32
+ MarkdownUrlEvent,
33
+ } from "./types";
34
+ export { useJetMarkdownEditor } from "./useJetMarkdownEditor";
@@ -0,0 +1,369 @@
1
+ import {
2
+ type ColorValue,
3
+ processColor,
4
+ type StyleProp,
5
+ StyleSheet,
6
+ } from "react-native";
7
+
8
+ import type {
9
+ MarkdownContainerStyle,
10
+ MarkdownLayoutStyle,
11
+ MarkdownStyles,
12
+ MarkdownTextStyle,
13
+ } from "./types";
14
+
15
+ type Serialized = Record<string, unknown>;
16
+
17
+ const MAIN_STYLE_KEYS = [
18
+ "backgroundColor",
19
+ "padding",
20
+ "paddingHorizontal",
21
+ "paddingVertical",
22
+ "paddingLeft",
23
+ "paddingRight",
24
+ "paddingTop",
25
+ "paddingBottom",
26
+ "gap",
27
+ // Base text styles: cascade into every text element via stylesJson.
28
+ "fontSize",
29
+ "fontWeight",
30
+ "fontFamily",
31
+ "lineHeight",
32
+ "color",
33
+ "fontVariant",
34
+ "textDecorationColor",
35
+ "textDecorationLine",
36
+ "textDecorationStyle",
37
+ ] as const;
38
+
39
+ /**
40
+ * Splits the container `style` prop: content-affecting keys are measured
41
+ * natively so they travel in stylesJson (`main`); anything else passes
42
+ * through to the host view.
43
+ */
44
+ export function splitContainerStyle(style: StyleProp<MarkdownContainerStyle>): {
45
+ hostStyle: Record<string, unknown>;
46
+ main: MainStyle;
47
+ } {
48
+ const flattened = StyleSheet.flatten(style) ?? {};
49
+ const main: MainStyle = {};
50
+ const hostStyle: Record<string, unknown> = {};
51
+ for (const [key, value] of Object.entries(flattened)) {
52
+ if ((MAIN_STYLE_KEYS as readonly string[]).includes(key)) {
53
+ main[key as keyof MainStyle] = value as never;
54
+ } else {
55
+ hostStyle[key] = value;
56
+ }
57
+ }
58
+ return { hostStyle, main };
59
+ }
60
+
61
+ /**
62
+ * Main container style extracted from the `style` prop; these keys affect
63
+ * native content layout (and measurement), so they ride along in stylesJson.
64
+ * Text keys become the `base` section: the root of the text-style cascade.
65
+ */
66
+ export interface MainStyle extends MarkdownTextStyle {
67
+ backgroundColor?: ColorValue;
68
+ gap?: number;
69
+ padding?: number;
70
+ paddingBottom?: number;
71
+ paddingHorizontal?: number;
72
+ paddingLeft?: number;
73
+ paddingRight?: number;
74
+ paddingTop?: number;
75
+ paddingVertical?: number;
76
+ }
77
+
78
+ function put(out: Serialized, key: string, value: unknown): void {
79
+ if (value !== undefined && value !== null) {
80
+ out[key] = value;
81
+ }
82
+ }
83
+
84
+ function putColor(
85
+ out: Serialized,
86
+ key: string,
87
+ value: ColorValue | undefined
88
+ ): void {
89
+ if (value === undefined || value === null) {
90
+ return;
91
+ }
92
+ const processed = processColor(value);
93
+ if (typeof processed === "number") {
94
+ // The Int32 JSON contract wants signed 32-bit; iOS processColor returns
95
+ // unsigned.
96
+ out[key] = processed | 0;
97
+ } else if (typeof processed === "object" && processed !== null) {
98
+ // Platform colors (PlatformColor / DynamicColorIOS): processColor
99
+ // returns a JSON-serializable descriptor ({semantic}/{dynamic} on iOS,
100
+ // {resource_paths} on Android) that the native style parser resolves.
101
+ out[key] = processed;
102
+ }
103
+ }
104
+
105
+ // Expands `padding` shorthands into explicit sides so native code never
106
+ // sees shorthand. Precedence: side > axis (Horizontal/Vertical) > padding.
107
+ function putPadding(
108
+ out: Serialized,
109
+ style: {
110
+ padding?: number;
111
+ paddingBottom?: number;
112
+ paddingHorizontal?: number;
113
+ paddingLeft?: number;
114
+ paddingRight?: number;
115
+ paddingTop?: number;
116
+ paddingVertical?: number;
117
+ },
118
+ sides: ReadonlyArray<"Left" | "Right" | "Top" | "Bottom">
119
+ ): void {
120
+ const axis = {
121
+ Bottom: style.paddingVertical,
122
+ Left: style.paddingHorizontal,
123
+ Right: style.paddingHorizontal,
124
+ Top: style.paddingVertical,
125
+ } as const;
126
+ for (const side of sides) {
127
+ put(
128
+ out,
129
+ `padding${side}`,
130
+ style[`padding${side}`] ?? axis[side] ?? style.padding
131
+ );
132
+ }
133
+ }
134
+
135
+ function serializeText(
136
+ style: MarkdownTextStyle | undefined
137
+ ): Serialized | undefined {
138
+ if (style == null) {
139
+ return;
140
+ }
141
+ const out: Serialized = {};
142
+ put(out, "fontSize", style.fontSize);
143
+ if (style.fontWeight !== undefined) {
144
+ out.fontWeight = String(style.fontWeight);
145
+ }
146
+ put(out, "fontFamily", style.fontFamily);
147
+ put(out, "lineHeight", style.lineHeight);
148
+ putColor(out, "backgroundColor", style.backgroundColor);
149
+ putColor(out, "color", style.color);
150
+ put(out, "fontVariant", style.fontVariant);
151
+ putColor(out, "textDecorationColor", style.textDecorationColor);
152
+ put(out, "textDecorationLine", style.textDecorationLine);
153
+ put(out, "textDecorationStyle", style.textDecorationStyle);
154
+ return Object.keys(out).length > 0 ? out : undefined;
155
+ }
156
+
157
+ function serializeLayout(
158
+ style: MarkdownLayoutStyle | undefined
159
+ ): Serialized | undefined {
160
+ if (style == null) {
161
+ return;
162
+ }
163
+ const out: Serialized = {};
164
+ putColor(out, "backgroundColor", style.backgroundColor);
165
+ putPadding(out, style, ["Left", "Right", "Top", "Bottom"]);
166
+ put(out, "borderRadius", style.borderRadius);
167
+ put(out, "borderCurve", style.borderCurve);
168
+ for (const side of ["Left", "Right", "Top", "Bottom"] as const) {
169
+ putColor(
170
+ out,
171
+ `border${side}Color`,
172
+ style[`border${side}Color`] ?? style.borderColor
173
+ );
174
+ put(
175
+ out,
176
+ `border${side}Width`,
177
+ style[`border${side}Width`] ?? style.borderWidth
178
+ );
179
+ }
180
+ return Object.keys(out).length > 0 ? out : undefined;
181
+ }
182
+
183
+ function merge(
184
+ ...parts: Array<Serialized | undefined>
185
+ ): Serialized | undefined {
186
+ const defined = parts.filter(
187
+ (part): part is Serialized => part !== undefined
188
+ );
189
+ if (defined.length === 0) {
190
+ return;
191
+ }
192
+ return Object.assign({}, ...defined);
193
+ }
194
+
195
+ /**
196
+ * Serializes the styles into the stable JSON string handed to native code.
197
+ * Colors become processed ARGB ints, padding shorthands are expanded, and
198
+ * mention variants become a longest-pattern-first ordered array.
199
+ */
200
+ // biome-ignore lint/complexity/noExcessiveCognitiveComplexity: go away
201
+ export function serializeStyles(
202
+ styles: MarkdownStyles | undefined,
203
+ main: MainStyle | undefined
204
+ ): string {
205
+ const out: Serialized = {};
206
+
207
+ if (main != null) {
208
+ const section: Serialized = {};
209
+ putColor(section, "backgroundColor", main.backgroundColor);
210
+ putPadding(section, main, ["Left", "Right", "Top", "Bottom"]);
211
+ put(section, "gap", main.gap);
212
+ if (Object.keys(section).length > 0) {
213
+ out.main = section;
214
+ }
215
+ // Text keys cascade into every text element as the `base` style. The
216
+ // container's backgroundColor must not ride along, or every text run
217
+ // would inherit it as a highlight.
218
+ const { backgroundColor: _containerBackground, ...mainText } = main;
219
+ put(out, "base", serializeText(mainText));
220
+ }
221
+
222
+ if (styles != null) {
223
+ for (const level of ["h1", "h2", "h3", "h4", "h5", "h6"] as const) {
224
+ put(out, level, serializeText(styles.headings?.[level]));
225
+ }
226
+ put(out, "paragraph", serializeText(styles.paragraph));
227
+
228
+ if (styles.image != null) {
229
+ const image: Serialized = {};
230
+ put(image, "borderRadius", styles.image.borderRadius);
231
+ putColor(image, "backgroundColor", styles.image.backgroundColor);
232
+ put(image, "height", styles.image.height);
233
+ put(image, "maxHeight", styles.image.maxHeight);
234
+ if (Object.keys(image).length > 0) {
235
+ out.image = image;
236
+ }
237
+ }
238
+
239
+ if (styles.table != null) {
240
+ const table = serializeLayout(styles.table) ?? {};
241
+ put(table, "minColumnWidth", styles.table.minColumnWidth);
242
+ put(table, "maxColumnWidth", styles.table.maxColumnWidth);
243
+ if (Object.keys(table).length > 0) {
244
+ out.table = table;
245
+ }
246
+ }
247
+ put(out, "tableRow", serializeLayout(styles.tableRow));
248
+ put(out, "tableHeaderRow", serializeLayout(styles.tableHeaderRow));
249
+ put(out, "tableBodyRow", serializeLayout(styles.tableBodyRow));
250
+ if (styles.tableHeaderCell != null) {
251
+ const headerCell = serializeText(styles.tableHeaderCell) ?? {};
252
+ putPadding(headerCell, styles.tableHeaderCell, [
253
+ "Left",
254
+ "Right",
255
+ "Top",
256
+ "Bottom",
257
+ ]);
258
+ if (Object.keys(headerCell).length > 0) {
259
+ out.tableHeaderCell = headerCell;
260
+ }
261
+ }
262
+ if (styles.tableCell != null) {
263
+ const cell = serializeText(styles.tableCell) ?? {};
264
+ putPadding(cell, styles.tableCell, ["Left", "Right", "Top", "Bottom"]);
265
+ if (Object.keys(cell).length > 0) {
266
+ out.tableCell = cell;
267
+ }
268
+ }
269
+
270
+ if (styles.spoiler != null) {
271
+ const spoiler: Serialized = {};
272
+ putColor(spoiler, "backgroundColor", styles.spoiler.backgroundColor);
273
+ put(spoiler, "borderRadius", styles.spoiler.borderRadius);
274
+ put(spoiler, "borderCurve", styles.spoiler.borderCurve);
275
+ if (Object.keys(spoiler).length > 0) {
276
+ out.spoiler = spoiler;
277
+ }
278
+ }
279
+
280
+ put(out, "gap", styles.gap);
281
+
282
+ if (styles.divider != null) {
283
+ const divider: Serialized = {};
284
+ putColor(divider, "color", styles.divider.color);
285
+ put(divider, "height", styles.divider.height);
286
+ if (Object.keys(divider).length > 0) {
287
+ out.divider = divider;
288
+ }
289
+ }
290
+
291
+ put(out, "superscript", serializeText(styles.superscript));
292
+ put(out, "subscript", serializeText(styles.subscript));
293
+ put(out, "bold", serializeText(styles.bold));
294
+ put(out, "italic", serializeText(styles.italic));
295
+ put(out, "strikethrough", serializeText(styles.strikethrough));
296
+
297
+ if (styles.list != null) {
298
+ const list: Serialized = {};
299
+ put(list, "marginLeft", styles.list.marginLeft);
300
+ if (Object.keys(list).length > 0) {
301
+ out.list = list;
302
+ }
303
+ }
304
+ if (styles.listMarker != null) {
305
+ const marker: Serialized = {};
306
+ put(marker, "width", styles.listMarker.width);
307
+ put(marker, "marginLeft", styles.listMarker.marginLeft);
308
+ putColor(marker, "color", styles.listMarker.color);
309
+ if (Object.keys(marker).length > 0) {
310
+ out.listMarker = marker;
311
+ }
312
+ }
313
+ put(out, "listItem", serializeText(styles.listItem));
314
+
315
+ if (styles.link != null) {
316
+ const link = serializeText(styles.link) ?? {};
317
+ put(link, "borderRadius", styles.link.borderRadius);
318
+ put(link, "borderCurve", styles.link.borderCurve);
319
+ if (Object.keys(link).length > 0) {
320
+ out.link = link;
321
+ }
322
+ }
323
+
324
+ if (styles.mention != null) {
325
+ const { variants, ...base } = styles.mention;
326
+ const mention = serializeText(base) ?? {};
327
+ put(mention, "borderRadius", styles.mention.borderRadius);
328
+ put(mention, "borderCurve", styles.mention.borderCurve);
329
+ if (variants != null) {
330
+ const ordered = Object.keys(variants)
331
+ .sort((a, b) => b.length - a.length || a.localeCompare(b))
332
+ .map((pattern) => [pattern, serializeText(variants[pattern]) ?? {}]);
333
+ if (ordered.length > 0) {
334
+ mention.variants = ordered;
335
+ }
336
+ }
337
+ if (Object.keys(mention).length > 0) {
338
+ out.mention = mention;
339
+ }
340
+ }
341
+
342
+ if (styles.inlineCode != null) {
343
+ const code = serializeText(styles.inlineCode) ?? {};
344
+ putColor(code, "backgroundColor", styles.inlineCode.backgroundColor);
345
+ put(code, "borderRadius", styles.inlineCode.borderRadius);
346
+ put(code, "borderCurve", styles.inlineCode.borderCurve);
347
+ putPadding(code, styles.inlineCode, ["Left", "Right"]);
348
+ if (Object.keys(code).length > 0) {
349
+ out.inlineCode = code;
350
+ }
351
+ }
352
+
353
+ put(
354
+ out,
355
+ "codeBlock",
356
+ merge(serializeText(styles.codeBlock), serializeLayout(styles.codeBlock))
357
+ );
358
+ put(
359
+ out,
360
+ "blockQuote",
361
+ merge(
362
+ serializeText(styles.blockQuote),
363
+ serializeLayout(styles.blockQuote)
364
+ )
365
+ );
366
+ }
367
+
368
+ return JSON.stringify(out);
369
+ }