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.
- package/JetMarkdown.podspec +42 -0
- package/LICENSE +20 -0
- package/README.md +247 -0
- package/android/CMakeLists.txt +52 -0
- package/android/build.gradle +55 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/cpp/JniBindings.cpp +243 -0
- package/android/src/main/java/com/jetmarkdown/JetMarkdownNative.kt +114 -0
- package/android/src/main/java/com/jetmarkdown/JetMarkdownPackage.kt +22 -0
- package/android/src/main/java/com/jetmarkdown/JetMarkdownView.kt +235 -0
- package/android/src/main/java/com/jetmarkdown/JetMarkdownViewManager.kt +75 -0
- package/android/src/main/java/com/jetmarkdown/editor/EditorSpans.kt +217 -0
- package/android/src/main/java/com/jetmarkdown/editor/JetMarkdownEditorManager.kt +224 -0
- package/android/src/main/java/com/jetmarkdown/editor/JetMarkdownEditorView.kt +1736 -0
- package/android/src/main/java/com/jetmarkdown/measure/MarkdownMeasurer.kt +54 -0
- package/android/src/main/java/com/jetmarkdown/parser/AstDecoder.kt +59 -0
- package/android/src/main/java/com/jetmarkdown/parser/MdNode.kt +43 -0
- package/android/src/main/java/com/jetmarkdown/render/ContentCache.kt +53 -0
- package/android/src/main/java/com/jetmarkdown/render/RenderedContent.kt +318 -0
- package/android/src/main/java/com/jetmarkdown/render/SpannableRenderer.kt +658 -0
- package/android/src/main/java/com/jetmarkdown/render/spans/ChipSpan.kt +17 -0
- package/android/src/main/java/com/jetmarkdown/render/spans/InteractiveSpans.kt +11 -0
- package/android/src/main/java/com/jetmarkdown/render/spans/MarkdownLineHeightSpan.kt +59 -0
- package/android/src/main/java/com/jetmarkdown/render/spans/RunSpan.kt +40 -0
- package/android/src/main/java/com/jetmarkdown/style/Fonts.kt +34 -0
- package/android/src/main/java/com/jetmarkdown/style/LayoutStyleSpec.kt +71 -0
- package/android/src/main/java/com/jetmarkdown/style/PlatformColorResolver.kt +77 -0
- package/android/src/main/java/com/jetmarkdown/style/StyleConfig.kt +80 -0
- package/android/src/main/java/com/jetmarkdown/style/TextStyleSpec.kt +61 -0
- package/android/src/main/java/com/jetmarkdown/views/BlockStackView.kt +292 -0
- package/android/src/main/java/com/jetmarkdown/views/BlockTextView.kt +378 -0
- package/android/src/main/java/com/jetmarkdown/views/BoxDrawing.kt +50 -0
- package/android/src/main/java/com/jetmarkdown/views/MarkdownHost.kt +12 -0
- package/android/src/main/java/com/jetmarkdown/views/MarkdownImageView.kt +113 -0
- package/android/src/main/java/com/jetmarkdown/views/NestedHorizontalScrollView.kt +87 -0
- package/android/src/main/java/com/jetmarkdown/views/TableBlockView.kt +139 -0
- package/cpp/core/Ast.h +85 -0
- package/cpp/core/AstJson.cpp +129 -0
- package/cpp/core/AstJson.h +12 -0
- package/cpp/core/AstSerializer.cpp +40 -0
- package/cpp/core/AstSerializer.h +21 -0
- package/cpp/core/AstToMarkdown.cpp +549 -0
- package/cpp/core/AstToMarkdown.h +16 -0
- package/cpp/core/EditorRuns.cpp +640 -0
- package/cpp/core/EditorRuns.h +94 -0
- package/cpp/core/EditorText.cpp +15 -0
- package/cpp/core/EditorText.h +18 -0
- package/cpp/core/InlineExtensions.cpp +305 -0
- package/cpp/core/InlineExtensions.h +17 -0
- package/cpp/core/Parser.cpp +470 -0
- package/cpp/core/Parser.h +16 -0
- package/cpp/core/Preprocess.cpp +181 -0
- package/cpp/core/Preprocess.h +14 -0
- package/cpp/md4c/LICENSE +22 -0
- package/cpp/md4c/VERSION +1 -0
- package/cpp/md4c/md4c.c +6462 -0
- package/cpp/md4c/md4c.h +407 -0
- package/cpp/react/JetMarkdownEditorShadowNode.cpp +54 -0
- package/cpp/react/JetMarkdownEditorShadowNode.h +55 -0
- package/cpp/react/JetMarkdownEditorState.h +35 -0
- package/cpp/react/JetMarkdownMeasurer.cpp +32 -0
- package/cpp/react/JetMarkdownMeasurer.h +41 -0
- package/cpp/react/JetMarkdownShadowNode.cpp +100 -0
- package/cpp/react/JetMarkdownShadowNode.h +53 -0
- package/cpp/react/JetMarkdownState.h +52 -0
- package/cpp/react/override/react/renderer/components/JetMarkdownViewSpec/ComponentDescriptors.h +21 -0
- package/cpp/tests/parser_tests.cpp +662 -0
- package/cpp/tests/run_tests.sh +25 -0
- package/ios/JetMarkdownView.h +14 -0
- package/ios/JetMarkdownView.mm +468 -0
- package/ios/editor/JetMarkdownEditor.h +12 -0
- package/ios/editor/JetMarkdownEditor.mm +2002 -0
- package/ios/measure/JMDMarkdownMeasurer.h +22 -0
- package/ios/measure/JMDMarkdownMeasurer.mm +38 -0
- package/ios/render/JMDBlock.h +97 -0
- package/ios/render/JMDBlock.m +28 -0
- package/ios/render/JMDBlockRenderer.h +17 -0
- package/ios/render/JMDBlockRenderer.mm +852 -0
- package/ios/render/JMDContentCache.h +17 -0
- package/ios/render/JMDContentCache.mm +42 -0
- package/ios/render/JMDRenderedContent.h +35 -0
- package/ios/render/JMDRenderedContent.mm +274 -0
- package/ios/style/JMDFontScale.h +33 -0
- package/ios/style/JMDLayoutStyle.h +37 -0
- package/ios/style/JMDLayoutStyle.mm +68 -0
- package/ios/style/JMDStyleConfig.h +39 -0
- package/ios/style/JMDStyleConfig.mm +111 -0
- package/ios/style/JMDTextStyle.h +25 -0
- package/ios/style/JMDTextStyle.mm +68 -0
- package/ios/views/JMDBlockStackView.h +18 -0
- package/ios/views/JMDBlockStackView.m +97 -0
- package/ios/views/JMDBlockTextView.h +24 -0
- package/ios/views/JMDBlockTextView.m +340 -0
- package/ios/views/JMDBoxViews.h +33 -0
- package/ios/views/JMDBoxViews.m +292 -0
- package/ios/views/JMDImageView.h +19 -0
- package/ios/views/JMDImageView.m +68 -0
- package/ios/views/JMDMarkdownHost.h +16 -0
- package/ios/views/JMDTableView.h +16 -0
- package/ios/views/JMDTableView.m +172 -0
- package/lib/module/JetMarkdownEditor.js +85 -0
- package/lib/module/JetMarkdownEditor.js.map +1 -0
- package/lib/module/JetMarkdownEditor.native.js +244 -0
- package/lib/module/JetMarkdownEditor.native.js.map +1 -0
- package/lib/module/JetMarkdownEditorNativeComponent.ts +173 -0
- package/lib/module/JetMarkdownView.js +19 -0
- package/lib/module/JetMarkdownView.js.map +1 -0
- package/lib/module/JetMarkdownView.native.js +44 -0
- package/lib/module/JetMarkdownView.native.js.map +1 -0
- package/lib/module/JetMarkdownViewNativeComponent.ts +35 -0
- package/lib/module/defaultStyles.js +122 -0
- package/lib/module/defaultStyles.js.map +1 -0
- package/lib/module/index.js +9 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/serializeStyles.js +259 -0
- package/lib/module/serializeStyles.js.map +1 -0
- package/lib/module/types.js +4 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/useJetMarkdownEditor.js +41 -0
- package/lib/module/useJetMarkdownEditor.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/JetMarkdownEditor.d.ts +6 -0
- package/lib/typescript/src/JetMarkdownEditor.d.ts.map +1 -0
- package/lib/typescript/src/JetMarkdownEditor.native.d.ts +6 -0
- package/lib/typescript/src/JetMarkdownEditor.native.d.ts.map +1 -0
- package/lib/typescript/src/JetMarkdownEditorNativeComponent.d.ts +105 -0
- package/lib/typescript/src/JetMarkdownEditorNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/JetMarkdownView.d.ts +3 -0
- package/lib/typescript/src/JetMarkdownView.d.ts.map +1 -0
- package/lib/typescript/src/JetMarkdownView.native.d.ts +3 -0
- package/lib/typescript/src/JetMarkdownView.native.d.ts.map +1 -0
- package/lib/typescript/src/JetMarkdownViewNativeComponent.d.ts +28 -0
- package/lib/typescript/src/JetMarkdownViewNativeComponent.d.ts.map +1 -0
- package/lib/typescript/src/defaultStyles.d.ts +20 -0
- package/lib/typescript/src/defaultStyles.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +7 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/serializeStyles.d.ts +34 -0
- package/lib/typescript/src/serializeStyles.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +352 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/lib/typescript/src/useJetMarkdownEditor.d.ts +36 -0
- package/lib/typescript/src/useJetMarkdownEditor.d.ts.map +1 -0
- package/package.json +150 -0
- package/react-native.config.js +11 -0
- package/src/JetMarkdownEditor.native.tsx +301 -0
- package/src/JetMarkdownEditor.tsx +90 -0
- package/src/JetMarkdownEditorNativeComponent.ts +173 -0
- package/src/JetMarkdownView.native.tsx +67 -0
- package/src/JetMarkdownView.tsx +16 -0
- package/src/JetMarkdownViewNativeComponent.ts +35 -0
- package/src/defaultStyles.ts +102 -0
- package/src/index.tsx +34 -0
- package/src/serializeStyles.ts +369 -0
- package/src/types.ts +438 -0
- 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
|
+
}
|