als-layout
Version:
HTML layout constructor with dynamic meta, styles, and scripts
83 lines • 4.01 kB
JavaScript
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
}
}