UNPKG

interweave

Version:

React library to safely render HTML, filter attributes, autowrap text, autolink, and much more.

415 lines (370 loc) 10.4 kB
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } // Bundled with Packemon: https://packemon.dev // Platform: browser, Support: stable, Format: esm import React from 'react'; /* eslint-disable no-bitwise, no-magic-numbers, sort-keys */ // https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Content_categories const TYPE_FLOW = 1; const TYPE_SECTION = 1 << 1; const TYPE_HEADING = 1 << 2; const TYPE_PHRASING = 1 << 3; const TYPE_EMBEDDED = 1 << 4; const TYPE_INTERACTIVE = 1 << 5; const TYPE_PALPABLE = 1 << 6; // https://developer.mozilla.org/en-US/docs/Web/HTML/Element const tagConfigs = { a: { content: TYPE_FLOW | TYPE_PHRASING, self: false, type: TYPE_FLOW | TYPE_PHRASING | TYPE_INTERACTIVE | TYPE_PALPABLE }, address: { invalid: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'address', 'article', 'aside', 'section', 'div', 'header', 'footer'], self: false }, audio: { children: ['track', 'source'] }, br: { type: TYPE_FLOW | TYPE_PHRASING, void: true }, body: { content: TYPE_FLOW | TYPE_SECTION | TYPE_HEADING | TYPE_PHRASING | TYPE_EMBEDDED | TYPE_INTERACTIVE | TYPE_PALPABLE }, button: { content: TYPE_PHRASING, type: TYPE_FLOW | TYPE_PHRASING | TYPE_INTERACTIVE | TYPE_PALPABLE }, caption: { content: TYPE_FLOW, parent: ['table'] }, col: { parent: ['colgroup'], void: true }, colgroup: { children: ['col'], parent: ['table'] }, details: { children: ['summary'], type: TYPE_FLOW | TYPE_INTERACTIVE | TYPE_PALPABLE }, dd: { content: TYPE_FLOW, parent: ['dl'] }, dl: { children: ['dt', 'dd'], type: TYPE_FLOW }, dt: { content: TYPE_FLOW, invalid: ['footer', 'header'], parent: ['dl'] }, figcaption: { content: TYPE_FLOW, parent: ['figure'] }, footer: { invalid: ['footer', 'header'] }, header: { invalid: ['footer', 'header'] }, hr: { type: TYPE_FLOW, void: true }, img: { void: true }, li: { content: TYPE_FLOW, parent: ['ul', 'ol', 'menu'] }, main: { self: false }, ol: { children: ['li'], type: TYPE_FLOW }, picture: { children: ['source', 'img'], type: TYPE_FLOW | TYPE_PHRASING | TYPE_EMBEDDED }, rb: { parent: ['ruby', 'rtc'] }, rp: { parent: ['ruby', 'rtc'] }, rt: { content: TYPE_PHRASING, parent: ['ruby', 'rtc'] }, rtc: { content: TYPE_PHRASING, parent: ['ruby'] }, ruby: { children: ['rb', 'rp', 'rt', 'rtc'] }, source: { parent: ['audio', 'video', 'picture'], void: true }, summary: { content: TYPE_PHRASING, parent: ['details'] }, table: { children: ['caption', 'colgroup', 'thead', 'tbody', 'tfoot', 'tr'], type: TYPE_FLOW }, tbody: { parent: ['table'], children: ['tr'] }, td: { content: TYPE_FLOW, parent: ['tr'] }, tfoot: { parent: ['table'], children: ['tr'] }, th: { content: TYPE_FLOW, parent: ['tr'] }, thead: { parent: ['table'], children: ['tr'] }, tr: { parent: ['table', 'tbody', 'thead', 'tfoot'], children: ['th', 'td'] }, track: { parent: ['audio', 'video'], void: true }, ul: { children: ['li'], type: TYPE_FLOW }, video: { children: ['track', 'source'] }, wbr: { type: TYPE_FLOW | TYPE_PHRASING, void: true } }; function createConfigBuilder(config) { return tagName => { tagConfigs[tagName] = { ...config, ...tagConfigs[tagName] }; }; } ['address', 'main', 'div', 'figure', 'p', 'pre'].forEach(createConfigBuilder({ content: TYPE_FLOW, type: TYPE_FLOW | TYPE_PALPABLE })); ['abbr', 'b', 'bdi', 'bdo', 'cite', 'code', 'data', 'dfn', 'em', 'i', 'kbd', 'mark', 'q', 'ruby', 'samp', 'strong', 'sub', 'sup', 'time', 'u', 'var'].forEach(createConfigBuilder({ content: TYPE_PHRASING, type: TYPE_FLOW | TYPE_PHRASING | TYPE_PALPABLE })); ['p', 'pre'].forEach(createConfigBuilder({ content: TYPE_PHRASING, type: TYPE_FLOW | TYPE_PALPABLE })); ['s', 'small', 'span', 'del', 'ins'].forEach(createConfigBuilder({ content: TYPE_PHRASING, type: TYPE_FLOW | TYPE_PHRASING })); ['article', 'aside', 'footer', 'header', 'nav', 'section', 'blockquote'].forEach(createConfigBuilder({ content: TYPE_FLOW, type: TYPE_FLOW | TYPE_SECTION | TYPE_PALPABLE })); ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].forEach(createConfigBuilder({ content: TYPE_PHRASING, type: TYPE_FLOW | TYPE_HEADING | TYPE_PALPABLE })); ['audio', 'canvas', 'iframe', 'img', 'video'].forEach(createConfigBuilder({ type: TYPE_FLOW | TYPE_PHRASING | TYPE_EMBEDDED | TYPE_PALPABLE })); // Disable this map from being modified const TAGS = Object.freeze(tagConfigs); // Tags that should never be allowed, even if the allow list is disabled const BANNED_TAG_LIST = ['applet', 'base', 'body', 'command', 'embed', 'frame', 'frameset', 'head', 'html', 'link', 'meta', 'noscript', 'object', 'script', 'style', 'title']; const ALLOWED_TAG_LIST = Object.keys(TAGS).filter(tag => tag !== 'canvas' && tag !== 'iframe'); // Filters apply to HTML attributes const FILTER_ALLOW = 1; const FILTER_DENY = 2; const FILTER_CAST_NUMBER = 3; const FILTER_CAST_BOOL = 4; const FILTER_NO_CAST = 5; // Attributes not listed here will be denied // https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes const ATTRIBUTES = Object.freeze({ alt: FILTER_ALLOW, cite: FILTER_ALLOW, class: FILTER_ALLOW, colspan: FILTER_CAST_NUMBER, controls: FILTER_CAST_BOOL, datetime: FILTER_ALLOW, default: FILTER_CAST_BOOL, disabled: FILTER_CAST_BOOL, dir: FILTER_ALLOW, height: FILTER_ALLOW, href: FILTER_ALLOW, id: FILTER_ALLOW, kind: FILTER_ALLOW, label: FILTER_ALLOW, lang: FILTER_ALLOW, loading: FILTER_ALLOW, loop: FILTER_CAST_BOOL, media: FILTER_ALLOW, muted: FILTER_CAST_BOOL, poster: FILTER_ALLOW, rel: FILTER_ALLOW, role: FILTER_ALLOW, rowspan: FILTER_CAST_NUMBER, scope: FILTER_ALLOW, sizes: FILTER_ALLOW, span: FILTER_CAST_NUMBER, start: FILTER_CAST_NUMBER, style: FILTER_NO_CAST, src: FILTER_ALLOW, srclang: FILTER_ALLOW, srcset: FILTER_ALLOW, tabindex: FILTER_ALLOW, target: FILTER_ALLOW, title: FILTER_ALLOW, type: FILTER_ALLOW, width: FILTER_ALLOW }); // Attributes to camel case for React props const ATTRIBUTES_TO_PROPS = Object.freeze({ class: 'className', colspan: 'colSpan', datetime: 'dateTime', rowspan: 'rowSpan', srclang: 'srcLang', srcset: 'srcSet', tabindex: 'tabIndex' }); function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function Element({ attributes = {}, className, children = null, selfClose = false, tagName }) { const Tag = tagName; return selfClose ? /*#__PURE__*/React.createElement(Tag, _extends({ className: className }, attributes)) : /*#__PURE__*/React.createElement(Tag, _extends({ className: className }, attributes), children); } class Filter { /** * Filter and clean an HTML attribute value. */ attribute(name, value) { // eslint-disable-next-line @typescript-eslint/no-unsafe-return return value; } /** * Filter and clean an HTML node. */ node(name, node) { return node; } } /** * Trigger the actual pattern match and package the matched * response through a callback. */ function match(string, pattern, process, isVoid = false) { const matches = string.match(pattern instanceof RegExp ? pattern : new RegExp(pattern, 'i')); if (!matches) { return null; } return { match: matches[0], void: isVoid, ...process(matches), index: matches.index, length: matches[0].length, valid: true }; } class Matcher { constructor(name, options, factory) { _defineProperty(this, "greedy", false); _defineProperty(this, "options", void 0); _defineProperty(this, "propName", void 0); _defineProperty(this, "inverseName", void 0); _defineProperty(this, "factory", void 0); if (process.env.NODE_ENV !== "production" && (!name || name.toLowerCase() === 'html')) { throw new Error(`The matcher name "${name}" is not allowed.`); } // @ts-expect-error Allow override this.options = { ...options }; this.propName = name; this.inverseName = `no${name.charAt(0).toUpperCase() + name.slice(1)}`; this.factory = factory !== null && factory !== void 0 ? factory : null; } /** * Attempts to create a React element using a custom user provided factory, * or the default matcher factory. */ createElement(children, props) { const element = this.factory ? /*#__PURE__*/React.createElement(this.factory, props, children) : this.replaceWith(children, props); if (process.env.NODE_ENV !== "production" && typeof element !== 'string' && ! /*#__PURE__*/React.isValidElement(element)) { throw new Error(`Invalid React element created from ${this.constructor.name}.`); } return element; } /** * Trigger the actual pattern match and package the matched * response through a callback. */ doMatch(string, pattern, callback, isVoid = false) { return match(string, pattern, callback, isVoid); } /** * Callback triggered before parsing. */ onBeforeParse(content, props) { return content; } /** * Callback triggered after parsing. */ onAfterParse(content, props) { return content; } /** * Replace the match with a React element based on the matched token and optional props. */ } export { ALLOWED_TAG_LIST as A, BANNED_TAG_LIST as B, Element as E, Filter as F, Matcher as M, TAGS as T, _extends as _, ATTRIBUTES as a, FILTER_DENY as b, ATTRIBUTES_TO_PROPS as c, FILTER_CAST_BOOL as d, FILTER_CAST_NUMBER as e, FILTER_NO_CAST as f, TYPE_FLOW as g, TYPE_SECTION as h, TYPE_HEADING as i, TYPE_PHRASING as j, TYPE_EMBEDDED as k, TYPE_INTERACTIVE as l, TYPE_PALPABLE as m, FILTER_ALLOW as n, match as o }; //# sourceMappingURL=bundle-7aab7250.js.map