Skip to content

Commit d2a81ec

Browse files
committed
refactor: update package.json structure and enhance web support
1 parent 434cc01 commit d2a81ec

7 files changed

Lines changed: 353 additions & 49 deletions

File tree

apps/web-example/metro.config.js

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,35 +5,13 @@ const path = require('path');
55
const projectRoot = __dirname;
66
const monorepoRoot = path.resolve(projectRoot, '../..');
77

8-
const pkg = require('../../package.json');
9-
108
const config = getDefaultConfig(projectRoot);
119

12-
// Watch the monorepo root so Metro picks up changes to the library source.
1310
config.watchFolders = [monorepoRoot];
1411

15-
// Resolve node_modules from both the project and the monorepo root.
1612
config.resolver.nodeModulesPaths = [
1713
path.resolve(projectRoot, 'node_modules'),
1814
path.resolve(monorepoRoot, 'node_modules'),
1915
];
2016

21-
// Redirect the library's package name to its source entry point so that
22-
// Metro doesn't try to load the built output (./lib/module/index.js) which
23-
// is not committed to the repo. Use the platform-specific entry on web.
24-
const upstreamResolveRequest = config.resolver.resolveRequest;
25-
config.resolver.resolveRequest = (context, moduleName, platform) => {
26-
if (moduleName === pkg.name) {
27-
const entry = platform === 'web' ? 'src/index.web.tsx' : 'src/index.tsx';
28-
return {
29-
filePath: path.resolve(monorepoRoot, entry),
30-
type: 'sourceFile',
31-
};
32-
}
33-
if (upstreamResolveRequest) {
34-
return upstreamResolveRequest(context, moduleName, platform);
35-
}
36-
return context.resolveRequest(context, moduleName, platform);
37-
};
38-
3917
module.exports = config;

