UNPKG

solid-markdown

Version:
402 lines (397 loc) 12.1 kB
import { insert, createComponent, memo, effect, className, Dynamic, mergeProps as mergeProps$1, template } from 'solid-js/web'; import remarkParse from 'remark-parse'; import remarkRehype from 'remark-rehype'; import { mergeProps, createMemo, createRenderEffect, For, Switch, Match, Show } from 'solid-js'; import { createStore, reconcile } from 'solid-js/store'; import { html, find, svg } from 'property-information'; import { unified } from 'unified'; import { VFile } from 'vfile'; import { visit } from 'unist-util-visit'; import { stringify } from 'comma-separated-tokens'; import { stringify as stringify$1 } from 'space-separated-tokens'; // src/index.tsx var rehypeFilter = (options) => { if (options.allowedElements && options.disallowedElements) { throw new TypeError( "Only one of `allowedElements` and `disallowedElements` should be defined" ); } if (options.allowedElements || options.disallowedElements || options.allowElement) { return (tree) => { visit(tree, "element", (node, index, parent_) => { const parent = parent_; if (parent === null) return; let remove; if (options.allowedElements) { remove = !options.allowedElements.includes(node.tagName); } else if (options.disallowedElements) { remove = options.disallowedElements.includes(node.tagName); } if (!remove && options.allowElement && typeof index === "number" && parent) { remove = !options.allowElement(node, index, parent); } if (remove && typeof index === "number" && parent) { if (options.unwrapDisallowed && node.children) { parent.children.splice(index, 1, ...node.children); } else { parent.children.splice(index, 1); } return index; } return void 0; }); }; } }; var rehype_filter_default = rehypeFilter; function getInputElement(node) { let index = -1; while (++index < node.children.length) { const child = node.children[index]; if (child?.type === "element" && child?.tagName === "input") { return child; } } return null; } function getElementsBeforeCount(parent, node) { let index = -1; let count = 0; while (++index < parent.children.length) { if (parent.children[index] === node) break; if (parent.children[index]?.type === "element") count++; } return count; } function addProperty(props, prop, value, ctx) { const info = find(ctx.schema, prop); let result = value; if (info.property === "className") { info.property = "class"; } if (result === null || result === void 0 || result !== result) { return; } if (Array.isArray(result)) { result = info.commaSeparated ? stringify(result) : stringify$1(result); } if (info.space && info.property) { props[info.property] = result; } else if (info.attribute) { props[info.attribute] = result; } } function flattenPosition(pos) { return [ pos.start.line, ":", pos.start.column, "-", pos.end.line, ":", pos.end.column ].map((d) => String(d)).join(""); } // src/renderer.tsx var own = {}.hasOwnProperty; var MarkdownRoot = (props) => createComponent(MarkdownChildren, { get node() { return props.node; }, get context() { return props.context; } }); var MarkdownChildren = (props) => createComponent(For, { get each() { return props.node.children; }, children: (child, index) => createComponent(Switch, { get children() { return [createComponent(Match, { get when() { return child.type === "element"; }, get children() { return createComponent(MarkdownNode, { get context() { return props.context; }, get index() { return index(); }, node: child, get parent() { return props.node; } }); } }), createComponent(Match, { get when() { return child.type === "text" && child.value !== "\n"; }, get children() { return createComponent(MarkdownText, { get context() { return props.context; }, get index() { return index(); }, node: child, get parent() { return props.node; } }); } })]; } }) }); var MarkdownText = (props) => { const childProps = createMemo(() => { const context = { ...props.context }; const options = context.options; const node = props.node; const parent = props.parent; const properties = { parent }; const position = node.position || { start: { line: null, column: null, offset: null }, end: { line: null, column: null, offset: null } }; const component = options.components && own.call(options.components, "text") ? options.components.text : null; const basic = typeof component === "string"; properties.key = ["text", position.start.line, position.start.column, props.index].join("-"); if (options.sourcePos) { properties["data-sourcepos"] = flattenPosition(position); } if (!basic && options.rawSourcePos) { properties.sourcePosition = node.position; } if (!basic) { properties.node = node; } return { properties, context, component }; }); return createComponent(Show, { get when() { return childProps().component; }, get fallback() { return props.node.value; }, get children() { return createComponent(Dynamic, mergeProps$1({ get component() { return childProps().component || "span"; } }, () => childProps().properties)); } }); }; var MarkdownNode = (props) => { const childProps = createMemo(() => { const context = { ...props.context }; const options = context.options; const parentSchema = context.schema; const node = props.node; const name = node.tagName; const parent = props.parent; const properties = {}; let schema = parentSchema; let property; if (parentSchema.space === "html" && name === "svg") { schema = svg; context.schema = schema; } if (node.properties) { for (property in node.properties) { if (own.call(node.properties, property)) { addProperty(properties, property, node.properties[property], context); } } } if (name === "ol" || name === "ul") { context.listDepth++; } if (name === "ol" || name === "ul") { context.listDepth--; } context.schema = parentSchema; const position = node.position || { start: { line: null, column: null, offset: null }, end: { line: null, column: null, offset: null } }; const component = options.components && own.call(options.components, name) ? options.components[name] : name; const basic = typeof component === "string"; properties.key = [name, position.start.line, position.start.column, props.index].join("-"); if (name === "a" && options.linkTarget) { properties.target = typeof options.linkTarget === "function" ? options.linkTarget(String(properties.href || ""), node.children, typeof properties.title === "string" ? properties.title : void 0) : options.linkTarget; } if (name === "a" && options.transformLinkUri) { properties.href = options.transformLinkUri(String(properties.href || ""), node.children, typeof properties.title === "string" ? properties.title : void 0); } if (!basic && name === "code" && parent.type === "element" && parent.tagName !== "pre") { properties.inline = true; } if (!basic && (name === "h1" || name === "h2" || name === "h3" || name === "h4" || name === "h5" || name === "h6")) { properties.level = Number.parseInt(name.charAt(1), 10); } if (name === "img" && options.transformImageUri) { properties.src = options.transformImageUri(String(properties.src || ""), String(properties.alt || ""), typeof properties.title === "string" ? properties.title : void 0); } if (!basic && name === "li" && parent.type === "element") { const input = getInputElement(node); properties.checked = input?.properties ? Boolean(input.properties.checked) : null; properties.index = getElementsBeforeCount(parent, node); properties.ordered = parent.tagName === "ol"; } if (!basic && (name === "ol" || name === "ul")) { properties.ordered = name === "ol"; properties.depth = context.listDepth; } if (name === "td" || name === "th") { if (properties.align) { if (!properties.style) properties.style = {}; properties.style.textAlign = properties.align; delete properties.align; } if (!basic) { properties.isHeader = name === "th"; } } if (!basic && name === "tr" && parent.type === "element") { properties.isHeader = Boolean(parent.tagName === "thead"); } if (options.sourcePos) { properties["data-sourcepos"] = flattenPosition(position); } if (!basic && options.rawSourcePos) { properties.sourcePosition = node.position; } if (!basic && options.includeElementIndex) { properties.index = getElementsBeforeCount(parent, node); properties.siblingCount = getElementsBeforeCount(parent); } if (!basic) { properties.node = node; } return { properties, context, component }; }); return createComponent(Dynamic, mergeProps$1({ get component() { return childProps().component; } }, () => childProps().properties, { get children() { return createComponent(MarkdownChildren, { get node() { return props.node; }, get context() { return childProps().context; } }); } })); }; // src/index.tsx var _tmpl$ = /* @__PURE__ */ template(`<div>`); var defaults = { renderingStrategy: "memo", remarkPlugins: [], rehypePlugins: [], class: "", unwrapDisallowed: false, disallowedElements: void 0, allowedElements: void 0, allowElement: void 0, children: "", sourcePos: false, rawSourcePos: false, skipHtml: false, includeElementIndex: false, transformLinkUri: null, transformImageUri: void 0, linkTarget: "_self", components: {} }; var SolidMarkdown = (opts) => { const options = mergeProps(defaults, opts); const [node, setNode] = createStore({ type: "root", children: [] }); const generateNode = createMemo(() => { const children = options.children; const processor = unified().use(remarkParse).use(options.remarkPlugins || []).use(remarkRehype, { allowDangerousHtml: true }).use(options.rehypePlugins || []).use(rehype_filter_default, options); const file = new VFile(); if (typeof children === "string") { file.value = children; } else if (children !== void 0 && options.children !== null) { console.warn(`[solid-markdown] Warning: please pass a string as \`children\` (not: \`${typeof children}\`)`); } const hastNode = processor.runSync(processor.parse(file), file); if (hastNode.type !== "root") { throw new TypeError("Expected a `root` node"); } return hastNode; }); if (options.renderingStrategy === "reconcile") { createRenderEffect(() => { setNode(reconcile(generateNode())); }); } return (() => { var _el$ = _tmpl$(); insert(_el$, createComponent(MarkdownRoot, { context: { options, schema: html, listDepth: 0 }, get node() { return memo(() => options.renderingStrategy === "memo")() ? generateNode() : node; } })); effect(() => className(_el$, options.class)); return _el$; })(); }; export { SolidMarkdown };