@codearcade/expo-markdown 1.0.6 → 1.1.0
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/LICENSE +20 -0
- package/README.md +37 -128
- package/{dist/index.js → lib/module/components/markdown.js} +102 -16
- package/lib/module/components/markdown.js.map +1 -0
- package/lib/module/index.js +4 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/typescript/package.json +1 -0
- package/{dist → lib/typescript/src}/components/markdown.d.ts +3 -2
- package/lib/typescript/src/components/markdown.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +2 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/package.json +103 -40
- package/src/components/markdown.tsx +338 -0
- package/{dist/index.d.ts → src/index.ts} +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abhishek Singh
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,128 +1,37 @@
|
|
|
1
|
-
# @codearcade/expo-markdown
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Usage
|
|
41
|
-
|
|
42
|
-
Using `@codearcade/expo-markdown` is simple. Import the `Markdown` component and pass your content string.
|
|
43
|
-
|
|
44
|
-
### Basic Usage
|
|
45
|
-
|
|
46
|
-
```tsx
|
|
47
|
-
import { Markdown } from "@codearcade/expo-markdown";
|
|
48
|
-
|
|
49
|
-
const markdown = `
|
|
50
|
-
# Hello World
|
|
51
|
-
|
|
52
|
-
This is a **markdown** component.
|
|
53
|
-
|
|
54
|
-
\`\`\`javascript
|
|
55
|
-
console.log("Hello from CodeArcade!");
|
|
56
|
-
\`\`\`
|
|
57
|
-
`;
|
|
58
|
-
|
|
59
|
-
export default function App() {
|
|
60
|
-
return (
|
|
61
|
-
<View style={{ flex: 1 }}>
|
|
62
|
-
<Markdown content={markdown} />
|
|
63
|
-
</View>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
### Dark Mode & Custom Themes
|
|
69
|
-
|
|
70
|
-
Easily switch between light and dark modes, and customize the color palette for each mode.
|
|
71
|
-
|
|
72
|
-
```tsx
|
|
73
|
-
import { useState } from "react";
|
|
74
|
-
import { View } from "react-native";
|
|
75
|
-
import { Markdown } from "@codearcade/expo-markdown";
|
|
76
|
-
|
|
77
|
-
const myDarkTheme = {
|
|
78
|
-
backgroundColor: "#1e1e1e",
|
|
79
|
-
textColor: "#e0e0e0",
|
|
80
|
-
codeBackgroundColor: "#252526",
|
|
81
|
-
primaryColor: "#569cd6",
|
|
82
|
-
secondaryColor: "#4ec9b0",
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
export default function BlogPost() {
|
|
86
|
-
const [isDarkMode, setIsDarkMode] = useState(false);
|
|
87
|
-
|
|
88
|
-
return (
|
|
89
|
-
<View style={{ flex: 1 }}>
|
|
90
|
-
<Markdown
|
|
91
|
-
content="# My Blog Post"
|
|
92
|
-
theme={isDarkMode ? "dark" : "light"}
|
|
93
|
-
// Optional: Customize themes
|
|
94
|
-
defaultDarkTheme={myDarkTheme}
|
|
95
|
-
/>
|
|
96
|
-
</View>
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## API Reference
|
|
102
|
-
|
|
103
|
-
### `<Markdown />`
|
|
104
|
-
|
|
105
|
-
| Prop | Type | Default | Description |
|
|
106
|
-
| ------------------- | ------------------- | ----------------------- | --------------------------------------------- |
|
|
107
|
-
| `content` | `string` | **Required** | The raw markdown string to render. |
|
|
108
|
-
| `theme` | `"light" \| "dark"` | `"light"` | Controls the rendering mode of the component. |
|
|
109
|
-
| `defaultLightTheme` | `ThemeConfig` | `Default Light Palette` | Custom colors for light mode. |
|
|
110
|
-
| `defaultDarkTheme` | `ThemeConfig` | `Default Dark Palette` | Custom colors for dark mode. |
|
|
111
|
-
|
|
112
|
-
### ThemeConfig Interface
|
|
113
|
-
|
|
114
|
-
The theme objects (`defaultLightTheme` and `defaultDarkTheme`) should follow this structure:
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
interface ThemeConfig {
|
|
118
|
-
backgroundColor: string;
|
|
119
|
-
textColor: string;
|
|
120
|
-
codeBackgroundColor: string;
|
|
121
|
-
primaryColor: string;
|
|
122
|
-
secondaryColor: string;
|
|
123
|
-
}
|
|
124
|
-
```
|
|
125
|
-
|
|
126
|
-
## License
|
|
127
|
-
|
|
128
|
-
MIT © [CodeArcade](https://github.com/codearcade-io)
|
|
1
|
+
# @codearcade/expo-markdown
|
|
2
|
+
|
|
3
|
+
Markdown component to render markdown in expo with syntax highlighting and styling
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
```sh
|
|
9
|
+
npm install @codearcade/expo-markdown
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
```js
|
|
17
|
+
import { multiply } from '@codearcade/expo-markdown';
|
|
18
|
+
|
|
19
|
+
// ...
|
|
20
|
+
|
|
21
|
+
const result = await multiply(3, 7);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## Contributing
|
|
26
|
+
|
|
27
|
+
- [Development workflow](CONTRIBUTING.md#development-workflow)
|
|
28
|
+
- [Sending a pull request](CONTRIBUTING.md#sending-a-pull-request)
|
|
29
|
+
- [Code of conduct](CODE_OF_CONDUCT.md)
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
MIT
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
Made with [create-react-native-library](https://github.com/callstack/react-native-builder-bob)
|
|
@@ -1,4 +1,46 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import * as Clipboard from 'expo-clipboard';
|
|
4
|
+
import MarkdownIt from 'markdown-it';
|
|
5
|
+
import React, { useMemo } from 'react';
|
|
6
|
+
import { ActivityIndicator, StyleSheet, View } from 'react-native';
|
|
7
|
+
import { WebView } from 'react-native-webview';
|
|
8
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
|
+
const mdParser = new MarkdownIt({
|
|
10
|
+
html: true,
|
|
11
|
+
linkify: true,
|
|
12
|
+
typographer: true
|
|
13
|
+
});
|
|
14
|
+
export const Markdown = ({
|
|
15
|
+
content,
|
|
16
|
+
theme = 'light',
|
|
17
|
+
defaultDarkTheme = {
|
|
18
|
+
backgroundColor: '#111111ff',
|
|
19
|
+
textColor: '#fff',
|
|
20
|
+
codeBackgroundColor: '#2d2d2d',
|
|
21
|
+
primaryColor: '#326fdfff',
|
|
22
|
+
secondaryColor: '#39c927ff'
|
|
23
|
+
},
|
|
24
|
+
defaultLightTheme = {
|
|
25
|
+
backgroundColor: '#ffffffff',
|
|
26
|
+
textColor: '#000',
|
|
27
|
+
codeBackgroundColor: '#f3f3f3ff',
|
|
28
|
+
primaryColor: '#326fdfff',
|
|
29
|
+
secondaryColor: '#39c927ff'
|
|
30
|
+
}
|
|
31
|
+
}) => {
|
|
32
|
+
const activeTheme = theme === 'dark' ? defaultDarkTheme : defaultLightTheme;
|
|
33
|
+
|
|
34
|
+
// 1. Convert Markdown to HTML String
|
|
35
|
+
const htmlContent = useMemo(() => mdParser.render(content), [content]);
|
|
36
|
+
|
|
37
|
+
// 2. Select Prism Theme (CDN URLs)
|
|
38
|
+
// You can swap these URLs for any theme from the list you provided
|
|
39
|
+
const prismThemeUrl = theme === 'dark' ? 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css' : 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css';
|
|
40
|
+
|
|
41
|
+
// 3. Your Custom CSS (Converted to String)
|
|
42
|
+
// I pasted your exact CSS here, plus some WebView specific tweaks
|
|
43
|
+
const userStyles = `
|
|
2
44
|
|
|
3
45
|
* { box-sizing: border-box; }
|
|
4
46
|
|
|
@@ -8,8 +50,8 @@ import*as N from"expo-clipboard";import _ from"markdown-it";import{useMemo as $}
|
|
|
8
50
|
font-size: 16px; /* Body text size */
|
|
9
51
|
line-height: 1.6;
|
|
10
52
|
margin: 0;
|
|
11
|
-
background-color: ${
|
|
12
|
-
color: ${
|
|
53
|
+
background-color: ${activeTheme.backgroundColor} !important;
|
|
54
|
+
color: ${activeTheme.textColor} !important;
|
|
13
55
|
}
|
|
14
56
|
|
|
15
57
|
/* =========================
|
|
@@ -40,7 +82,7 @@ import*as N from"expo-clipboard";import _ from"markdown-it";import{useMemo as $}
|
|
|
40
82
|
.markdownBody p { margin-bottom: 1rem; }
|
|
41
83
|
|
|
42
84
|
.markdownBody a {
|
|
43
|
-
color: ${
|
|
85
|
+
color: ${activeTheme.primaryColor};
|
|
44
86
|
text-decoration: none;
|
|
45
87
|
}
|
|
46
88
|
.markdownBody a:hover { text-decoration: underline; }
|
|
@@ -122,8 +164,8 @@ import*as N from"expo-clipboard";import _ from"markdown-it";import{useMemo as $}
|
|
|
122
164
|
position: relative; /* Anchor for the absolute button */
|
|
123
165
|
margin: 1.5rem 0;
|
|
124
166
|
border-radius: 0.5rem;
|
|
125
|
-
background-color: ${
|
|
126
|
-
border: 1px solid ${
|
|
167
|
+
background-color: ${activeTheme.codeBackgroundColor} !important;
|
|
168
|
+
border: 1px solid ${theme === 'dark' ? '#3e3e3e' : '#e0e0e0'};
|
|
127
169
|
overflow: hidden; /* Ensures rounded corners */
|
|
128
170
|
}
|
|
129
171
|
|
|
@@ -132,9 +174,9 @@ import*as N from"expo-clipboard";import _ from"markdown-it";import{useMemo as $}
|
|
|
132
174
|
position: absolute;
|
|
133
175
|
top: 6px;
|
|
134
176
|
right: 6px;
|
|
135
|
-
background: ${
|
|
136
|
-
color: ${
|
|
137
|
-
border: 1px solid ${
|
|
177
|
+
background: ${theme === 'dark' ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.05)'};
|
|
178
|
+
color: ${theme === 'dark' ? '#ccc' : '#666'};
|
|
179
|
+
border: 1px solid ${theme === 'dark' ? '#555' : '#ddd'};
|
|
138
180
|
border-radius: 4px;
|
|
139
181
|
padding: 4px 10px;
|
|
140
182
|
font-size: 12px;
|
|
@@ -146,8 +188,8 @@ import*as N from"expo-clipboard";import _ from"markdown-it";import{useMemo as $}
|
|
|
146
188
|
|
|
147
189
|
/* Code Block Container */
|
|
148
190
|
pre {
|
|
149
|
-
background-color: ${
|
|
150
|
-
color: ${
|
|
191
|
+
background-color: ${theme === 'dark' ? defaultDarkTheme.codeBackgroundColor : defaultLightTheme.codeBackgroundColor} !important;
|
|
192
|
+
color: ${theme === 'dark' ? defaultDarkTheme.textColor : defaultLightTheme.textColor} !important;
|
|
151
193
|
position: relative;
|
|
152
194
|
padding: 1rem;
|
|
153
195
|
border-radius: 0.5rem;
|
|
@@ -169,16 +211,17 @@ import*as N from"expo-clipboard";import _ from"markdown-it";import{useMemo as $}
|
|
|
169
211
|
font-size: 13px;
|
|
170
212
|
}
|
|
171
213
|
|
|
172
|
-
|
|
214
|
+
`;
|
|
215
|
+
const htmlSource = `
|
|
173
216
|
<!DOCTYPE html>
|
|
174
217
|
<html>
|
|
175
218
|
<head>
|
|
176
219
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
177
|
-
<link href="${
|
|
178
|
-
<style>${
|
|
220
|
+
<link href="${prismThemeUrl}" rel="stylesheet" />
|
|
221
|
+
<style>${userStyles}</style>
|
|
179
222
|
</head>
|
|
180
223
|
<body class="markdownBody">
|
|
181
|
-
${
|
|
224
|
+
${htmlContent}
|
|
182
225
|
|
|
183
226
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
|
184
227
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
|
@@ -217,8 +260,51 @@ import*as N from"expo-clipboard";import _ from"markdown-it";import{useMemo as $}
|
|
|
217
260
|
});
|
|
218
261
|
|
|
219
262
|
wrapper.appendChild(button);
|
|
263
|
+
|
|
264
|
+
if (window.Prism) {
|
|
265
|
+
window.Prism.highlightAll();
|
|
266
|
+
}
|
|
220
267
|
});
|
|
221
268
|
</script>
|
|
222
269
|
</body>
|
|
223
270
|
</html>
|
|
224
|
-
|
|
271
|
+
`;
|
|
272
|
+
|
|
273
|
+
// 5. Handle Messages from WebView (Copy to Clipboard)
|
|
274
|
+
const handleMessage = async event => {
|
|
275
|
+
try {
|
|
276
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
277
|
+
if (data.type === 'copy') {
|
|
278
|
+
await Clipboard.setStringAsync(data.payload);
|
|
279
|
+
}
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.error('Error parsing webview message', error);
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
return /*#__PURE__*/_jsx(View, {
|
|
285
|
+
style: styles.container,
|
|
286
|
+
children: /*#__PURE__*/_jsx(WebView, {
|
|
287
|
+
originWhitelist: ['*'],
|
|
288
|
+
source: {
|
|
289
|
+
html: htmlSource
|
|
290
|
+
},
|
|
291
|
+
onMessage: handleMessage,
|
|
292
|
+
javaScriptEnabled: true,
|
|
293
|
+
domStorageEnabled: true,
|
|
294
|
+
startInLoadingState: true,
|
|
295
|
+
renderLoading: () => /*#__PURE__*/_jsx(ActivityIndicator, {
|
|
296
|
+
size: "large"
|
|
297
|
+
}),
|
|
298
|
+
style: {
|
|
299
|
+
flex: 1,
|
|
300
|
+
backgroundColor: 'transparent'
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
});
|
|
304
|
+
};
|
|
305
|
+
const styles = StyleSheet.create({
|
|
306
|
+
container: {
|
|
307
|
+
flex: 1
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
//# sourceMappingURL=markdown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["Clipboard","MarkdownIt","React","useMemo","ActivityIndicator","StyleSheet","View","WebView","jsx","_jsx","mdParser","html","linkify","typographer","Markdown","content","theme","defaultDarkTheme","backgroundColor","textColor","codeBackgroundColor","primaryColor","secondaryColor","defaultLightTheme","activeTheme","htmlContent","render","prismThemeUrl","userStyles","htmlSource","handleMessage","event","data","JSON","parse","nativeEvent","type","setStringAsync","payload","error","console","style","styles","container","children","originWhitelist","source","onMessage","javaScriptEnabled","domStorageEnabled","startInLoadingState","renderLoading","size","flex","create"],"sourceRoot":"..\\..\\..\\src","sources":["components/markdown.tsx"],"mappings":";;AAAA,OAAO,KAAKA,SAAS,MAAM,gBAAgB;AAC3C,OAAOC,UAAU,MAAM,aAAa;AACpC,OAAOC,KAAK,IAAIC,OAAO,QAAQ,OAAO;AACtC,SAASC,iBAAiB,EAAEC,UAAU,EAAEC,IAAI,QAAQ,cAAc;AAElE,SAASC,OAAO,QAAQ,sBAAsB;AAAC,SAAAC,GAAA,IAAAC,IAAA;AAE/C,MAAMC,QAAQ,GAAG,IAAIT,UAAU,CAAC;EAC9BU,IAAI,EAAE,IAAI;EACVC,OAAO,EAAE,IAAI;EACbC,WAAW,EAAE;AACf,CAAC,CAAC;AAqBF,OAAO,MAAMC,QAAyB,GAAGA,CAAC;EACxCC,OAAO;EACPC,KAAK,GAAG,OAAO;EACfC,gBAAgB,GAAG;IACjBC,eAAe,EAAE,WAAW;IAC5BC,SAAS,EAAE,MAAM;IACjBC,mBAAmB,EAAE,SAAS;IAC9BC,YAAY,EAAE,WAAW;IACzBC,cAAc,EAAE;EAClB,CAAC;EACDC,iBAAiB,GAAG;IAClBL,eAAe,EAAE,WAAW;IAC5BC,SAAS,EAAE,MAAM;IACjBC,mBAAmB,EAAE,WAAW;IAChCC,YAAY,EAAE,WAAW;IACzBC,cAAc,EAAE;EAClB;AACF,CAAC,KAAK;EACJ,MAAME,WAAW,GAAGR,KAAK,KAAK,MAAM,GAAGC,gBAAgB,GAAGM,iBAAiB;;EAE3E;EACA,MAAME,WAAW,GAAGtB,OAAO,CAAC,MAAMO,QAAQ,CAACgB,MAAM,CAACX,OAAO,CAAC,EAAE,CAACA,OAAO,CAAC,CAAC;;EAEtE;EACA;EACA,MAAMY,aAAa,GACjBX,KAAK,KAAK,MAAM,GACZ,mFAAmF,GACnF,0EAA0E;;EAEhF;EACA;EACA,MAAMY,UAAU,GAAG;AACrB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0BJ,WAAW,CAACN,eAAe;AACrD,eAAeM,WAAW,CAACL,SAAS;AACpC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,eAAeK,WAAW,CAACH,YAAY;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BAA0BG,WAAW,CAACJ,mBAAmB;AACzD,0BAA0BJ,KAAK,KAAK,MAAM,GAAG,SAAS,GAAG,SAAS;AAClE;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,oBACQA,KAAK,KAAK,MAAM,GAAG,uBAAuB,GAAG,kBAAkB;AACvE,eACeA,KAAK,KAAK,MAAM,GAAG,MAAM,GAAG,MAAM;AACjD,0BAA0BA,KAAK,KAAK,MAAM,GAAG,MAAM,GAAG,MAAM;AAC5D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0BACQA,KAAK,KAAK,MAAM,GACZC,gBAAgB,CAACG,mBAAmB,GACpCG,iBAAiB,CAACH,mBAAmB;AACjD,eAEQJ,KAAK,KAAK,MAAM,GACZC,gBAAgB,CAACE,SAAS,GAC1BI,iBAAiB,CAACJ,SAAS;AACvC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GACG;EAED,MAAMU,UAAU,GAAG;AACrB;AACA;AACA;AACA;AACA,gBAAgBF,aAAa;AAC7B,WAAWC,UAAU;AACrB;AACA;AACA,IAAIH,WAAW;AACf;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;;EAED;EACA,MAAMK,aAAa,GAAG,MAAOC,KAA0B,IAAK;IAC1D,IAAI;MACF,MAAMC,IAAI,GAAGC,IAAI,CAACC,KAAK,CAACH,KAAK,CAACI,WAAW,CAACH,IAAI,CAAC;MAC/C,IAAIA,IAAI,CAACI,IAAI,KAAK,MAAM,EAAE;QACxB,MAAMpC,SAAS,CAACqC,cAAc,CAACL,IAAI,CAACM,OAAO,CAAC;MAC9C;IACF,CAAC,CAAC,OAAOC,KAAK,EAAE;MACdC,OAAO,CAACD,KAAK,CAAC,+BAA+B,EAAEA,KAAK,CAAC;IACvD;EACF,CAAC;EAED,oBACE9B,IAAA,CAACH,IAAI;IAACmC,KAAK,EAAEC,MAAM,CAACC,SAAU;IAAAC,QAAA,eAC5BnC,IAAA,CAACF,OAAO;MACNsC,eAAe,EAAE,CAAC,GAAG,CAAE;MACvBC,MAAM,EAAE;QAAEnC,IAAI,EAAEkB;MAAW,CAAE;MAC7BkB,SAAS,EAAEjB,aAAc;MACzBkB,iBAAiB,EAAE,IAAK;MACxBC,iBAAiB,EAAE,IAAK;MACxBC,mBAAmB,EAAE,IAAK;MAC1BC,aAAa,EAAEA,CAAA,kBAAM1C,IAAA,CAACL,iBAAiB;QAACgD,IAAI,EAAC;MAAO,CAAE,CAAE;MACxDX,KAAK,EAAE;QAAEY,IAAI,EAAE,CAAC;QAAEnC,eAAe,EAAE;MAAc;IAAE,CACpD;EAAC,CACE,CAAC;AAEX,CAAC;AAED,MAAMwB,MAAM,GAAGrC,UAAU,CAACiD,MAAM,CAAC;EAC/BX,SAAS,EAAE;IACTU,IAAI,EAAE;EACR;AACF,CAAC,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"..\\..\\src","sources":["index.ts"],"mappings":";;AAAA,cAAc,0BAAuB","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React from
|
|
1
|
+
import React from 'react';
|
|
2
2
|
interface Props {
|
|
3
3
|
content: string;
|
|
4
|
-
theme?:
|
|
4
|
+
theme?: 'light' | 'dark';
|
|
5
5
|
defaultDarkTheme?: {
|
|
6
6
|
backgroundColor: string;
|
|
7
7
|
textColor: string;
|
|
@@ -19,3 +19,4 @@ interface Props {
|
|
|
19
19
|
}
|
|
20
20
|
export declare const Markdown: React.FC<Props>;
|
|
21
21
|
export {};
|
|
22
|
+
//# sourceMappingURL=markdown.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../../../src/components/markdown.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAWvC,UAAU,KAAK;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE;QACjB,eAAe,EAAE,MAAM,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,iBAAiB,CAAC,EAAE;QAClB,eAAe,EAAE,MAAM,CAAC;QACxB,SAAS,EAAE,MAAM,CAAC;QAClB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;CACH;AAED,eAAO,MAAM,QAAQ,EAAE,KAAK,CAAC,EAAE,CAAC,KAAK,CA2SpC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,60 +1,123 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codearcade/expo-markdown",
|
|
3
|
-
"
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"types": "./
|
|
7
|
-
"license": "MIT",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Markdown component to render markdown in expo with syntax highlighting and styling",
|
|
5
|
+
"main": "./lib/module/index.js",
|
|
6
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
8
7
|
"exports": {
|
|
9
8
|
".": {
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"build": "bun ./builder.ts && bun run generate-types",
|
|
16
|
-
"prepublishOnly": "bun run build",
|
|
17
|
-
"generate-types": "bunx tsc --emitDeclarationOnly --declaration --outDir dist",
|
|
18
|
-
"clear": "rm -rf dist"
|
|
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"
|
|
19
14
|
},
|
|
20
15
|
"files": [
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"
|
|
16
|
+
"src",
|
|
17
|
+
"lib",
|
|
18
|
+
"android",
|
|
19
|
+
"ios",
|
|
20
|
+
"cpp",
|
|
21
|
+
"*.podspec",
|
|
22
|
+
"react-native.config.js",
|
|
23
|
+
"!ios/build",
|
|
24
|
+
"!android/build",
|
|
25
|
+
"!android/gradle",
|
|
26
|
+
"!android/gradlew",
|
|
27
|
+
"!android/gradlew.bat",
|
|
28
|
+
"!android/local.properties",
|
|
29
|
+
"!**/__tests__",
|
|
30
|
+
"!**/__fixtures__",
|
|
31
|
+
"!**/__mocks__",
|
|
32
|
+
"!**/.*"
|
|
24
33
|
],
|
|
34
|
+
"scripts": {
|
|
35
|
+
"example": "yarn workspace @codearcade/expo-markdown-example",
|
|
36
|
+
"clean": "del-cli lib",
|
|
37
|
+
"prepare": "bob build",
|
|
38
|
+
"typecheck": "tsc",
|
|
39
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\""
|
|
40
|
+
},
|
|
25
41
|
"keywords": [
|
|
26
|
-
"expo",
|
|
27
|
-
"markdown",
|
|
28
|
-
"markdown-expo",
|
|
29
42
|
"react-native",
|
|
30
|
-
"
|
|
31
|
-
"
|
|
32
|
-
"@codearcade/expo-markdown"
|
|
43
|
+
"ios",
|
|
44
|
+
"android"
|
|
33
45
|
],
|
|
34
|
-
"publishConfig": {
|
|
35
|
-
"access": "public"
|
|
36
|
-
},
|
|
37
|
-
"homepage": "https://github.com/codearcade-io/expo-markdown.git#readme",
|
|
38
46
|
"repository": {
|
|
39
47
|
"type": "git",
|
|
40
48
|
"url": "git+https://github.com/codearcade-io/expo-markdown.git"
|
|
41
49
|
},
|
|
42
|
-
"
|
|
43
|
-
"
|
|
50
|
+
"author": "Abhishek Singh <official.6packprogrammer@gmail.com> (https://github.com/meabhisingh)",
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/codearcade-io/expo-markdown/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/codearcade-io/expo-markdown#readme",
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"registry": "https://registry.npmjs.org/"
|
|
58
|
+
},
|
|
44
59
|
"devDependencies": {
|
|
45
|
-
"@
|
|
46
|
-
"@
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"@types/
|
|
60
|
+
"@eslint/compat": "^1.3.2",
|
|
61
|
+
"@eslint/eslintrc": "^3.3.1",
|
|
62
|
+
"@eslint/js": "^9.35.0",
|
|
63
|
+
"@react-native/babel-preset": "0.83.0",
|
|
64
|
+
"@react-native/eslint-config": "0.83.0",
|
|
65
|
+
"@types/markdown-it": "^14",
|
|
66
|
+
"@types/react": "^19.1.12",
|
|
67
|
+
"del-cli": "^6.0.0",
|
|
68
|
+
"eslint": "^9.35.0",
|
|
69
|
+
"eslint-config-prettier": "^10.1.8",
|
|
70
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
71
|
+
"expo-clipboard": "^8.0.8",
|
|
72
|
+
"prettier": "^2.8.8",
|
|
73
|
+
"react": "19.1.0",
|
|
74
|
+
"react-native": "0.81.5",
|
|
75
|
+
"react-native-builder-bob": "^0.40.13",
|
|
76
|
+
"react-native-webview": "^13.16.0",
|
|
77
|
+
"typescript": "^5.9.2"
|
|
51
78
|
},
|
|
52
79
|
"peerDependencies": {
|
|
53
|
-
"expo": "
|
|
54
|
-
"
|
|
55
|
-
"react": "
|
|
56
|
-
"react-native": "
|
|
57
|
-
|
|
80
|
+
"expo-clipboard": "*",
|
|
81
|
+
"react": "*",
|
|
82
|
+
"react-native": "*",
|
|
83
|
+
"react-native-webview": "*"
|
|
84
|
+
},
|
|
85
|
+
"workspaces": [
|
|
86
|
+
"example"
|
|
87
|
+
],
|
|
88
|
+
"packageManager": "yarn@4.11.0",
|
|
89
|
+
"react-native-builder-bob": {
|
|
90
|
+
"source": "src",
|
|
91
|
+
"output": "lib",
|
|
92
|
+
"targets": [
|
|
93
|
+
[
|
|
94
|
+
"module",
|
|
95
|
+
{
|
|
96
|
+
"esm": true
|
|
97
|
+
}
|
|
98
|
+
],
|
|
99
|
+
[
|
|
100
|
+
"typescript",
|
|
101
|
+
{
|
|
102
|
+
"project": "tsconfig.build.json"
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
]
|
|
106
|
+
},
|
|
107
|
+
"prettier": {
|
|
108
|
+
"quoteProps": "consistent",
|
|
109
|
+
"singleQuote": true,
|
|
110
|
+
"tabWidth": 2,
|
|
111
|
+
"trailingComma": "es5",
|
|
112
|
+
"useTabs": false
|
|
113
|
+
},
|
|
114
|
+
"create-react-native-library": {
|
|
115
|
+
"type": "library",
|
|
116
|
+
"languages": "js",
|
|
117
|
+
"tools": [
|
|
118
|
+
"eslint"
|
|
119
|
+
],
|
|
120
|
+
"version": "0.57.0"
|
|
58
121
|
},
|
|
59
122
|
"dependencies": {
|
|
60
123
|
"markdown-it": "^14.1.0"
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import * as Clipboard from 'expo-clipboard';
|
|
2
|
+
import MarkdownIt from 'markdown-it';
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
|
+
import { ActivityIndicator, StyleSheet, View } from 'react-native';
|
|
5
|
+
import type { WebViewMessageEvent } from 'react-native-webview';
|
|
6
|
+
import { WebView } from 'react-native-webview';
|
|
7
|
+
|
|
8
|
+
const mdParser = new MarkdownIt({
|
|
9
|
+
html: true,
|
|
10
|
+
linkify: true,
|
|
11
|
+
typographer: true,
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
content: string;
|
|
16
|
+
theme?: 'light' | 'dark';
|
|
17
|
+
defaultDarkTheme?: {
|
|
18
|
+
backgroundColor: string;
|
|
19
|
+
textColor: string;
|
|
20
|
+
codeBackgroundColor: string;
|
|
21
|
+
primaryColor: string;
|
|
22
|
+
secondaryColor: string;
|
|
23
|
+
};
|
|
24
|
+
defaultLightTheme?: {
|
|
25
|
+
backgroundColor: string;
|
|
26
|
+
textColor: string;
|
|
27
|
+
codeBackgroundColor: string;
|
|
28
|
+
primaryColor: string;
|
|
29
|
+
secondaryColor: string;
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const Markdown: React.FC<Props> = ({
|
|
34
|
+
content,
|
|
35
|
+
theme = 'light',
|
|
36
|
+
defaultDarkTheme = {
|
|
37
|
+
backgroundColor: '#111111ff',
|
|
38
|
+
textColor: '#fff',
|
|
39
|
+
codeBackgroundColor: '#2d2d2d',
|
|
40
|
+
primaryColor: '#326fdfff',
|
|
41
|
+
secondaryColor: '#39c927ff',
|
|
42
|
+
},
|
|
43
|
+
defaultLightTheme = {
|
|
44
|
+
backgroundColor: '#ffffffff',
|
|
45
|
+
textColor: '#000',
|
|
46
|
+
codeBackgroundColor: '#f3f3f3ff',
|
|
47
|
+
primaryColor: '#326fdfff',
|
|
48
|
+
secondaryColor: '#39c927ff',
|
|
49
|
+
},
|
|
50
|
+
}) => {
|
|
51
|
+
const activeTheme = theme === 'dark' ? defaultDarkTheme : defaultLightTheme;
|
|
52
|
+
|
|
53
|
+
// 1. Convert Markdown to HTML String
|
|
54
|
+
const htmlContent = useMemo(() => mdParser.render(content), [content]);
|
|
55
|
+
|
|
56
|
+
// 2. Select Prism Theme (CDN URLs)
|
|
57
|
+
// You can swap these URLs for any theme from the list you provided
|
|
58
|
+
const prismThemeUrl =
|
|
59
|
+
theme === 'dark'
|
|
60
|
+
? 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css'
|
|
61
|
+
: 'https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism.min.css';
|
|
62
|
+
|
|
63
|
+
// 3. Your Custom CSS (Converted to String)
|
|
64
|
+
// I pasted your exact CSS here, plus some WebView specific tweaks
|
|
65
|
+
const userStyles = `
|
|
66
|
+
|
|
67
|
+
* { box-sizing: border-box; }
|
|
68
|
+
|
|
69
|
+
.markdownBody {
|
|
70
|
+
padding: 1rem;
|
|
71
|
+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
72
|
+
font-size: 16px; /* Body text size */
|
|
73
|
+
line-height: 1.6;
|
|
74
|
+
margin: 0;
|
|
75
|
+
background-color: ${activeTheme.backgroundColor} !important;
|
|
76
|
+
color: ${activeTheme.textColor} !important;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/* =========================
|
|
80
|
+
Headings
|
|
81
|
+
========================= */
|
|
82
|
+
.markdownBody h1, .markdownBody h2, .markdownBody h3,
|
|
83
|
+
.markdownBody h4, .markdownBody h5, .markdownBody h6 {
|
|
84
|
+
font-weight: 600;
|
|
85
|
+
margin-top: 2rem;
|
|
86
|
+
margin-bottom: 1rem;
|
|
87
|
+
line-height: 1.25;
|
|
88
|
+
color: inherit;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.markdownBody h1 { font-size: 1.875rem; padding-bottom: 0.3em; }
|
|
92
|
+
.markdownBody h2 { font-size: 1.5rem; padding-bottom: 0.3em; }
|
|
93
|
+
.markdownBody h3 { font-size: 1.25rem; }
|
|
94
|
+
|
|
95
|
+
.markdownBody h1:first-child,
|
|
96
|
+
.markdownBody h2:first-child,
|
|
97
|
+
.markdownBody h3:first-child {
|
|
98
|
+
margin-top: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* =========================
|
|
102
|
+
Paragraphs & Links
|
|
103
|
+
========================= */
|
|
104
|
+
.markdownBody p { margin-bottom: 1rem; }
|
|
105
|
+
|
|
106
|
+
.markdownBody a {
|
|
107
|
+
color: ${activeTheme.primaryColor};
|
|
108
|
+
text-decoration: none;
|
|
109
|
+
}
|
|
110
|
+
.markdownBody a:hover { text-decoration: underline; }
|
|
111
|
+
|
|
112
|
+
/* =========================
|
|
113
|
+
Lists (UL, OL, LI) - Added!
|
|
114
|
+
========================= */
|
|
115
|
+
.markdownBody ul,
|
|
116
|
+
.markdownBody ol {
|
|
117
|
+
padding-left: 1.5rem; /* Indentation */
|
|
118
|
+
margin-bottom: 1rem;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.markdownBody ul { list-style-type: disc; }
|
|
122
|
+
.markdownBody ol { list-style-type: decimal; }
|
|
123
|
+
|
|
124
|
+
.markdownBody li {
|
|
125
|
+
margin-bottom: 0.25rem;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Nested Lists */
|
|
129
|
+
.markdownBody li > ul,
|
|
130
|
+
.markdownBody li > ol {
|
|
131
|
+
margin-top: 0.25rem;
|
|
132
|
+
margin-bottom: 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* =========================
|
|
136
|
+
Blockquotes
|
|
137
|
+
========================= */
|
|
138
|
+
.markdownBody blockquote {
|
|
139
|
+
border-left: 4px solid rgba(0,0,0,0.02);
|
|
140
|
+
padding-left: 1rem;
|
|
141
|
+
font-style: italic;
|
|
142
|
+
color: rgba(0,0,0,0.5);
|
|
143
|
+
margin: 1rem 0;
|
|
144
|
+
margin-left: 0;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* =========================
|
|
148
|
+
Tables
|
|
149
|
+
========================= */
|
|
150
|
+
.markdownBody table {
|
|
151
|
+
width: 100%;
|
|
152
|
+
border-collapse: collapse;
|
|
153
|
+
margin-bottom: 1rem;
|
|
154
|
+
display: block;
|
|
155
|
+
overflow-x: auto;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
.markdownBody th,
|
|
159
|
+
.markdownBody td {
|
|
160
|
+
border: 1px solid rgba(0,0,0,0.02);
|
|
161
|
+
padding: 0.5rem;
|
|
162
|
+
text-align: left;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.markdownBody th { font-weight: 600; background-color: rgba(0,0,0,0.02) }
|
|
166
|
+
|
|
167
|
+
/* =========================
|
|
168
|
+
Images & HR
|
|
169
|
+
========================= */
|
|
170
|
+
.markdownBody img { max-width: 100%; height: auto; border-radius: 0.375rem; }
|
|
171
|
+
|
|
172
|
+
.markdownBody hr {
|
|
173
|
+
border: none;
|
|
174
|
+
border-top: 2px solid rgba(0,0,0,0.02);
|
|
175
|
+
margin: 2rem 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* =========================
|
|
179
|
+
Text Emphasis
|
|
180
|
+
========================= */
|
|
181
|
+
.markdownBody strong { font-weight: 700; }
|
|
182
|
+
.markdownBody em { font-style: italic; }
|
|
183
|
+
.markdownBody del { text-decoration: line-through; opacity: 0.7; }
|
|
184
|
+
|
|
185
|
+
.code-wrapper {
|
|
186
|
+
position: relative; /* Anchor for the absolute button */
|
|
187
|
+
margin: 1.5rem 0;
|
|
188
|
+
border-radius: 0.5rem;
|
|
189
|
+
background-color: ${activeTheme.codeBackgroundColor} !important;
|
|
190
|
+
border: 1px solid ${theme === 'dark' ? '#3e3e3e' : '#e0e0e0'};
|
|
191
|
+
overflow: hidden; /* Ensures rounded corners */
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/* 2. The Copy Button (Stays fixed in top right of container) */
|
|
195
|
+
.copy-btn {
|
|
196
|
+
position: absolute;
|
|
197
|
+
top: 6px;
|
|
198
|
+
right: 6px;
|
|
199
|
+
background: ${
|
|
200
|
+
theme === 'dark' ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.05)'
|
|
201
|
+
};
|
|
202
|
+
color: ${theme === 'dark' ? '#ccc' : '#666'};
|
|
203
|
+
border: 1px solid ${theme === 'dark' ? '#555' : '#ddd'};
|
|
204
|
+
border-radius: 4px;
|
|
205
|
+
padding: 4px 10px;
|
|
206
|
+
font-size: 12px;
|
|
207
|
+
font-weight: 600;
|
|
208
|
+
cursor: pointer;
|
|
209
|
+
z-index: 10;
|
|
210
|
+
}
|
|
211
|
+
.copy-btn:active { transform: scale(0.95); opacity: 0.8; }
|
|
212
|
+
|
|
213
|
+
/* Code Block Container */
|
|
214
|
+
pre {
|
|
215
|
+
background-color: ${
|
|
216
|
+
theme === 'dark'
|
|
217
|
+
? defaultDarkTheme.codeBackgroundColor
|
|
218
|
+
: defaultLightTheme.codeBackgroundColor
|
|
219
|
+
} !important;
|
|
220
|
+
color: ${
|
|
221
|
+
theme === 'dark'
|
|
222
|
+
? defaultDarkTheme.textColor
|
|
223
|
+
: defaultLightTheme.textColor
|
|
224
|
+
} !important;
|
|
225
|
+
position: relative;
|
|
226
|
+
padding: 1rem;
|
|
227
|
+
border-radius: 0.5rem;
|
|
228
|
+
overflow-x: auto;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/* 4. The Code Font (Smaller & Sharper) */
|
|
232
|
+
code {
|
|
233
|
+
font-family: "Menlo", "Monaco", "Courier New", monospace !important;
|
|
234
|
+
font-size: 13px !important;
|
|
235
|
+
line-height: 1.5;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/* Inline code styling (not blocks) */
|
|
239
|
+
p > code {
|
|
240
|
+
background-color: rgba(0, 0, 0, 0.13);
|
|
241
|
+
padding: 2px 4px;
|
|
242
|
+
border-radius: 4px;
|
|
243
|
+
font-size: 13px;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
`;
|
|
247
|
+
|
|
248
|
+
const htmlSource = `
|
|
249
|
+
<!DOCTYPE html>
|
|
250
|
+
<html>
|
|
251
|
+
<head>
|
|
252
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
253
|
+
<link href="${prismThemeUrl}" rel="stylesheet" />
|
|
254
|
+
<style>${userStyles}</style>
|
|
255
|
+
</head>
|
|
256
|
+
<body class="markdownBody">
|
|
257
|
+
${htmlContent}
|
|
258
|
+
|
|
259
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js"></script>
|
|
260
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js"></script>
|
|
261
|
+
|
|
262
|
+
<script>
|
|
263
|
+
// 1. Add Copy Buttons to all <pre> blocks
|
|
264
|
+
document.querySelectorAll('pre').forEach((pre) => {
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
const wrapper = document.createElement('div');
|
|
268
|
+
wrapper.className = 'code-wrapper';
|
|
269
|
+
|
|
270
|
+
pre.parentNode.insertBefore(wrapper, pre);
|
|
271
|
+
wrapper.appendChild(pre);
|
|
272
|
+
|
|
273
|
+
// Create button
|
|
274
|
+
const button = document.createElement('button');
|
|
275
|
+
button.innerText = 'Copy';
|
|
276
|
+
button.className = 'copy-btn';
|
|
277
|
+
|
|
278
|
+
// Add click event
|
|
279
|
+
button.addEventListener('click', () => {
|
|
280
|
+
// Get code text
|
|
281
|
+
const code = pre.querySelector('code') ? pre.querySelector('code').innerText : pre.innerText;
|
|
282
|
+
|
|
283
|
+
// Send to React Native
|
|
284
|
+
window.ReactNativeWebView.postMessage(JSON.stringify({
|
|
285
|
+
type: 'copy',
|
|
286
|
+
payload: code
|
|
287
|
+
}));
|
|
288
|
+
|
|
289
|
+
// Visual feedback
|
|
290
|
+
const originalText = button.innerText;
|
|
291
|
+
button.innerText = 'Copied!';
|
|
292
|
+
setTimeout(() => { button.innerText = originalText; }, 2000);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
wrapper.appendChild(button);
|
|
296
|
+
|
|
297
|
+
if (window.Prism) {
|
|
298
|
+
window.Prism.highlightAll();
|
|
299
|
+
}
|
|
300
|
+
});
|
|
301
|
+
</script>
|
|
302
|
+
</body>
|
|
303
|
+
</html>
|
|
304
|
+
`;
|
|
305
|
+
|
|
306
|
+
// 5. Handle Messages from WebView (Copy to Clipboard)
|
|
307
|
+
const handleMessage = async (event: WebViewMessageEvent) => {
|
|
308
|
+
try {
|
|
309
|
+
const data = JSON.parse(event.nativeEvent.data);
|
|
310
|
+
if (data.type === 'copy') {
|
|
311
|
+
await Clipboard.setStringAsync(data.payload);
|
|
312
|
+
}
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error('Error parsing webview message', error);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
return (
|
|
319
|
+
<View style={styles.container}>
|
|
320
|
+
<WebView
|
|
321
|
+
originWhitelist={['*']}
|
|
322
|
+
source={{ html: htmlSource }}
|
|
323
|
+
onMessage={handleMessage}
|
|
324
|
+
javaScriptEnabled={true}
|
|
325
|
+
domStorageEnabled={true}
|
|
326
|
+
startInLoadingState={true}
|
|
327
|
+
renderLoading={() => <ActivityIndicator size="large" />}
|
|
328
|
+
style={{ flex: 1, backgroundColor: 'transparent' }}
|
|
329
|
+
/>
|
|
330
|
+
</View>
|
|
331
|
+
);
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const styles = StyleSheet.create({
|
|
335
|
+
container: {
|
|
336
|
+
flex: 1,
|
|
337
|
+
},
|
|
338
|
+
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export * from "./components/markdown";
|
|
1
|
+
export * from "./components/markdown";
|