import * as fs from 'fs' import * as path from 'path' import * as util from 'util' import * as jsdom from 'jsdom' import {Content} from '../ambient' import xjs_ParentNode from '../iface/ParentNode.iface' import xjs_Node, { NodeType } from './Node.class' import xjs_Element from './Element.class' import xjs_HTMLTemplateElement_import from './HTMLTemplateElement.class' /** * Wrapper for a DocumentFragment. * @see https://www.w3.org/TR/dom/#documentfragment */ export default class xjs_DocumentFragment extends xjs_Node implements xjs_ParentNode { /** * Concatenate multiple contents into text. * * ```js * xjs.DocumentFragment.concat( * new xjs.Element(document.createElement('strong')).append(`hello `), * new xjs.Element(document.createElement('em' )).append(`world`), * new xjs.Element(document.createElement('mark' )).append(`!`) * ) // 'hello world!' * ``` * @param contents the contents to concatenate * @returns the resulting output of concatenation */ static concat(...contents: Content[]): string { return new xjs_DocumentFragment(jsdom.JSDOM.fragment('')).append(...contents).innerHTML() } /** * Read an HTML string and return a document fragment with its contents. * * The DocumentFragment object will be wrapped in an `xjs.DocumentFragment` object. * To access the actual fragment, call {@link xjs_DocumentFragment#node}. * @param str a string of markup * @returns the fragment, wrapped */ static fromString(str: string): xjs_DocumentFragment { return new xjs_DocumentFragment(jsdom.JSDOM.fragment(str)) } /** * Read an HTML file and return a document fragment with its contents. * * The DocumentFragment object will be wrapped in an `xjs.DocumentFragment` object. * To access the actual fragment, call {@link xjs_DocumentFragment#node}. * @param filepath the path to the file * @returns the fragment, wrapped */ static async fromFile(filepath: string): Promise { return xjs_DocumentFragment.fromString(await util.promisify(fs.readFile)(filepath, 'utf8')) } /** * Synchronous version of {@link xjs_DocumentFragment.fromFile}. * @param filepath the path to the file * @returns the fragment, wrapped */ static fromFileSync(filepath: string): xjs_DocumentFragment { return xjs_DocumentFragment.fromString(fs.readFileSync(filepath, 'utf8')) } /** * Construct a new xjs_DocumentFragment object. * @param node the node to wrap */ constructor(node: DocumentFragment) { super(node) } /** * This wrapper’s node. */ get node(): DocumentFragment { return super.node as DocumentFragment } /** @implements xjs_ParentNode */ prepend(...contents: Content[]): this { this.node.prepend(...contents.map((c) => (c instanceof xjs_Node) ? c.node : (c === null) ? '' : c )) return this } /** @implements xjs_ParentNode */ append(...contents: Content[]): this { this.node.append(...contents.map((c) => (c instanceof xjs_Node) ? c.node : (c === null) ? '' : c )) return this } /** @implements xjs_ParentNode */ querySelector(selector: string): xjs_Element|null { let el: Element|null = this.node.querySelector(selector) return (el === null) ? null : new xjs_Element(el) } /** @implements xjs_ParentNode */ querySelectorAll(selector: string): xjs_Element[] { return [...this.node.querySelectorAll(selector)].map((el) => new xjs_Element(el)) } /** * Get the "innerHTML" of this document fragment. * @returns a concatenation of all the `outerHTML` and/or data of the fragment’s node children */ innerHTML(): string { return [...this.node.childNodes].map((node) => (new Map string>([ [NodeType.ELEMENT_NODE , (el : Element ) => el.outerHTML], [NodeType.TEXT_NODE , (text: Text ) => text.data], [NodeType.COMMENT_NODE , (comm: Comment ) => ``], [NodeType.DOCUMENT_FRAGMENT_NODE , (frag: DocumentFragment) => new xjs_DocumentFragment(frag).innerHTML()], ]).get(node.nodeType) || (() => ''))(node) ).join('') } /** * Replace all `link[rel~="import"][data-import]` elements with contents from their documents. * * This method finds all `link[rel~="import"][data-import]`s in this document fragment, * and then replaces those links with another `DocumentFragment` holding some contents. * These contents depend on the value set for `data-import`: * * - if `[data-import="document"]`, then the replaced contents will be the contents of the link’s imported document itself * - if `[data-import="template"]`, then the replaced contents will be the contents of the first `template` descendant in the link’s imported document * - if the `[data-import]` attribute value is neither `"document"` nor `"template"`, or if it is absent, then the `link` element is completely ignored and left as-is * * Note: If {@link https://developer.mozilla.org/en-US/docs/Web/Web_Components/HTML_Imports|HTMLLinkElement#import} * is supported (by the browser or jsdom), then when `[data-import="document"]` is set, * the appended contents will instead be a `Document` object, as defined by * {@link https://www.w3.org/TR/html-imports/|HTML Imports}, rather than a `DocumentFragment` object. * * Note: `DocumentFragment#querySelectorAll` does *not* traverse inside `