@flighthq/text-markup 0.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/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/markupClassStyles.d.ts +17 -0
- package/dist/markupClassStyles.d.ts.map +1 -0
- package/dist/markupClassStyles.js +19 -0
- package/dist/markupClassStyles.js.map +1 -0
- package/dist/markupNamedColors.d.ts +17 -0
- package/dist/markupNamedColors.d.ts.map +1 -0
- package/dist/markupNamedColors.js +180 -0
- package/dist/markupNamedColors.js.map +1 -0
- package/dist/markupTagRegistry.d.ts +41 -0
- package/dist/markupTagRegistry.d.ts.map +1 -0
- package/dist/markupTagRegistry.js +228 -0
- package/dist/markupTagRegistry.js.map +1 -0
- package/dist/textMarkup.d.ts +42 -0
- package/dist/textMarkup.d.ts.map +1 -0
- package/dist/textMarkup.js +365 -0
- package/dist/textMarkup.js.map +1 -0
- package/package.json +38 -0
- package/src/markupClassStyles.test.ts +65 -0
- package/src/markupNamedColors.test.ts +51 -0
- package/src/markupTagRegistry.test.ts +139 -0
- package/src/textMarkup.test.ts +248 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { createRichTextContent, createTextFormatRange } from '@flighthq/textlayout';
|
|
2
|
+
import { createMarkupTagRegistry, registerStandardMarkupTags } from './markupTagRegistry';
|
|
3
|
+
/**
|
|
4
|
+
* Serializes a `RichTextContent` back into `htmlText`-subset markup — the inverse of
|
|
5
|
+
* `parseTextMarkup` for everything the rich-text model can express. The emitted tag set is the fixed
|
|
6
|
+
* standard dialect that reproduces the `formatRanges`: `<b>`/`<i>`/`<u>`/`<s>` for the style booleans,
|
|
7
|
+
* `<font color size face>` for color/size/font, `<a href target>` for links, `<p align>` for
|
|
8
|
+
* alignment, `<li>` for bullets, and `<textformat …>` for the block metrics. Colors always emit as
|
|
9
|
+
* `#rrggbb` (a named-color source like `red` normalizes to `#ff0000`). Text is escaped (`&` `<` `>`);
|
|
10
|
+
* attribute values additionally escape `"`.
|
|
11
|
+
*
|
|
12
|
+
* The round-trip guarantee is `parseTextMarkup(formatTextMarkup(parseTextMarkup(x)))` equals
|
|
13
|
+
* `parseTextMarkup(x)` — a fixed point over the modeled tags. `<p>`/`<li>` imply a collapsing line
|
|
14
|
+
* break before their content; the resulting `\n` carries no block format and re-parses as a plain
|
|
15
|
+
* newline that the block tag's own collapse rule does not double, so the fixed point holds. Format
|
|
16
|
+
* fields with no `htmlText` representation (`kerning`, `letterSpacing`) cannot be expressed and are
|
|
17
|
+
* omitted; `parseTextMarkup` never produces them, so the fixed point is unaffected.
|
|
18
|
+
*/
|
|
19
|
+
export function formatTextMarkup(content) {
|
|
20
|
+
const text = content.text;
|
|
21
|
+
if (text.length === 0)
|
|
22
|
+
return '';
|
|
23
|
+
const formats = resolveMarkupFormats(content);
|
|
24
|
+
let output = '';
|
|
25
|
+
let runStart = 0;
|
|
26
|
+
while (runStart < text.length) {
|
|
27
|
+
const format = formats[runStart];
|
|
28
|
+
let runEnd = runStart + 1;
|
|
29
|
+
while (runEnd < text.length && equalsMarkupFormat(formats[runEnd], format))
|
|
30
|
+
runEnd++;
|
|
31
|
+
output += formatMarkupRun(format, text.slice(runStart, runEnd));
|
|
32
|
+
runStart = runEnd;
|
|
33
|
+
}
|
|
34
|
+
return output;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parses `htmlText`-style markup into Flight's rich-text model — a plain `text` string plus the
|
|
38
|
+
* `TextFormatRange[]` a `RichText`/`TextLabel` node renders. This is the explicit, Flight-way
|
|
39
|
+
* replacement for the `textField.htmlText = "…"` magic property: the caller invokes it and assigns
|
|
40
|
+
* the result, rather than the runtime silently parsing markup on assignment.
|
|
41
|
+
*
|
|
42
|
+
* Parsing is two layers. The parse layer here tokenizes the markup (a lenient, text-node-preserving
|
|
43
|
+
* pass — not the strict document tree of `@flighthq/xml`) and owns composition: it walks the tags,
|
|
44
|
+
* maintains a format stack (push a tag's handler contribution on open, pop on close), and emits the
|
|
45
|
+
* `text` and `TextFormatRange[]`. The meaning layer is the `registry` — an open map of tag name →
|
|
46
|
+
* handler that decides what each tag contributes. When no `registry` is passed the standard
|
|
47
|
+
* `htmlText` dialect is used (`registerStandardMarkupTags`), so the default is backward-compatible;
|
|
48
|
+
* pass a custom registry to add, replace, or narrow the supported tags.
|
|
49
|
+
*
|
|
50
|
+
* Nested tags compose — `<font color="#f00"><b>x</b></font>` yields one range carrying both `color`
|
|
51
|
+
* and `bold`. An unregistered tag keeps its enclosed text but applies no format. Entities
|
|
52
|
+
* (`& < > " ' &#nn; &#xhh;`) decode; unknown named entities are left verbatim.
|
|
53
|
+
*
|
|
54
|
+
* Malformed markup is recovered best-effort, never thrown: unclosed tags simply extend to the end of
|
|
55
|
+
* the text, a stray `<` with no `>` stays literal text, and an extra closing tag is ignored. The
|
|
56
|
+
* result is always a valid `RichTextContent`.
|
|
57
|
+
*/
|
|
58
|
+
export function parseTextMarkup(html, registry) {
|
|
59
|
+
const handlers = (registry ?? getDefaultMarkupTagRegistry()).handlers;
|
|
60
|
+
const content = createRichTextContent();
|
|
61
|
+
const stack = [{}];
|
|
62
|
+
const tagPattern = /<[^>]*>/g;
|
|
63
|
+
let index = 0;
|
|
64
|
+
let match;
|
|
65
|
+
while ((match = tagPattern.exec(html)) !== null) {
|
|
66
|
+
appendMarkupText(content, html.slice(index, match.index), stack[stack.length - 1]);
|
|
67
|
+
handleMarkupToken(content, handlers, match[0], stack);
|
|
68
|
+
index = match.index + match[0].length;
|
|
69
|
+
}
|
|
70
|
+
appendMarkupText(content, html.slice(index), stack[stack.length - 1]);
|
|
71
|
+
return content;
|
|
72
|
+
}
|
|
73
|
+
// Inserts a collapsing block break — the implicit newline before a `<p>`/`<li>`. Suppressed at the
|
|
74
|
+
// start of the output and against an existing trailing newline so block tags never stack blank lines.
|
|
75
|
+
// The break carries no format, which keeps it indistinguishable from a plain newline on re-parse and
|
|
76
|
+
// preserves the serialize/parse fixed point.
|
|
77
|
+
function appendMarkupBreakBefore(content) {
|
|
78
|
+
const text = content.text;
|
|
79
|
+
if (text.length === 0 || text.endsWith('\n'))
|
|
80
|
+
return;
|
|
81
|
+
appendMarkupString(content, '\n', emptyMarkupFormat);
|
|
82
|
+
}
|
|
83
|
+
// Appends a literal string (already decoded — a handler's `text`, or a decoded text node).
|
|
84
|
+
function appendMarkupString(content, value, format) {
|
|
85
|
+
if (value.length === 0)
|
|
86
|
+
return;
|
|
87
|
+
const start = content.text.length;
|
|
88
|
+
content.text += value;
|
|
89
|
+
pushMarkupRange(content.formatRanges, format, start, content.text.length);
|
|
90
|
+
}
|
|
91
|
+
// Appends a raw text node, decoding entities first.
|
|
92
|
+
function appendMarkupText(content, raw, format) {
|
|
93
|
+
appendMarkupString(content, decodeMarkupEntities(raw), format);
|
|
94
|
+
}
|
|
95
|
+
function codePointToString(code, fallback) {
|
|
96
|
+
if (!Number.isFinite(code) || code < 0 || code > 0x10ffff)
|
|
97
|
+
return fallback;
|
|
98
|
+
return String.fromCodePoint(code);
|
|
99
|
+
}
|
|
100
|
+
function decodeMarkupEntities(value) {
|
|
101
|
+
if (value.indexOf('&') === -1)
|
|
102
|
+
return value;
|
|
103
|
+
return value.replace(/&(#x[0-9a-f]+|#[0-9]+|[a-z]+);/gi, (matched, entity) => {
|
|
104
|
+
const lower = entity.toLowerCase();
|
|
105
|
+
if (lower.startsWith('#x'))
|
|
106
|
+
return codePointToString(Number.parseInt(lower.slice(2), 16), matched);
|
|
107
|
+
if (lower.startsWith('#'))
|
|
108
|
+
return codePointToString(Number.parseInt(lower.slice(1), 10), matched);
|
|
109
|
+
return markupNamedEntities[lower] ?? matched;
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function equalsMarkupFormat(a, b) {
|
|
113
|
+
const aKeys = Object.keys(a);
|
|
114
|
+
const bKeys = Object.keys(b);
|
|
115
|
+
if (aKeys.length !== bKeys.length)
|
|
116
|
+
return false;
|
|
117
|
+
for (const key of aKeys) {
|
|
118
|
+
const aValue = a[key];
|
|
119
|
+
const bValue = b[key];
|
|
120
|
+
if (Array.isArray(aValue) && Array.isArray(bValue)) {
|
|
121
|
+
if (aValue.length !== bValue.length)
|
|
122
|
+
return false;
|
|
123
|
+
for (let i = 0; i < aValue.length; i++) {
|
|
124
|
+
if (aValue[i] !== bValue[i])
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else if (aValue !== bValue) {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
function escapeMarkupAttribute(value) {
|
|
135
|
+
return value.replace(/[&<>"]/g, (character) => markupAttributeEscapes[character]);
|
|
136
|
+
}
|
|
137
|
+
function escapeMarkupText(value) {
|
|
138
|
+
return value.replace(/[&<>]/g, (character) => markupTextEscapes[character]);
|
|
139
|
+
}
|
|
140
|
+
function formatMarkupAnchorTag(format) {
|
|
141
|
+
let tag = '<a';
|
|
142
|
+
if (format.url !== undefined)
|
|
143
|
+
tag += ` href="${escapeMarkupAttribute(format.url)}"`;
|
|
144
|
+
if (format.target !== undefined)
|
|
145
|
+
tag += ` target="${escapeMarkupAttribute(format.target)}"`;
|
|
146
|
+
return `${tag}>`;
|
|
147
|
+
}
|
|
148
|
+
function formatMarkupColor(color) {
|
|
149
|
+
return `#${((color >>> 0) & 0xffffff).toString(16).padStart(6, '0')}`;
|
|
150
|
+
}
|
|
151
|
+
function formatMarkupFontTag(format) {
|
|
152
|
+
const hasColor = format.color !== undefined;
|
|
153
|
+
const hasSize = format.size !== undefined;
|
|
154
|
+
const hasFace = format.font !== undefined;
|
|
155
|
+
if (!hasColor && !hasSize && !hasFace)
|
|
156
|
+
return null;
|
|
157
|
+
let tag = '<font';
|
|
158
|
+
if (hasColor)
|
|
159
|
+
tag += ` color="${formatMarkupColor(format.color)}"`;
|
|
160
|
+
if (hasSize)
|
|
161
|
+
tag += ` size="${format.size}"`;
|
|
162
|
+
if (hasFace)
|
|
163
|
+
tag += ` face="${escapeMarkupAttribute(format.font)}"`;
|
|
164
|
+
return `${tag}>`;
|
|
165
|
+
}
|
|
166
|
+
function formatMarkupListTag(format) {
|
|
167
|
+
if (format.listMarker !== undefined)
|
|
168
|
+
return `<li type="${format.listMarker}">`;
|
|
169
|
+
return '<li>';
|
|
170
|
+
}
|
|
171
|
+
function formatMarkupRun(format, text) {
|
|
172
|
+
const open = [];
|
|
173
|
+
const close = [];
|
|
174
|
+
const textformat = formatMarkupTextformatTag(format);
|
|
175
|
+
if (textformat !== null) {
|
|
176
|
+
open.push(textformat);
|
|
177
|
+
close.unshift('</textformat>');
|
|
178
|
+
}
|
|
179
|
+
if (format.align !== undefined) {
|
|
180
|
+
open.push(`<p align="${format.align}">`);
|
|
181
|
+
close.unshift('</p>');
|
|
182
|
+
}
|
|
183
|
+
if (format.bullet === true) {
|
|
184
|
+
open.push(formatMarkupListTag(format));
|
|
185
|
+
close.unshift('</li>');
|
|
186
|
+
}
|
|
187
|
+
if (format.url !== undefined || format.target !== undefined) {
|
|
188
|
+
open.push(formatMarkupAnchorTag(format));
|
|
189
|
+
close.unshift('</a>');
|
|
190
|
+
}
|
|
191
|
+
const font = formatMarkupFontTag(format);
|
|
192
|
+
if (font !== null) {
|
|
193
|
+
open.push(font);
|
|
194
|
+
close.unshift('</font>');
|
|
195
|
+
}
|
|
196
|
+
if (format.bold === true) {
|
|
197
|
+
open.push('<b>');
|
|
198
|
+
close.unshift('</b>');
|
|
199
|
+
}
|
|
200
|
+
if (format.italic === true) {
|
|
201
|
+
open.push('<i>');
|
|
202
|
+
close.unshift('</i>');
|
|
203
|
+
}
|
|
204
|
+
if (format.underline === true) {
|
|
205
|
+
open.push('<u>');
|
|
206
|
+
close.unshift('</u>');
|
|
207
|
+
}
|
|
208
|
+
if (format.strikethrough === true) {
|
|
209
|
+
open.push('<s>');
|
|
210
|
+
close.unshift('</s>');
|
|
211
|
+
}
|
|
212
|
+
return open.join('') + escapeMarkupText(text) + close.join('');
|
|
213
|
+
}
|
|
214
|
+
function formatMarkupTextformatTag(format) {
|
|
215
|
+
let tag = '<textformat';
|
|
216
|
+
let any = false;
|
|
217
|
+
if (format.blockIndent !== undefined) {
|
|
218
|
+
tag += ` blockindent="${format.blockIndent}"`;
|
|
219
|
+
any = true;
|
|
220
|
+
}
|
|
221
|
+
if (format.indent !== undefined) {
|
|
222
|
+
tag += ` indent="${format.indent}"`;
|
|
223
|
+
any = true;
|
|
224
|
+
}
|
|
225
|
+
if (format.leading !== undefined) {
|
|
226
|
+
tag += ` leading="${format.leading}"`;
|
|
227
|
+
any = true;
|
|
228
|
+
}
|
|
229
|
+
if (format.leftMargin !== undefined) {
|
|
230
|
+
tag += ` leftmargin="${format.leftMargin}"`;
|
|
231
|
+
any = true;
|
|
232
|
+
}
|
|
233
|
+
if (format.rightMargin !== undefined) {
|
|
234
|
+
tag += ` rightmargin="${format.rightMargin}"`;
|
|
235
|
+
any = true;
|
|
236
|
+
}
|
|
237
|
+
if (format.tabStops !== undefined) {
|
|
238
|
+
tag += ` tabstops="${format.tabStops.join(',')}"`;
|
|
239
|
+
any = true;
|
|
240
|
+
}
|
|
241
|
+
return any ? `${tag}>` : null;
|
|
242
|
+
}
|
|
243
|
+
// The default standard-dialect registry, built lazily and memoized. Not a module-load side effect:
|
|
244
|
+
// nothing runs until the first `parseTextMarkup` call that omits a registry. Kept internal so the
|
|
245
|
+
// standard tags stay immutable from outside; a caller wanting custom tags builds their own registry.
|
|
246
|
+
function getDefaultMarkupTagRegistry() {
|
|
247
|
+
let registry = defaultMarkupTagRegistry;
|
|
248
|
+
if (registry === null) {
|
|
249
|
+
registry = createMarkupTagRegistry();
|
|
250
|
+
registerStandardMarkupTags(registry);
|
|
251
|
+
defaultMarkupTagRegistry = registry;
|
|
252
|
+
}
|
|
253
|
+
return registry;
|
|
254
|
+
}
|
|
255
|
+
// The dispatch + build binder: resolves one tag token against the registry and applies its result to
|
|
256
|
+
// the format stack and emitted content. Structure (tokenizing, the stack) stays free of TextFormat
|
|
257
|
+
// specifics beyond what a handler returns, so a general markup-binding engine could lift this shape
|
|
258
|
+
// out later against a different handler result type.
|
|
259
|
+
function handleMarkupToken(content, handlers, token, stack) {
|
|
260
|
+
// The tag body is everything between the angle brackets; drop comments (`<!-- -->`), doctypes
|
|
261
|
+
// (`<!…>`), processing instructions (`<?…>`), and the degenerate empty `<>`.
|
|
262
|
+
const inner = token.slice(1, -1).trim();
|
|
263
|
+
if (inner.length === 0 || inner.startsWith('!') || inner.startsWith('?'))
|
|
264
|
+
return;
|
|
265
|
+
const closing = inner.startsWith('/');
|
|
266
|
+
const selfClosing = inner.endsWith('/');
|
|
267
|
+
const body = (closing ? inner.slice(1) : inner).replace(/\/$/, '').trim();
|
|
268
|
+
const separator = body.search(/\s/);
|
|
269
|
+
const name = (separator === -1 ? body : body.slice(0, separator)).toLowerCase();
|
|
270
|
+
if (closing) {
|
|
271
|
+
// Guard the base format: an extra closing tag with nothing open is ignored, not an error.
|
|
272
|
+
if (stack.length > 1)
|
|
273
|
+
stack.pop();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const top = stack[stack.length - 1];
|
|
277
|
+
const handler = handlers.get(name);
|
|
278
|
+
if (handler === undefined) {
|
|
279
|
+
// Unregistered tag: keep the enclosed text, apply no format. Push a copy of the current format so
|
|
280
|
+
// the matching close pops it without disturbing an enclosing tag.
|
|
281
|
+
if (!selfClosing)
|
|
282
|
+
stack.push({ ...top });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const attributes = parseMarkupAttributes(separator === -1 ? '' : body.slice(separator + 1));
|
|
286
|
+
const result = normalizeMarkupTagResult(handler(attributes));
|
|
287
|
+
// A void insertion tag (text, no format) inserts literal text and never pushes — e.g. `<br>`.
|
|
288
|
+
if (result.format === undefined && result.text !== undefined) {
|
|
289
|
+
appendMarkupString(content, result.text, top);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (result.breakBefore === true)
|
|
293
|
+
appendMarkupBreakBefore(content);
|
|
294
|
+
if (result.text !== undefined)
|
|
295
|
+
appendMarkupString(content, result.text, top);
|
|
296
|
+
const merged = { ...top, ...result.format };
|
|
297
|
+
if (!selfClosing)
|
|
298
|
+
stack.push(merged);
|
|
299
|
+
}
|
|
300
|
+
// Normalizes a handler result to the `MarkupTagEffect` shape. The common `Partial<TextFormat>` return
|
|
301
|
+
// carries none of the reserved effect keys, so it is wrapped as `{ format }`; a richer return is used
|
|
302
|
+
// as-is. `TextFormat` shares no field name with `format`/`breakBefore`/`text`, so the test is exact.
|
|
303
|
+
function normalizeMarkupTagResult(result) {
|
|
304
|
+
if ('format' in result || 'breakBefore' in result || 'text' in result)
|
|
305
|
+
return result;
|
|
306
|
+
return { format: result };
|
|
307
|
+
}
|
|
308
|
+
function parseMarkupAttributes(source) {
|
|
309
|
+
const attributes = {};
|
|
310
|
+
const pattern = /([^\s=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'>]+)))?/g;
|
|
311
|
+
let match;
|
|
312
|
+
while ((match = pattern.exec(source)) !== null) {
|
|
313
|
+
const name = match[1].toLowerCase();
|
|
314
|
+
attributes[name] = decodeMarkupEntities(match[2] ?? match[3] ?? match[4] ?? '');
|
|
315
|
+
}
|
|
316
|
+
return attributes;
|
|
317
|
+
}
|
|
318
|
+
function pushMarkupRange(ranges, format, start, end) {
|
|
319
|
+
if (start === end)
|
|
320
|
+
return;
|
|
321
|
+
// Unformatted text carries no range — plain text produces an empty `formatRanges`.
|
|
322
|
+
if (Object.keys(format).length === 0)
|
|
323
|
+
return;
|
|
324
|
+
const previous = ranges[ranges.length - 1];
|
|
325
|
+
if (previous !== undefined && previous.end === start && equalsMarkupFormat(previous.format, format)) {
|
|
326
|
+
previous.end = end;
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
ranges.push(createTextFormatRange({ ...format }, start, end));
|
|
330
|
+
}
|
|
331
|
+
function resolveMarkupFormats(content) {
|
|
332
|
+
const length = content.text.length;
|
|
333
|
+
const formats = new Array(length);
|
|
334
|
+
for (let i = 0; i < length; i++)
|
|
335
|
+
formats[i] = {};
|
|
336
|
+
// Ranges apply in array order; a later range overrides an earlier one on overlap.
|
|
337
|
+
for (const range of content.formatRanges) {
|
|
338
|
+
const start = Math.max(0, Math.min(length, range.start));
|
|
339
|
+
const end = Math.max(start, Math.min(length, range.end));
|
|
340
|
+
for (let i = start; i < end; i++)
|
|
341
|
+
formats[i] = { ...formats[i], ...range.format };
|
|
342
|
+
}
|
|
343
|
+
return formats;
|
|
344
|
+
}
|
|
345
|
+
let defaultMarkupTagRegistry = null;
|
|
346
|
+
const emptyMarkupFormat = {};
|
|
347
|
+
const markupAttributeEscapes = {
|
|
348
|
+
'"': '"',
|
|
349
|
+
'&': '&',
|
|
350
|
+
'<': '<',
|
|
351
|
+
'>': '>',
|
|
352
|
+
};
|
|
353
|
+
const markupNamedEntities = {
|
|
354
|
+
amp: '&',
|
|
355
|
+
apos: "'",
|
|
356
|
+
gt: '>',
|
|
357
|
+
lt: '<',
|
|
358
|
+
quot: '"',
|
|
359
|
+
};
|
|
360
|
+
const markupTextEscapes = {
|
|
361
|
+
'&': '&',
|
|
362
|
+
'<': '<',
|
|
363
|
+
'>': '>',
|
|
364
|
+
};
|
|
365
|
+
//# sourceMappingURL=textMarkup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textMarkup.js","sourceRoot":"","sources":["../src/textMarkup.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@flighthq/text-markup",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"dist",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"!dist/**/*.test.js",
|
|
17
|
+
"!dist/**/*.test.d.ts",
|
|
18
|
+
"!dist/**/*.test.js.map",
|
|
19
|
+
"!dist/**/*.test.d.ts.map"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -b",
|
|
23
|
+
"clean": "tsc -b --clean",
|
|
24
|
+
"test": "vitest run --config vitest.config.ts",
|
|
25
|
+
"test:watch": "vitest --watch --config vitest.config.ts",
|
|
26
|
+
"prepack": "npm run clean && npm run clean:dist && npm run build",
|
|
27
|
+
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@flighthq/textlayout": "0.1.0",
|
|
31
|
+
"@flighthq/types": "0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^5.3.0"
|
|
35
|
+
},
|
|
36
|
+
"description": "Tree-shakable styled-text markup codec — parse/serialize the htmlText tag set to the rich-text model",
|
|
37
|
+
"sideEffects": false
|
|
38
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { RichTextContent, TextFormat } from '@flighthq/types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { registerMarkupClassStyles } from './markupClassStyles';
|
|
5
|
+
import { createMarkupTagRegistry, registerStandardMarkupTags } from './markupTagRegistry';
|
|
6
|
+
import { parseTextMarkup } from './textMarkup';
|
|
7
|
+
|
|
8
|
+
function formatAt(content: RichTextContent, index: number): TextFormat {
|
|
9
|
+
for (const range of content.formatRanges) {
|
|
10
|
+
if (index >= range.start && index < range.end) return range.format;
|
|
11
|
+
}
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('registerMarkupClassStyles', () => {
|
|
16
|
+
it('styles a span by its class after being registered', () => {
|
|
17
|
+
const registry = createMarkupTagRegistry();
|
|
18
|
+
registerStandardMarkupTags(registry);
|
|
19
|
+
registerMarkupClassStyles(registry, { warn: { bold: true, color: 0xff0000 } });
|
|
20
|
+
const content = parseTextMarkup('<span class="warn">x</span>', registry);
|
|
21
|
+
expect(content.text).toBe('x');
|
|
22
|
+
expect(formatAt(content, 0)).toEqual({ bold: true, color: 0xff0000 });
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('leaves a span inert when its class is not in the map', () => {
|
|
26
|
+
const registry = createMarkupTagRegistry();
|
|
27
|
+
registerStandardMarkupTags(registry);
|
|
28
|
+
registerMarkupClassStyles(registry, { warn: { bold: true } });
|
|
29
|
+
const content = parseTextMarkup('<span class="unknown">x</span>', registry);
|
|
30
|
+
expect(content.text).toBe('x');
|
|
31
|
+
expect(content.formatRanges).toEqual([]);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('merges several space-separated classes left to right', () => {
|
|
35
|
+
const registry = createMarkupTagRegistry();
|
|
36
|
+
registerStandardMarkupTags(registry);
|
|
37
|
+
registerMarkupClassStyles(registry, {
|
|
38
|
+
big: { size: 24 },
|
|
39
|
+
loud: { bold: true, color: 0x0000ff },
|
|
40
|
+
quiet: { color: 0x333333 },
|
|
41
|
+
});
|
|
42
|
+
// `loud` then `quiet` both name color; the later `quiet` wins the shared field, `size` survives.
|
|
43
|
+
expect(formatAt(parseTextMarkup('<span class="big loud quiet">x</span>', registry), 0)).toEqual({
|
|
44
|
+
bold: true,
|
|
45
|
+
color: 0x333333,
|
|
46
|
+
size: 24,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('matches class names case-sensitively', () => {
|
|
51
|
+
const registry = createMarkupTagRegistry();
|
|
52
|
+
registerStandardMarkupTags(registry);
|
|
53
|
+
registerMarkupClassStyles(registry, { Warn: { bold: true } });
|
|
54
|
+
expect(formatAt(parseTextMarkup('<span class="warn">x</span>', registry), 0)).toEqual({});
|
|
55
|
+
expect(formatAt(parseTextMarkup('<span class="Warn">x</span>', registry), 0)).toEqual({ bold: true });
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('is last-write-wins over a previously installed class resolver', () => {
|
|
59
|
+
const registry = createMarkupTagRegistry();
|
|
60
|
+
registerStandardMarkupTags(registry);
|
|
61
|
+
registerMarkupClassStyles(registry, { tag: { bold: true } });
|
|
62
|
+
registerMarkupClassStyles(registry, { tag: { italic: true } });
|
|
63
|
+
expect(formatAt(parseTextMarkup('<span class="tag">x</span>', registry), 0)).toEqual({ italic: true });
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { RichTextContent, TextFormat } from '@flighthq/types';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { registerMarkupNamedColors } from './markupNamedColors';
|
|
5
|
+
import { createMarkupTagRegistry, registerStandardMarkupTags } from './markupTagRegistry';
|
|
6
|
+
import { parseTextMarkup } from './textMarkup';
|
|
7
|
+
|
|
8
|
+
function formatAt(content: RichTextContent, index: number): TextFormat {
|
|
9
|
+
for (const range of content.formatRanges) {
|
|
10
|
+
if (index >= range.start && index < range.end) return range.format;
|
|
11
|
+
}
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe('registerMarkupNamedColors', () => {
|
|
16
|
+
it('resolves CSS named font colors after being registered', () => {
|
|
17
|
+
const registry = createMarkupTagRegistry();
|
|
18
|
+
registerStandardMarkupTags(registry);
|
|
19
|
+
registerMarkupNamedColors(registry);
|
|
20
|
+
expect(formatAt(parseTextMarkup('<font color="red">x</font>', registry), 0).color).toBe(0xff0000);
|
|
21
|
+
expect(formatAt(parseTextMarkup('<font color="RebeccaPurple">x</font>', registry), 0).color).toBe(0x663399);
|
|
22
|
+
expect(formatAt(parseTextMarkup('<font color="cornflowerblue">x</font>', registry), 0).color).toBe(0x6495ed);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('still resolves hex colors once named colors are registered', () => {
|
|
26
|
+
const registry = createMarkupTagRegistry();
|
|
27
|
+
registerStandardMarkupTags(registry);
|
|
28
|
+
registerMarkupNamedColors(registry);
|
|
29
|
+
expect(formatAt(parseTextMarkup('<font color="#f00">x</font>', registry), 0).color).toBe(0xff0000);
|
|
30
|
+
expect(formatAt(parseTextMarkup('<font color="0x00ff00">x</font>', registry), 0).color).toBe(0x00ff00);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('leaves an unknown color name unresolved rather than erroring', () => {
|
|
34
|
+
const registry = createMarkupTagRegistry();
|
|
35
|
+
registerStandardMarkupTags(registry);
|
|
36
|
+
registerMarkupNamedColors(registry);
|
|
37
|
+
expect(formatAt(parseTextMarkup('<font color="notacolor">x</font>', registry), 0).color).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('augments the color seam in place without disturbing the standard font handler', () => {
|
|
41
|
+
const registry = createMarkupTagRegistry();
|
|
42
|
+
registerStandardMarkupTags(registry);
|
|
43
|
+
registerMarkupNamedColors(registry);
|
|
44
|
+
// size and face still parse through the same `<font>` handler.
|
|
45
|
+
expect(formatAt(parseTextMarkup('<font color="red" size="24" face="Arial">x</font>', registry), 0)).toEqual({
|
|
46
|
+
color: 0xff0000,
|
|
47
|
+
font: 'Arial',
|
|
48
|
+
size: 24,
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
});
|