shelving
Version:
Toolkit for using data in JavaScript.
64 lines (63 loc) • 3.11 kB
JavaScript
/**
* Parse a text string as Markdownish syntax and render it as a JSX node.
* - Syntax is not defined by this code, but by the rules supplied to it.
*
* @param input The string content possibly containing markup syntax, e.g. "This is a *bold* string.
* @param options An options object for the render.
* @param context The context to render in (defaults to `"block"`).
*
* @returns JSXNode, i.e. either a complete `JSXElement`, `null`, `undefined`, `string`, or an array of zero or more of those.
*/
export function renderMarkup(input, options, context = "block") {
const arr = Array.from(_parseString(input, options, context));
return !arr.length ? null : arr.length === 1 ? arr[0] : arr;
}
/**
* Parse a string to its corresponding JSX nodes in a given context.
* - This code is heavily inspired by `simple-markdown`, but intends to be even simpler (and faster) by always producing JSX elements.
*/
function* _parseString(
/** The input string. */
input,
/** Options that configure the render including the rules we're using. */
options,
/** The context for the render e.g. `"block"` */
context,
/** The offset of where we are in the _original_ string — this is used to compute the `key` property for the returned element. */
offset = 0) {
// The best matched rule is the one with the highest priority.
// If two have equal priority use the earliest match in the string.
let bestMatch;
let bestRule;
// Loop through all rules in the list and see if any match.
for (const rule of options.rules) {
const { priority, regexp, contexts } = rule;
// Skip rule if it has lower priority than the current best rule, or it doesn't apply in this context.
if ((!bestRule || priority >= bestRule.priority) && contexts.includes(context)) {
const match = regexp.exec(input);
// This rule is the best rule if it matches, has higher priority than the current best rule, or is earlier in the string than the current best rule.
if (match && (!bestRule || !bestMatch || priority > bestRule.priority || match.index < bestMatch.index)) {
bestRule = rule;
bestMatch = match;
}
}
}
// Was there a matching rule?
if (bestRule && bestMatch) {
const { index, 0: { length }, } = bestMatch;
const end = bestMatch.index + length;
// Parse the prefix and yield any elements.
if (bestMatch.index > 0)
yield* _parseString(input.slice(0, bestMatch.index), options, context);
// Yield the matched element.
yield bestRule.render(bestMatch, options, (offset + index).toString());
// Parse the suffix and yield any elements.
// Pass `end` as `offset` so that `key` for any yielded elements acknowledges its increased position in the string.
if (input.length > end)
yield* _parseString(input.slice(end), options, context, end);
}
else if (input.length) {
// Nothing matched so return the entire string because this is a text node.
yield input;
}
}