MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 124 lines 3.3 kB view raw
1const VOID = new Set(['br', 'hr', 'img', 'input', 'meta', 'link', 'area', 'base', 'col', 'embed', 'source', 'track', 'wbr']); 2 3const ESCAPE_MAP = { 4 '&': '&amp;', 5 '<': '&lt;', 6 '>': '&gt;', 7 '"': '&quot;', 8 "'": '&#39;' 9}; 10 11const ATTR_MAP = { 12 className: 'class', 13 htmlFor: 'for', 14 tabIndex: 'tabindex', 15 readOnly: 'readonly', 16 maxLength: 'maxlength', 17 cellSpacing: 'cellspacing', 18 cellPadding: 'cellpadding', 19 rowSpan: 'rowspan', 20 colSpan: 'colspan', 21 encType: 'enctype', 22 contentEditable: 'contenteditable', 23 crossOrigin: 'crossorigin', 24 accessKey: 'accesskey', 25 autoComplete: 'autocomplete', 26 autoFocus: 'autofocus', 27 autoPlay: 'autoplay', 28 formAction: 'formaction', 29 noValidate: 'novalidate' 30}; 31 32const UNITLESS = new Set([ 33 'animationIterationCount', 34 'columns', 35 'columnCount', 36 'flex', 37 'flexGrow', 38 'flexShrink', 39 'fontWeight', 40 'gridColumn', 41 'gridRow', 42 'lineHeight', 43 'opacity', 44 'order', 45 'orphans', 46 'tabSize', 47 'widows', 48 'zIndex' 49]); 50 51function camelToKebab(str) { 52 return str.replace(/[A-Z]/g, ch => `-${ch.toLowerCase()}`); 53} 54 55function escapeHTML(str) { 56 return String(str).replace(/[&<>"']/g, ch => ESCAPE_MAP[ch]); 57} 58 59function formatValue(key, val) { 60 if (typeof val === 'number' && val !== 0 && !UNITLESS.has(key)) return `${val}px`; 61 return val; 62} 63 64function styleToString(style) { 65 return Object.entries(style) 66 .filter(([, v]) => v != null && v !== '') 67 .map(([k, v]) => `${camelToKebab(k)}:${formatValue(k, v)}`) 68 .join(';'); 69} 70 71function renderAttrs(props) { 72 let result = ''; 73 for (const [key, val] of Object.entries(props)) { 74 if (key === 'children' || key === 'dangerouslySetInnerHTML') continue; 75 if (key.startsWith('on') && key[2] >= 'A' && key[2] <= 'Z') continue; 76 if (val == null || val === false) continue; 77 78 if (key === 'style' && typeof val === 'object') { 79 result += ` style="${escapeHTML(styleToString(val))}"`; 80 continue; 81 } 82 83 const attr = ATTR_MAP[key] || key; 84 if (val === true) result += ` ${attr}`; 85 else result += ` ${attr}="${escapeHTML(val)}"`; 86 } 87 return result; 88} 89 90export function renderToHTML(element) { 91 if (element == null || typeof element === 'boolean') return ''; 92 if (typeof element === 'string') return escapeHTML(element); 93 if (typeof element === 'number') return String(element); 94 if (Array.isArray(element)) return element.map(renderToHTML).join(''); 95 96 const { type, props } = element; 97 98 if (type === Symbol.for('react.fragment') || type === undefined) { 99 return renderChildren(props?.children); 100 } 101 102 if (typeof type === 'function') { 103 if (type.prototype && type.prototype.isReactComponent) { 104 const instance = new type(props); 105 return renderToHTML(instance.render()); 106 } 107 return renderToHTML(type(props)); 108 } 109 110 const attrStr = renderAttrs(props || {}); 111 if (VOID.has(type)) return `<${type}${attrStr}>`; 112 113 if (props?.dangerouslySetInnerHTML) { 114 return `<${type}${attrStr}>${props.dangerouslySetInnerHTML.__html}</${type}>`; 115 } 116 117 return `<${type}${attrStr}>${renderChildren(props?.children)}</${type}>`; 118} 119 120function renderChildren(children) { 121 if (children == null) return ''; 122 if (Array.isArray(children)) return children.map(renderToHTML).join(''); 123 return renderToHTML(children); 124}