package.json

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,10 @@
22
"name": "react-native-enriched-markdown",
33
"version": "0.4.1",
44
"description": "Markdown Text component for React Native",
5-
"main": "./lib/module/index.js",
5+
"main": "./lib/module/index",
6+
"module": "./lib/module/index",
7+
"source": "./src/index.tsx",
68
"types": "./lib/typescript/src/index.d.ts",
7-
"exports": {
8-
".": {
9-
"source": "./src/index.tsx",
10-
"types": "./lib/typescript/src/index.d.ts",
11-
"default": "./lib/module/index.js"
12-
},
13-
"./package.json": "./package.json",
14-
"./app.plugin.js": "./app.plugin.js"
15-
},
169
"files": [
1710
"src",
1811
"lib",

src/index.web.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
export { EnrichedMarkdownText, default } from './web/EnrichedMarkdownText';
2-
export type {
3-
EnrichedMarkdownTextProps,
4-
ContextMenuItem as TextContextMenuItem,
5-
} from './types/MarkdownTextProps';
2+
export type { EnrichedMarkdownTextProps } from './types/MarkdownTextProps.web';
63
export type { MarkdownStyle, Md4cFlags } from './types/MarkdownStyle';
74
export type {
85
LinkPressEvent,

src/normalizeMarkdownStyle.web.ts

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
import type { MarkdownStyle } from './types/MarkdownStyle';
2+
import type {
3+
BlockTextAlign,
4+
EmphasisFontStyle,
5+
MarkdownStyleInternal,
6+
} from './types/MarkdownStyleInternal';
7+
8+
const SYSTEM_FONT =
9+
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
10+
const MONOSPACE_FONT =
11+
'ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, "DejaVu Sans Mono", monospace';
12+
13+
function mergeSubStyle<T extends Record<string, unknown>>(
14+
defaultStyle: T,
15+
userStyle?: Partial<T>
16+
): T {
17+
if (!userStyle) return defaultStyle;
18+
return { ...defaultStyle, ...userStyle };
19+
}
20+
21+
const defaultTextColor = '#1F2937';
22+
const defaultHeadingColor = '#111827';
23+
24+
const baseHeader: {
25+
fontFamily: string;
26+
fontWeight: string;
27+
marginTop: number;
28+
marginBottom: number;
29+
textAlign: BlockTextAlign;
30+
} = {
31+
fontFamily: SYSTEM_FONT,
32+
fontWeight: '',
33+
marginTop: 0,
34+
marginBottom: 8,
35+
textAlign: 'auto',
36+
};
37+
38+
const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({
39+
paragraph: {
40+
fontSize: 16,
41+
fontFamily: SYSTEM_FONT,
42+
fontWeight: '',
43+
color: defaultTextColor,
44+
lineHeight: 26,
45+
marginTop: 0,
46+
marginBottom: 16,
47+
textAlign: 'auto' as BlockTextAlign,
48+
},
49+
h1: {
50+
...baseHeader,
51+
fontSize: 30,
52+
color: defaultHeadingColor,
53+
lineHeight: 38,
54+
},
55+
h2: {
56+
...baseHeader,
57+
fontSize: 24,
58+
color: defaultHeadingColor,
59+
lineHeight: 32,
60+
},
61+
h3: {
62+
...baseHeader,
63+
fontSize: 20,
64+
color: defaultHeadingColor,
65+
lineHeight: 28,
66+
},
67+
h4: {
68+
...baseHeader,
69+
fontSize: 18,
70+
color: defaultHeadingColor,
71+
lineHeight: 26,
72+
},
73+
h5: {
74+
...baseHeader,
75+
fontSize: 16,
76+
color: '#374151',
77+
lineHeight: 24,
78+
},
79+
h6: {
80+
...baseHeader,
81+
fontSize: 14,
82+
color: '#4B5563',
83+
lineHeight: 22,
84+
},
85+
blockquote: {
86+
fontSize: 16,
87+
fontFamily: SYSTEM_FONT,
88+
fontWeight: '',
89+
color: '#4B5563',
90+
lineHeight: 26,
91+
marginTop: 0,
92+
marginBottom: 16,
93+
borderColor: '#D1D5DB',
94+
borderWidth: 3,
95+
gapWidth: 16,
96+
backgroundColor: '#F9FAFB',
97+
},
98+
list: {
99+
fontSize: 16,
100+
fontFamily: SYSTEM_FONT,
101+
fontWeight: '',
102+
color: defaultTextColor,
103+
lineHeight: 26,
104+
marginTop: 0,
105+
marginBottom: 16,
106+
bulletColor: '#6B7280',
107+
bulletSize: 6,
108+
markerColor: '#6B7280',
109+
markerFontWeight: '500',
110+
gapWidth: 12,
111+
marginLeft: 24,
112+
},
113+
codeBlock: {
114+
fontSize: 14,
115+
fontFamily: MONOSPACE_FONT,
116+
fontWeight: '',
117+
color: '#F3F4F6',
118+
lineHeight: 22,
119+
marginTop: 0,
120+
marginBottom: 16,
121+
backgroundColor: '#1F2937',
122+
borderColor: '#374151',
123+
borderRadius: 8,
124+
borderWidth: 1,
125+
padding: 16,
126+
},
127+
link: { fontFamily: '', color: '#2563EB', underline: true },
128+
strong: { fontFamily: '', fontWeight: 'bold', color: undefined },
129+
em: {
130+
fontFamily: '',
131+
fontStyle: 'italic' as EmphasisFontStyle,
132+
color: undefined,
133+
},
134+
strikethrough: { color: '#9CA3AF' },
135+
underline: { color: defaultTextColor },
136+
code: {
137+
fontFamily: MONOSPACE_FONT,
138+
fontSize: 0,
139+
color: '#E01E5A',
140+
backgroundColor: '#FDF2F4',
141+
borderColor: '#F8D7DA',
142+
},
143+
image: { height: 200, borderRadius: 8, marginTop: 0, marginBottom: 16 },
144+
inlineImage: { size: 20 },
145+
thematicBreak: {
146+
color: '#E5E7EB',
147+
height: 1,
148+
marginTop: 24,
149+
marginBottom: 24,
150+
},
151+
table: {
152+
fontSize: 14,
153+
fontFamily: SYSTEM_FONT,
154+
fontWeight: '',
155+
color: defaultTextColor,
156+
marginTop: 0,
157+
marginBottom: 16,
158+
lineHeight: 22,
159+
headerFontFamily: '',
160+
headerBackgroundColor: '#F3F4F6',
161+
headerTextColor: '#111827',
162+
rowEvenBackgroundColor: '#FFFFFF',
163+
rowOddBackgroundColor: '#F9FAFB',
164+
borderColor: '#E5E7EB',
165+
borderWidth: 1,
166+
borderRadius: 6,
167+
cellPaddingHorizontal: 12,
168+
cellPaddingVertical: 8,
169+
},
170+
math: {
171+
fontSize: 20,
172+
color: defaultTextColor,
173+
backgroundColor: '#F3F4F6',
174+
padding: 12,
175+
marginTop: 0,
176+
marginBottom: 16,
177+
textAlign: 'center' as BlockTextAlign,
178+
},
179+
inlineMath: { color: defaultTextColor },
180+
taskList: {
181+
checkedColor: '#007AFF',
182+
borderColor: '#9E9E9E',
183+
checkboxSize: 14,
184+
checkboxBorderRadius: 3,
185+
checkmarkColor: '#FFFFFF',
186+
checkedTextColor: '#000000',
187+
checkedStrikethrough: false,
188+
},
189+
});
190+
191+
const refCache = new WeakMap<MarkdownStyle, MarkdownStyleInternal>();
192+
const structuralCache: {
193+
style: MarkdownStyle;
194+
result: MarkdownStyleInternal;
195+
}[] = [];
196+
const LRU_MAX = 8;
197+
198+
const isStyleEqual = (a: MarkdownStyle, b: MarkdownStyle): boolean => {
199+
const keys = Object.keys(DEFAULT_NORMALIZED_STYLE) as (keyof MarkdownStyle)[];
200+
return keys.every((key) => {
201+
const subA = a[key],
202+
subB = b[key];
203+
if (subA === subB) return true;
204+
if (!subA || !subB) return false;
205+
const subKeys = Object.keys(subA) as (keyof typeof subA)[];
206+
return subKeys.every((k) => subA[k] === subB[k]);
207+
});
208+
};
209+
210+
export const normalizeMarkdownStyle = (
211+
style: MarkdownStyle
212+
): MarkdownStyleInternal => {
213+
if (!style || Object.keys(style).length === 0)
214+
return DEFAULT_NORMALIZED_STYLE;
215+
216+
const refHit = refCache.get(style);
217+
if (refHit) return refHit;
218+
219+
const structIdx = structuralCache.findIndex((e) =>
220+
isStyleEqual(e.style, style)
221+
);
222+
if (structIdx !== -1) {
223+
const entry = structuralCache.splice(structIdx, 1)[0]!;
224+
structuralCache.unshift(entry);
225+
refCache.set(style, entry.result);
226+
return entry.result;
227+
}
228+
229+
const result: Record<string, unknown> = {};
230+
(
231+
Object.keys(DEFAULT_NORMALIZED_STYLE) as (keyof MarkdownStyleInternal)[]
232+
).forEach((key) => {
233+
result[key] = mergeSubStyle(
234+
DEFAULT_NORMALIZED_STYLE[key] as unknown as Record<string, unknown>,
235+
style[key] as unknown as Record<string, unknown> | undefined
236+
);
237+
});
238+
239+
if (style.taskList?.checkboxSize === undefined) {
240+
const listSize = (result.list as { fontSize: number }).fontSize;
241+
(result.taskList as { checkboxSize: number }).checkboxSize = Math.round(
242+
listSize * 0.9
243+
);
244+
}
245+
246+
const finalResult = Object.freeze(result) as unknown as MarkdownStyleInternal;
247+
refCache.set(style, finalResult);
248+
structuralCache.unshift({ style, result: finalResult });
249+
if (structuralCache.length > LRU_MAX) structuralCache.pop();
250+
251+
return finalResult;
252+
};

0 commit comments

Comments
 (0)