react-native-richify 1.0.4 → 1.0.5
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/RenderedOutput.js +168 -0
- package/lib/commonjs/components/RenderedOutput.js.map +1 -0
- package/lib/commonjs/components/RichTextInput.js +134 -15
- package/lib/commonjs/components/RichTextInput.js.map +1 -1
- package/lib/commonjs/components/Toolbar.js +41 -2
- package/lib/commonjs/components/Toolbar.js.map +1 -1
- package/lib/commonjs/constants/defaultStyles.js +55 -1
- package/lib/commonjs/constants/defaultStyles.js.map +1 -1
- package/lib/commonjs/hooks/useFormatting.js +40 -2
- package/lib/commonjs/hooks/useFormatting.js.map +1 -1
- package/lib/commonjs/hooks/useRichText.js +75 -6
- package/lib/commonjs/hooks/useRichText.js.map +1 -1
- package/lib/commonjs/utils/formatter.js +48 -9
- package/lib/commonjs/utils/formatter.js.map +1 -1
- package/lib/commonjs/utils/parser.js +1 -1
- package/lib/commonjs/utils/parser.js.map +1 -1
- package/lib/commonjs/utils/serializer.js +102 -6
- package/lib/commonjs/utils/serializer.js.map +1 -1
- package/lib/commonjs/utils/styleMapper.js +11 -0
- package/lib/commonjs/utils/styleMapper.js.map +1 -1
- package/lib/module/components/RenderedOutput.js +163 -0
- package/lib/module/components/RenderedOutput.js.map +1 -0
- package/lib/module/components/RichTextInput.js +135 -16
- package/lib/module/components/RichTextInput.js.map +1 -1
- package/lib/module/components/Toolbar.js +41 -2
- package/lib/module/components/Toolbar.js.map +1 -1
- package/lib/module/constants/defaultStyles.js +55 -1
- package/lib/module/constants/defaultStyles.js.map +1 -1
- package/lib/module/hooks/useFormatting.js +41 -3
- package/lib/module/hooks/useFormatting.js.map +1 -1
- package/lib/module/hooks/useRichText.js +75 -6
- package/lib/module/hooks/useRichText.js.map +1 -1
- package/lib/module/utils/formatter.js +46 -9
- package/lib/module/utils/formatter.js.map +1 -1
- package/lib/module/utils/parser.js +1 -1
- package/lib/module/utils/parser.js.map +1 -1
- package/lib/module/utils/serializer.js +102 -6
- package/lib/module/utils/serializer.js.map +1 -1
- package/lib/module/utils/styleMapper.js +11 -0
- package/lib/module/utils/styleMapper.js.map +1 -1
- package/lib/typescript/src/components/RenderedOutput.d.ts +9 -0
- package/lib/typescript/src/components/RenderedOutput.d.ts.map +1 -0
- package/lib/typescript/src/components/RichTextInput.d.ts.map +1 -1
- package/lib/typescript/src/components/Toolbar.d.ts.map +1 -1
- package/lib/typescript/src/constants/defaultStyles.d.ts +1 -0
- package/lib/typescript/src/constants/defaultStyles.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useFormatting.d.ts +4 -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 +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/types/index.d.ts +94 -1
- package/lib/typescript/src/types/index.d.ts.map +1 -1
- package/lib/typescript/src/utils/formatter.d.ts +9 -1
- package/lib/typescript/src/utils/formatter.d.ts.map +1 -1
- package/lib/typescript/src/utils/parser.d.ts.map +1 -1
- package/lib/typescript/src/utils/serializer.d.ts.map +1 -1
- package/lib/typescript/src/utils/styleMapper.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/RenderedOutput.tsx +231 -0
- package/src/components/RichTextInput.tsx +197 -19
- package/src/components/Toolbar.tsx +54 -2
- package/src/constants/defaultStyles.d.ts +2 -1
- package/src/constants/defaultStyles.ts +21 -0
- package/src/hooks/useFormatting.ts +76 -2
- package/src/hooks/useRichText.ts +101 -5
- package/src/index.d.ts +1 -1
- package/src/index.ts +4 -0
- package/src/types/index.d.ts +94 -1
- package/src/types/index.ts +104 -1
- package/src/utils/formatter.ts +60 -6
- package/src/utils/parser.ts +6 -1
- package/src/utils/serializer.ts +150 -8
- package/src/utils/styleMapper.ts +21 -0
package/src/utils/serializer.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
FormatStyle,
|
|
3
3
|
HeadingLevel,
|
|
4
|
+
ListType,
|
|
4
5
|
OutputFormat,
|
|
5
6
|
StyledSegment,
|
|
7
|
+
TextAlign,
|
|
6
8
|
} from '../types';
|
|
7
9
|
|
|
8
10
|
type LineFragment = Pick<StyledSegment, 'text' | 'styles'>;
|
|
@@ -15,7 +17,29 @@ export function serializeSegments(
|
|
|
15
17
|
format: OutputFormat = 'markdown',
|
|
16
18
|
): string {
|
|
17
19
|
const lines = splitSegmentsByLine(segments);
|
|
18
|
-
|
|
20
|
+
const blocks: string[] = [];
|
|
21
|
+
|
|
22
|
+
for (let index = 0; index < lines.length; ) {
|
|
23
|
+
const line = lines[index];
|
|
24
|
+
const listType = getLineListType(line);
|
|
25
|
+
|
|
26
|
+
if (listType && listType !== 'none') {
|
|
27
|
+
const listLines: LineFragment[][] = [];
|
|
28
|
+
|
|
29
|
+
while (index < lines.length && getLineListType(lines[index]) === listType) {
|
|
30
|
+
listLines.push(lines[index]);
|
|
31
|
+
index++;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
blocks.push(serializeListBlock(listLines, format, listType));
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
blocks.push(serializeBlockLine(line, format));
|
|
39
|
+
index++;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return blocks.join('\n');
|
|
19
43
|
}
|
|
20
44
|
|
|
21
45
|
/**
|
|
@@ -39,7 +63,7 @@ function splitSegmentsByLine(segments: StyledSegment[]): LineFragment[][] {
|
|
|
39
63
|
const parts = segment.text.split('\n');
|
|
40
64
|
|
|
41
65
|
parts.forEach((part, index) => {
|
|
42
|
-
if (part.length > 0) {
|
|
66
|
+
if (part.length > 0 || segment.styles.imageSrc) {
|
|
43
67
|
lines[lines.length - 1]?.push({
|
|
44
68
|
text: part,
|
|
45
69
|
styles: { ...segment.styles },
|
|
@@ -55,18 +79,48 @@ function splitSegmentsByLine(segments: StyledSegment[]): LineFragment[][] {
|
|
|
55
79
|
return lines;
|
|
56
80
|
}
|
|
57
81
|
|
|
58
|
-
function
|
|
82
|
+
function serializeListBlock(
|
|
83
|
+
lines: LineFragment[][],
|
|
84
|
+
format: OutputFormat,
|
|
85
|
+
listType: ListType,
|
|
86
|
+
): string {
|
|
87
|
+
if (format === 'html' || lines.some((line) => !!getLineTextAlign(line))) {
|
|
88
|
+
const tag = listType === 'ordered' ? 'ol' : 'ul';
|
|
89
|
+
const items = lines.map((line) => serializeHtmlListItem(line)).join('');
|
|
90
|
+
return `<${tag}>${items}</${tag}>`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return lines
|
|
94
|
+
.map((line, index) => {
|
|
95
|
+
const marker = listType === 'ordered' ? `${index + 1}.` : '-';
|
|
96
|
+
const content = serializeLineContent(line, format);
|
|
97
|
+
return content.length > 0 ? `${marker} ${content}` : marker;
|
|
98
|
+
})
|
|
99
|
+
.join('\n');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function serializeHtmlListItem(line: LineFragment[]): string {
|
|
103
|
+
const content = serializeLineContent(line, 'html');
|
|
104
|
+
const styleAttribute = buildBlockStyle(getLineTextAlign(line));
|
|
105
|
+
return `<li${styleAttribute ? ` style="${styleAttribute}"` : ''}>${content}</li>`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function serializeBlockLine(
|
|
59
109
|
line: LineFragment[],
|
|
60
110
|
format: OutputFormat,
|
|
61
111
|
): string {
|
|
62
112
|
const heading = getLineHeading(line);
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
.join('');
|
|
113
|
+
const textAlign = getLineTextAlign(line);
|
|
114
|
+
const content = serializeLineContent(line, format, heading);
|
|
66
115
|
|
|
67
116
|
if (format === 'html') {
|
|
68
117
|
const blockTag = heading ?? 'p';
|
|
69
|
-
|
|
118
|
+
const styleAttribute = buildBlockStyle(textAlign);
|
|
119
|
+
return `<${blockTag}${styleAttribute ? ` style="${styleAttribute}"` : ''}>${content}</${blockTag}>`;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (textAlign) {
|
|
123
|
+
return serializeAlignedMarkdownLine(content, heading, textAlign);
|
|
70
124
|
}
|
|
71
125
|
|
|
72
126
|
const headingPrefix = getHeadingPrefix(heading);
|
|
@@ -77,15 +131,42 @@ function serializeLine(
|
|
|
77
131
|
return content.length > 0 ? `${headingPrefix} ${content}` : headingPrefix;
|
|
78
132
|
}
|
|
79
133
|
|
|
134
|
+
function serializeAlignedMarkdownLine(
|
|
135
|
+
content: string,
|
|
136
|
+
heading: HeadingLevel | undefined,
|
|
137
|
+
textAlign: TextAlign,
|
|
138
|
+
): string {
|
|
139
|
+
const blockTag = heading ?? 'p';
|
|
140
|
+
const styleAttribute = buildBlockStyle(textAlign);
|
|
141
|
+
return `<${blockTag} style="${styleAttribute}">${content}</${blockTag}>`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function serializeLineContent(
|
|
145
|
+
line: LineFragment[],
|
|
146
|
+
format: OutputFormat,
|
|
147
|
+
lineHeading?: HeadingLevel,
|
|
148
|
+
): string {
|
|
149
|
+
return line
|
|
150
|
+
.map((fragment) => serializeFragment(fragment, format, lineHeading))
|
|
151
|
+
.join('');
|
|
152
|
+
}
|
|
153
|
+
|
|
80
154
|
function serializeFragment(
|
|
81
155
|
fragment: LineFragment,
|
|
82
156
|
format: OutputFormat,
|
|
83
157
|
lineHeading?: HeadingLevel,
|
|
84
158
|
): string {
|
|
159
|
+
if (fragment.styles.imageSrc) {
|
|
160
|
+
return serializeImageFragment(fragment, format);
|
|
161
|
+
}
|
|
162
|
+
|
|
85
163
|
const normalizedStyles: FormatStyle = {
|
|
86
164
|
...fragment.styles,
|
|
87
165
|
heading: undefined,
|
|
88
|
-
|
|
166
|
+
listType: undefined,
|
|
167
|
+
textAlign: undefined,
|
|
168
|
+
imageSrc: undefined,
|
|
169
|
+
imageAlt: undefined,
|
|
89
170
|
bold:
|
|
90
171
|
lineHeading && lineHeading !== 'none' ? false : fragment.styles.bold,
|
|
91
172
|
};
|
|
@@ -95,6 +176,20 @@ function serializeFragment(
|
|
|
95
176
|
: serializeMarkdownFragment(fragment.text, normalizedStyles);
|
|
96
177
|
}
|
|
97
178
|
|
|
179
|
+
function serializeImageFragment(
|
|
180
|
+
fragment: LineFragment,
|
|
181
|
+
format: OutputFormat,
|
|
182
|
+
): string {
|
|
183
|
+
const source = fragment.styles.imageSrc ?? '';
|
|
184
|
+
const altText = fragment.styles.imageAlt ?? extractImageAlt(fragment.text);
|
|
185
|
+
|
|
186
|
+
if (format === 'html') {
|
|
187
|
+
return `<img src="${escapeHtml(source)}" alt="${escapeHtml(altText)}" />`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return `})`;
|
|
191
|
+
}
|
|
192
|
+
|
|
98
193
|
function serializeHtmlFragment(text: string, styles: FormatStyle): string {
|
|
99
194
|
let result = escapeHtml(text);
|
|
100
195
|
|
|
@@ -123,6 +218,10 @@ function serializeHtmlFragment(text: string, styles: FormatStyle): string {
|
|
|
123
218
|
result = `<span style="${styleAttribute}">${result}</span>`;
|
|
124
219
|
}
|
|
125
220
|
|
|
221
|
+
if (styles.link) {
|
|
222
|
+
result = `<a href="${escapeHtml(styles.link)}">${result}</a>`;
|
|
223
|
+
}
|
|
224
|
+
|
|
126
225
|
return result;
|
|
127
226
|
}
|
|
128
227
|
|
|
@@ -154,6 +253,10 @@ function serializeMarkdownFragment(text: string, styles: FormatStyle): string {
|
|
|
154
253
|
result = `<span style="${styleAttribute}">${result}</span>`;
|
|
155
254
|
}
|
|
156
255
|
|
|
256
|
+
if (styles.link) {
|
|
257
|
+
result = `[${result}](${escapeMarkdownUrl(styles.link)})`;
|
|
258
|
+
}
|
|
259
|
+
|
|
157
260
|
return result;
|
|
158
261
|
}
|
|
159
262
|
|
|
@@ -175,6 +278,16 @@ function buildInlineStyle(styles: FormatStyle): string {
|
|
|
175
278
|
return cssRules.join('; ');
|
|
176
279
|
}
|
|
177
280
|
|
|
281
|
+
function buildBlockStyle(textAlign?: TextAlign): string {
|
|
282
|
+
const cssRules: string[] = [];
|
|
283
|
+
|
|
284
|
+
if (textAlign) {
|
|
285
|
+
cssRules.push(`text-align: ${textAlign}`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return cssRules.join('; ');
|
|
289
|
+
}
|
|
290
|
+
|
|
178
291
|
function getLineHeading(line: LineFragment[]): HeadingLevel | undefined {
|
|
179
292
|
for (const fragment of line) {
|
|
180
293
|
if (fragment.styles.heading && fragment.styles.heading !== 'none') {
|
|
@@ -185,6 +298,26 @@ function getLineHeading(line: LineFragment[]): HeadingLevel | undefined {
|
|
|
185
298
|
return undefined;
|
|
186
299
|
}
|
|
187
300
|
|
|
301
|
+
function getLineListType(line: LineFragment[]): ListType | undefined {
|
|
302
|
+
for (const fragment of line) {
|
|
303
|
+
if (fragment.styles.listType && fragment.styles.listType !== 'none') {
|
|
304
|
+
return fragment.styles.listType;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function getLineTextAlign(line: LineFragment[]): TextAlign | undefined {
|
|
312
|
+
for (const fragment of line) {
|
|
313
|
+
if (fragment.styles.textAlign) {
|
|
314
|
+
return fragment.styles.textAlign;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
|
|
188
321
|
function getHeadingPrefix(heading?: HeadingLevel): string | undefined {
|
|
189
322
|
switch (heading) {
|
|
190
323
|
case 'h1':
|
|
@@ -198,6 +331,11 @@ function getHeadingPrefix(heading?: HeadingLevel): string | undefined {
|
|
|
198
331
|
}
|
|
199
332
|
}
|
|
200
333
|
|
|
334
|
+
function extractImageAlt(text: string): string {
|
|
335
|
+
const normalized = text.replace(/^\[Image:\s*/i, '').replace(/^\[Image\]/i, '').replace(/\]$/, '').trim();
|
|
336
|
+
return normalized.length > 0 ? normalized : 'image';
|
|
337
|
+
}
|
|
338
|
+
|
|
201
339
|
function escapeHtml(text: string): string {
|
|
202
340
|
return text
|
|
203
341
|
.replaceAll('&', '&')
|
|
@@ -211,6 +349,10 @@ function escapeMarkdown(text: string): string {
|
|
|
211
349
|
return text.replace(/([\\`*_~[\]])/g, '\\$1');
|
|
212
350
|
}
|
|
213
351
|
|
|
352
|
+
function escapeMarkdownUrl(url: string): string {
|
|
353
|
+
return url.replaceAll(' ', '%20').replaceAll(')', '%29');
|
|
354
|
+
}
|
|
355
|
+
|
|
214
356
|
function wrapInlineCode(text: string): string {
|
|
215
357
|
const matches = text.match(/`+/g);
|
|
216
358
|
const longestBacktickRun = matches?.reduce(
|
package/src/utils/styleMapper.ts
CHANGED
|
@@ -55,6 +55,10 @@ export function formatStyleToTextStyle(
|
|
|
55
55
|
style.fontSize = formatStyle.fontSize;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
if (formatStyle.textAlign) {
|
|
59
|
+
style.textAlign = formatStyle.textAlign;
|
|
60
|
+
}
|
|
61
|
+
|
|
58
62
|
// Heading — overrides font size and weight
|
|
59
63
|
if (formatStyle.heading && formatStyle.heading !== 'none') {
|
|
60
64
|
style.fontSize = HEADING_FONT_SIZES[formatStyle.heading];
|
|
@@ -62,6 +66,23 @@ export function formatStyleToTextStyle(
|
|
|
62
66
|
style.lineHeight = HEADING_FONT_SIZES[formatStyle.heading] * 1.3;
|
|
63
67
|
}
|
|
64
68
|
|
|
69
|
+
if (formatStyle.link) {
|
|
70
|
+
style.color =
|
|
71
|
+
style.color ??
|
|
72
|
+
resolvedTheme.colors?.link ??
|
|
73
|
+
DEFAULT_THEME.colors?.link ??
|
|
74
|
+
DEFAULT_THEME.colors?.primary;
|
|
75
|
+
|
|
76
|
+
if (style.textDecorationLine === 'line-through') {
|
|
77
|
+
style.textDecorationLine = 'underline line-through';
|
|
78
|
+
} else if (
|
|
79
|
+
!style.textDecorationLine ||
|
|
80
|
+
style.textDecorationLine === 'none'
|
|
81
|
+
) {
|
|
82
|
+
style.textDecorationLine = 'underline';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
65
86
|
return style;
|
|
66
87
|
}
|
|
67
88
|
|