UNPKG

als-layout

Version:

HTML layout constructor with dynamic meta, styles, and scripts

83 lines 4.01 kB
import { Document, SingleNode, Node } from 'als-document'; export class Layout extends Document { constructor(html, host) { super(html, host); this.root = this.html; } get rawHtml() { return this.innerHTML } get clone() { return new this.constructor(new Document(this, this.URL), this.host) } lang(lang) { this.html.setAttribute('lang', lang); return this } link(href, attributes = { rel: "stylesheet", type: "text/css" }) { if (!href || typeof href !== 'string') throw new Error(`href attribute must be a string`) if (!this.root.querySelector(`link[rel=stylesheet][href^="${href}"]`)) this.head.insert(2, new SingleNode('link', { href, ...attributes })) return this } keywords(keywords = []) { let el = this.root.$('meta[name=keywords]') || new SingleNode('meta', { name: 'keywords' }) keywords = new Set([...(el.getAttribute('content') || '').split(','),...keywords.map(k => k.trim())].filter(Boolean)) if (keywords.size) el.setAttribute('content', Array.from(keywords).join(',')) if(keywords.size && !el.parent) this.head.insert(2, el) return this } style(styles) { if (typeof styles !== 'string') throw 'styles parameter should be string'; let el = this.root.$('style') || this.head.insert(2, new Node('style')) el.innerHTML = el.innerHTML + '\n' + styles; return this } url(url, host = this.URL) { try { url = (host ? new URL(url, host) : new URL(url)).href.replace(/\/$/, '') this.meta({ property: 'og:url', content: url }) const el = this.root.$('link[rel="canonical"]') || this.head.insert(2, new SingleNode('link', { rel: 'canonical', href: url })) el.setAttribute('href', url) } catch (error) { error.info = `url "${url}" with host "${host}" is not valid url`; throw error; } return this } meta(props) { const entries = Object.entries(props) const [name, value] = entries[0] const metaElement = this.root.querySelector(`meta[${name}="${value}"]`) || this.head.insert(2, new SingleNode('meta', props)) entries.forEach(([name, v]) => metaElement.setAttribute(name, props[name])) return this } script(attrs = {}, innerHTML = '', head = true) { if (typeof attrs !== 'object' || attrs === null || Array.isArray(attrs)) attrs = {} if (attrs.src && this.root.querySelector(`script[src="${attrs.src}"]`)) return this if (Object.keys(attrs).length || innerHTML) { const script = new Node('script', attrs) if (innerHTML) script.innerHTML = innerHTML if (head) this.head.insert(2, script) else this.html.insert(2, script) } return this } description(description) { this.meta({ name: 'description', content: description }) this.meta({ property: 'og:description', content: description }) this.meta({ property: 'twitter:description', content: description }) return this } favicon(href) { const el = this.root.$('link[rel=icon][type=image/x-icon]') if (el) el.setAttribute('href', href) else this.head.insert(2, new SingleNode('link', { rel: 'icon', href, type: 'image/x-icon' })) return this } viewport(viewport = 'width=device-width, initial-scale=1.0') { const el = this.root.$('meta[name="viewport"]') if (el) el.setAttribute('content', viewport) else this.head.insert(2, new SingleNode('meta', { name: 'viewport', content: viewport })) return this } image(image) { this.meta({ property: 'og:image', content: image }) this.meta({ name: 'twitter:image', content: image }) this.meta({ name: 'twitter:card', content: 'summary_large_image' }) return this } title(title) { super.title // create title tag if not exists super.title = title this.meta({ property: 'og:title', content: title }) return this } }