react-native-richify 1.0.3 → 1.0.4
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/lib/commonjs/components/RichTextInput.js +71 -46
- package/lib/commonjs/components/RichTextInput.js.map +1 -1
- package/lib/commonjs/constants/defaultStyles.js +26 -1
- package/lib/commonjs/constants/defaultStyles.js.map +1 -1
- package/lib/commonjs/hooks/useFormatting.js +7 -1
- package/lib/commonjs/hooks/useFormatting.js.map +1 -1
- package/lib/commonjs/hooks/useRichText.js +55 -6
- package/lib/commonjs/hooks/useRichText.js.map +1 -1
- package/lib/commonjs/index.d.js +19 -0
- package/lib/commonjs/index.d.js.map +1 -1
- package/lib/commonjs/index.js +19 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/utils/formatter.js +1 -4
- package/lib/commonjs/utils/formatter.js.map +1 -1
- package/lib/commonjs/utils/serializer.d.js +6 -0
- package/lib/commonjs/utils/serializer.d.js.map +1 -0
- package/lib/commonjs/utils/serializer.js +163 -0
- package/lib/commonjs/utils/serializer.js.map +1 -0
- package/lib/module/components/RichTextInput.js +73 -49
- package/lib/module/components/RichTextInput.js.map +1 -1
- package/lib/module/constants/defaultStyles.js +26 -1
- package/lib/module/constants/defaultStyles.js.map +1 -1
- package/lib/module/hooks/useFormatting.js +7 -1
- package/lib/module/hooks/useFormatting.js.map +1 -1
- package/lib/module/hooks/useRichText.js +55 -6
- package/lib/module/hooks/useRichText.js.map +1 -1
- package/lib/module/index.d.js +1 -0
- package/lib/module/index.d.js.map +1 -1
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/formatter.js +1 -4
- package/lib/module/utils/formatter.js.map +1 -1
- package/lib/module/utils/serializer.d.js +4 -0
- package/lib/module/utils/serializer.d.js.map +1 -0
- package/lib/module/utils/serializer.js +157 -0
- package/lib/module/utils/serializer.js.map +1 -0
- package/lib/typescript/src/components/RichTextInput.d.ts +2 -13
- package/lib/typescript/src/components/RichTextInput.d.ts.map +1 -1
- package/lib/typescript/src/constants/defaultStyles.d.ts +2 -0
- package/lib/typescript/src/constants/defaultStyles.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useFormatting.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useRichText.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +2 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +19 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/formatter.d.ts.map +1 -1
- package/lib/typescript/src/utils/serializer.d.ts +14 -0
- package/lib/typescript/src/utils/serializer.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/components/RichTextInput.d.ts +3 -14
- package/src/components/RichTextInput.tsx +107 -50
- package/src/constants/defaultStyles.d.ts +3 -1
- package/src/constants/defaultStyles.ts +26 -1
- package/src/hooks/useFormatting.ts +14 -1
- package/src/hooks/useRichText.ts +92 -6
- package/src/index.d.ts +2 -1
- package/src/index.ts +6 -0
- package/src/types/index.d.ts +19 -1
- package/src/types/index.ts +20 -1
- package/src/utils/formatter.ts +1 -5
- package/src/utils/serializer.d.ts +13 -0
- package/src/utils/serializer.ts +223 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { OutputFormat, StyledSegment } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Serialize styled segments as Markdown or HTML.
|
|
4
|
+
*/
|
|
5
|
+
export declare function serializeSegments(segments: StyledSegment[], format?: OutputFormat): string;
|
|
6
|
+
/**
|
|
7
|
+
* Convenience wrapper for Markdown output.
|
|
8
|
+
*/
|
|
9
|
+
export declare function segmentsToMarkdown(segments: StyledSegment[]): string;
|
|
10
|
+
/**
|
|
11
|
+
* Convenience wrapper for HTML output.
|
|
12
|
+
*/
|
|
13
|
+
export declare function segmentsToHTML(segments: StyledSegment[]): string;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FormatStyle,
|
|
3
|
+
HeadingLevel,
|
|
4
|
+
OutputFormat,
|
|
5
|
+
StyledSegment,
|
|
6
|
+
} from '../types';
|
|
7
|
+
|
|
8
|
+
type LineFragment = Pick<StyledSegment, 'text' | 'styles'>;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Serialize styled segments as Markdown or HTML.
|
|
12
|
+
*/
|
|
13
|
+
export function serializeSegments(
|
|
14
|
+
segments: StyledSegment[],
|
|
15
|
+
format: OutputFormat = 'markdown',
|
|
16
|
+
): string {
|
|
17
|
+
const lines = splitSegmentsByLine(segments);
|
|
18
|
+
return lines.map((line) => serializeLine(line, format)).join('\n');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Convenience wrapper for Markdown output.
|
|
23
|
+
*/
|
|
24
|
+
export function segmentsToMarkdown(segments: StyledSegment[]): string {
|
|
25
|
+
return serializeSegments(segments, 'markdown');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convenience wrapper for HTML output.
|
|
30
|
+
*/
|
|
31
|
+
export function segmentsToHTML(segments: StyledSegment[]): string {
|
|
32
|
+
return serializeSegments(segments, 'html');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function splitSegmentsByLine(segments: StyledSegment[]): LineFragment[][] {
|
|
36
|
+
const lines: LineFragment[][] = [[]];
|
|
37
|
+
|
|
38
|
+
for (const segment of segments) {
|
|
39
|
+
const parts = segment.text.split('\n');
|
|
40
|
+
|
|
41
|
+
parts.forEach((part, index) => {
|
|
42
|
+
if (part.length > 0) {
|
|
43
|
+
lines[lines.length - 1]?.push({
|
|
44
|
+
text: part,
|
|
45
|
+
styles: { ...segment.styles },
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (index < parts.length - 1) {
|
|
50
|
+
lines.push([]);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return lines;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function serializeLine(
|
|
59
|
+
line: LineFragment[],
|
|
60
|
+
format: OutputFormat,
|
|
61
|
+
): string {
|
|
62
|
+
const heading = getLineHeading(line);
|
|
63
|
+
const content = line
|
|
64
|
+
.map((fragment) => serializeFragment(fragment, format, heading))
|
|
65
|
+
.join('');
|
|
66
|
+
|
|
67
|
+
if (format === 'html') {
|
|
68
|
+
const blockTag = heading ?? 'p';
|
|
69
|
+
return `<${blockTag}>${content}</${blockTag}>`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const headingPrefix = getHeadingPrefix(heading);
|
|
73
|
+
if (!headingPrefix) {
|
|
74
|
+
return content;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return content.length > 0 ? `${headingPrefix} ${content}` : headingPrefix;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function serializeFragment(
|
|
81
|
+
fragment: LineFragment,
|
|
82
|
+
format: OutputFormat,
|
|
83
|
+
lineHeading?: HeadingLevel,
|
|
84
|
+
): string {
|
|
85
|
+
const normalizedStyles: FormatStyle = {
|
|
86
|
+
...fragment.styles,
|
|
87
|
+
heading: undefined,
|
|
88
|
+
// Markdown headings already express emphasis at the block level.
|
|
89
|
+
bold:
|
|
90
|
+
lineHeading && lineHeading !== 'none' ? false : fragment.styles.bold,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
return format === 'html'
|
|
94
|
+
? serializeHtmlFragment(fragment.text, normalizedStyles)
|
|
95
|
+
: serializeMarkdownFragment(fragment.text, normalizedStyles);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function serializeHtmlFragment(text: string, styles: FormatStyle): string {
|
|
99
|
+
let result = escapeHtml(text);
|
|
100
|
+
|
|
101
|
+
if (styles.code) {
|
|
102
|
+
result = `<code>${result}</code>`;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (styles.bold) {
|
|
106
|
+
result = `<strong>${result}</strong>`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (styles.italic) {
|
|
110
|
+
result = `<em>${result}</em>`;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (styles.underline) {
|
|
114
|
+
result = `<u>${result}</u>`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (styles.strikethrough) {
|
|
118
|
+
result = `<s>${result}</s>`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const styleAttribute = buildInlineStyle(styles);
|
|
122
|
+
if (styleAttribute) {
|
|
123
|
+
result = `<span style="${styleAttribute}">${result}</span>`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function serializeMarkdownFragment(text: string, styles: FormatStyle): string {
|
|
130
|
+
let result = escapeMarkdown(text);
|
|
131
|
+
|
|
132
|
+
if (styles.code) {
|
|
133
|
+
result = wrapInlineCode(text);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (styles.bold) {
|
|
137
|
+
result = `**${result}**`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (styles.italic) {
|
|
141
|
+
result = `*${result}*`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (styles.strikethrough) {
|
|
145
|
+
result = `~~${result}~~`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (styles.underline) {
|
|
149
|
+
result = `<u>${result}</u>`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const styleAttribute = buildInlineStyle(styles);
|
|
153
|
+
if (styleAttribute) {
|
|
154
|
+
result = `<span style="${styleAttribute}">${result}</span>`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return result;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildInlineStyle(styles: FormatStyle): string {
|
|
161
|
+
const cssRules: string[] = [];
|
|
162
|
+
|
|
163
|
+
if (styles.color) {
|
|
164
|
+
cssRules.push(`color: ${styles.color}`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (styles.backgroundColor) {
|
|
168
|
+
cssRules.push(`background-color: ${styles.backgroundColor}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (styles.fontSize) {
|
|
172
|
+
cssRules.push(`font-size: ${styles.fontSize}px`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
return cssRules.join('; ');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function getLineHeading(line: LineFragment[]): HeadingLevel | undefined {
|
|
179
|
+
for (const fragment of line) {
|
|
180
|
+
if (fragment.styles.heading && fragment.styles.heading !== 'none') {
|
|
181
|
+
return fragment.styles.heading;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function getHeadingPrefix(heading?: HeadingLevel): string | undefined {
|
|
189
|
+
switch (heading) {
|
|
190
|
+
case 'h1':
|
|
191
|
+
return '#';
|
|
192
|
+
case 'h2':
|
|
193
|
+
return '##';
|
|
194
|
+
case 'h3':
|
|
195
|
+
return '###';
|
|
196
|
+
default:
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function escapeHtml(text: string): string {
|
|
202
|
+
return text
|
|
203
|
+
.replaceAll('&', '&')
|
|
204
|
+
.replaceAll('<', '<')
|
|
205
|
+
.replaceAll('>', '>')
|
|
206
|
+
.replaceAll('"', '"')
|
|
207
|
+
.replaceAll("'", ''');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function escapeMarkdown(text: string): string {
|
|
211
|
+
return text.replace(/([\\`*_~[\]])/g, '\\$1');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function wrapInlineCode(text: string): string {
|
|
215
|
+
const matches = text.match(/`+/g);
|
|
216
|
+
const longestBacktickRun = matches?.reduce(
|
|
217
|
+
(max, match) => Math.max(max, match.length),
|
|
218
|
+
0,
|
|
219
|
+
) ?? 0;
|
|
220
|
+
const fence = '`'.repeat(longestBacktickRun + 1);
|
|
221
|
+
|
|
222
|
+
return `${fence}${text}${fence}`;
|
|
223
|
+
}
|