als-layout 0.4.9 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +3 -0
- package/lib/build-root.js +15 -0
- package/lib/elements/add-meta.js +11 -0
- package/lib/elements/charset.js +8 -0
- package/lib/elements/description.js +9 -0
- package/lib/elements/favicon.js +10 -0
- package/lib/elements/image.js +9 -0
- package/lib/elements/index.js +15 -0
- package/lib/elements/keywords.js +20 -0
- package/lib/elements/link.js +11 -0
- package/lib/elements/script.js +11 -0
- package/lib/elements/style.js +26 -0
- package/lib/elements/title.js +11 -0
- package/lib/elements/url.js +17 -0
- package/lib/elements/viewport.js +8 -0
- package/lib/layout.js +50 -0
- package/package.json +16 -5
- package/readme.md +51 -73
- package/tests/build-root.test.js +25 -0
- package/tests/constructor.test.js +47 -0
- package/tests/elements.test.js +101 -0
- package/tests/integrative.test.js +33 -0
- package/tests/run-tests.js +6 -0
- package/layout.js +0 -115
package/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const { buildFromCache, Root, parseHTML, cacheDoc } = require('als-document')
|
|
2
|
+
|
|
3
|
+
function buildRoot(item) {
|
|
4
|
+
const root =
|
|
5
|
+
(item instanceof Root) ? buildFromCache(cacheDoc(item))
|
|
6
|
+
: (typeof item === 'string') ? parseHTML(item)
|
|
7
|
+
: (typeof item === 'object') ? buildFromCache(item)
|
|
8
|
+
: new Root()
|
|
9
|
+
|
|
10
|
+
const firstNode = root.childNodes[0]
|
|
11
|
+
if(!firstNode || firstNode.tagName !== '!DOCTYPE') root.insert(1,/*html*/`<!DOCTYPE html>`)
|
|
12
|
+
return root
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = buildRoot
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { SingleNode } = require('als-document')
|
|
2
|
+
function addMeta(props, layout) {
|
|
3
|
+
const entries = Object.entries(props)
|
|
4
|
+
const [name, value] = entries[0]
|
|
5
|
+
const selector = `meta[${name}="${value}"]`
|
|
6
|
+
const metaElement = layout.root.$(selector)
|
|
7
|
+
if (metaElement) entries.forEach(([name, v]) => metaElement.setAttribute(name, props[name]))
|
|
8
|
+
else layout.head.insert(2, new SingleNode('meta', props))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = addMeta
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const { SingleNode } = require('als-document')
|
|
2
|
+
|
|
3
|
+
module.exports = function (charset = 'UTF-8', layout) {
|
|
4
|
+
const charsetElement = layout.head.$('meta[charset]')
|
|
5
|
+
if (charsetElement) charsetElement.setAttribute('charset', charset)
|
|
6
|
+
else layout.head.insert(2, new SingleNode('meta', { charset }))
|
|
7
|
+
return layout
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const addMeta = require('./add-meta')
|
|
2
|
+
|
|
3
|
+
function addDescription(description, layout) {
|
|
4
|
+
addMeta({ name: 'description', content: description }, layout)
|
|
5
|
+
addMeta({ property: 'og:description', content: description }, layout)
|
|
6
|
+
addMeta({ property: 'twitter:description', content: description }, layout)
|
|
7
|
+
return layout
|
|
8
|
+
}
|
|
9
|
+
module.exports = addDescription
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const { SingleNode } = require('als-document')
|
|
2
|
+
|
|
3
|
+
function addFavicon(href,layout) {
|
|
4
|
+
const faviconElement = layout.root.$('link[rel=icon][type=image/x-icon]')
|
|
5
|
+
if(faviconElement) faviconElement.setAttribute('href',href)
|
|
6
|
+
else layout.head.insert(2,new SingleNode('link',{rel:'icon',href,type:'image/x-icon'}))
|
|
7
|
+
return layout
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
module.exports = addFavicon
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const addMeta = require('./add-meta')
|
|
2
|
+
function addImage(image,layout) {
|
|
3
|
+
addMeta({property:'og:image',content:image},layout)
|
|
4
|
+
addMeta({name:'twitter:image',content:image},layout)
|
|
5
|
+
addMeta({name:'twitter:card',content:'summary_large_image'},layout)
|
|
6
|
+
return layout
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
module.exports = addImage
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const addKeywords = require('./keywords')
|
|
2
|
+
const addStyle = require('./style')
|
|
3
|
+
const addDescription = require('./description')
|
|
4
|
+
const addTitle = require('./title')
|
|
5
|
+
const addImage = require('./image')
|
|
6
|
+
const addUrl = require('./url')
|
|
7
|
+
const addFavicon = require('./favicon')
|
|
8
|
+
const addScript = require('./script')
|
|
9
|
+
const addLink = require('./link')
|
|
10
|
+
const charset = require('./charset')
|
|
11
|
+
const viewport = require('./viewport')
|
|
12
|
+
module.exports = {
|
|
13
|
+
addKeywords, addStyle, addDescription, addTitle, addImage,
|
|
14
|
+
addUrl, addFavicon, addScript,addLink,charset,viewport
|
|
15
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { SingleNode } = require('als-document')
|
|
2
|
+
|
|
3
|
+
function keywords(keywords = [], layout) {
|
|
4
|
+
const { root } = layout
|
|
5
|
+
let keywordsElement = root.$('meta[name=keywords]')
|
|
6
|
+
if (!keywordsElement) {
|
|
7
|
+
keywordsElement = new SingleNode('meta', { name: 'keywords' })
|
|
8
|
+
layout.head.insert(2, keywordsElement)
|
|
9
|
+
}
|
|
10
|
+
const content = keywordsElement.getAttribute('content')
|
|
11
|
+
const existingKeywords = content ? content.split(',') : []
|
|
12
|
+
keywords.forEach(keyword => {
|
|
13
|
+
keyword = keyword.trim()
|
|
14
|
+
if (!existingKeywords.includes(keyword)) existingKeywords.push(keyword)
|
|
15
|
+
});
|
|
16
|
+
keywordsElement.setAttribute('content', existingKeywords.join())
|
|
17
|
+
return layout
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = keywords
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { SingleNode } = require('als-document')
|
|
2
|
+
function addLink(href, layout) {
|
|
3
|
+
let linkElement = layout.root.$(`link[rel=stylesheet][href="${href}"]`)
|
|
4
|
+
if (linkElement) return
|
|
5
|
+
linkElement = new SingleNode('link', { rel: 'stylesheet', href })
|
|
6
|
+
layout.head.insert(2, linkElement)
|
|
7
|
+
return layout
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
module.exports = addLink
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const { Node } = require('als-document')
|
|
2
|
+
function addScript(attributes={}, innerHTML='', head = true, layout) {
|
|
3
|
+
if (attributes.src && layout.root.$(`script[src="${attributes.src}"]`)) return
|
|
4
|
+
const script = new Node('script', attributes)
|
|
5
|
+
script.innerHTML = innerHTML
|
|
6
|
+
if(head) layout.root.head.insert(2, script)
|
|
7
|
+
else layout.body.insert(3, script)
|
|
8
|
+
return layout
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = addScript
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const CssParser = require('als-css-parser');
|
|
2
|
+
const Simple = require('als-simple-css')
|
|
3
|
+
const { Node } = require('als-document')
|
|
4
|
+
function checkStyles(styles, minified) {
|
|
5
|
+
if (Array.isArray(styles)) styles = new Simple(styles).stylesheet(minified)
|
|
6
|
+
else if (minified) {
|
|
7
|
+
const cssParser = new CssParser(rawCss);
|
|
8
|
+
styles = cssParser.minified()
|
|
9
|
+
}
|
|
10
|
+
return styles
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function addStyle(styles, minified=false,layout) {
|
|
14
|
+
styles = checkStyles(styles, minified)
|
|
15
|
+
let styleElement = layout.root.$('style')
|
|
16
|
+
if (!styleElement) {
|
|
17
|
+
styleElement = new Node('style')
|
|
18
|
+
layout.head.insert(2, styleElement)
|
|
19
|
+
}
|
|
20
|
+
if (!styleElement.innerHTML.includes(styles)) {
|
|
21
|
+
styleElement.innerHTML = styleElement.innerHTML + '\n' + styles
|
|
22
|
+
}
|
|
23
|
+
return layout
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
module.exports = addStyle
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const addMeta = require('./add-meta')
|
|
2
|
+
|
|
3
|
+
function addTitle(title,layout) {
|
|
4
|
+
const element = layout.root.$('title')
|
|
5
|
+
if(element) element.innerHTML = title
|
|
6
|
+
else layout.head.insert(2,/*html*/`<title>${title}</title>`)
|
|
7
|
+
addMeta({property:'og:title',content:title},layout)
|
|
8
|
+
return layout
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
module.exports = addTitle
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
const addMeta = require('./add-meta')
|
|
2
|
+
const { SingleNode } = require('als-document')
|
|
3
|
+
|
|
4
|
+
function getUrl(url, host, layout) {
|
|
5
|
+
try {
|
|
6
|
+
url = new URL(url, host).href.replace(/\/$/, '')
|
|
7
|
+
addMeta({ property: 'og:url', content: url }, layout)
|
|
8
|
+
const canonicalElement = layout.root.$('link[rel="canonical"]')
|
|
9
|
+
if (canonicalElement) canonicalElement.setAttribute('href', url)
|
|
10
|
+
else layout.head.insert(2, new SingleNode('link', { rel: 'canonical', href: url }))
|
|
11
|
+
} catch (error) {
|
|
12
|
+
console.log(`url ${url} with host ${host} is not valid url`)
|
|
13
|
+
}
|
|
14
|
+
return layout
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = getUrl
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const { SingleNode } = require('als-document')
|
|
2
|
+
|
|
3
|
+
module.exports = function (viewport, layout) {
|
|
4
|
+
const element = layout.root.$('meta[name="viewport"]')
|
|
5
|
+
if (element) element.setAttribute('content', viewport)
|
|
6
|
+
else layout.head.insert(2, new SingleNode('meta', { name: 'viewport', content: viewport }))
|
|
7
|
+
return layout
|
|
8
|
+
}
|
package/lib/layout.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
const { cacheDoc } = require('als-document')
|
|
2
|
+
const {
|
|
3
|
+
addKeywords, addStyle, addDescription, addTitle, addImage,
|
|
4
|
+
addUrl, addFavicon, addScript, addLink, charset, viewport
|
|
5
|
+
} = require('./elements/index')
|
|
6
|
+
const buildRoot = require('./build-root')
|
|
7
|
+
|
|
8
|
+
class Layout {
|
|
9
|
+
constructor(layout, options = {}) {
|
|
10
|
+
this.options = options
|
|
11
|
+
this.root = buildRoot(layout)
|
|
12
|
+
this.body; this.head;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get html() {
|
|
16
|
+
const lang = this.options.lang || 'en'
|
|
17
|
+
if (!this.root.$('html')) this.root.insert(2,`<html></html>`)
|
|
18
|
+
const htmlElement = this.root.$('html')
|
|
19
|
+
if(lang && htmlElement.getAttribute('lang') !== lang) {
|
|
20
|
+
htmlElement.setAttribute('lang',lang)
|
|
21
|
+
}
|
|
22
|
+
return htmlElement
|
|
23
|
+
}
|
|
24
|
+
get body() {
|
|
25
|
+
if (!this.root.body) this.html.insert(2,`<body></body>`)
|
|
26
|
+
return this.root.body
|
|
27
|
+
}
|
|
28
|
+
get head() {
|
|
29
|
+
if (!this.root.head) this.html.insert(1,`<head></head>`)
|
|
30
|
+
return this.root.head
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
get rawHtml() { return this.root.innerHTML }
|
|
34
|
+
get cached() { return cacheDoc(this.root) }
|
|
35
|
+
get clone() { return new Layout(this.root, this.options) }
|
|
36
|
+
|
|
37
|
+
keywords(keywords = []) { return addKeywords(keywords, this) }
|
|
38
|
+
description(description) { return addDescription(description, this) }
|
|
39
|
+
title(title) { return addTitle(title, this) }
|
|
40
|
+
style(styles, minified) { return addStyle(styles, minified, this) }
|
|
41
|
+
image(image) { return addImage(image, this) }
|
|
42
|
+
url(url, host = this.options.host) { return addUrl(url, host, this) }
|
|
43
|
+
favicon(href) { return addFavicon(href, this) }
|
|
44
|
+
script(attributes, innerHTML, head = true) { return addScript(attributes, innerHTML, head, this) }
|
|
45
|
+
link(href) { return addLink(href, this) }
|
|
46
|
+
charset(newCharset = 'UTF-8') { return charset(newCharset, this) }
|
|
47
|
+
viewport(newViewport = 'width=device-width, initial-scale=1.0') { return viewport(newViewport, this) }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
module.exports = Layout
|
package/package.json
CHANGED
|
@@ -1,9 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "als-layout",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Html layout constructor",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"directories": {
|
|
7
|
+
"lib": "lib"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node ./tests/run-tests.js"
|
|
11
|
+
},
|
|
6
12
|
"keywords": [],
|
|
7
|
-
"author": "",
|
|
8
|
-
"license": "ISC"
|
|
13
|
+
"author": "Alex Sorkin",
|
|
14
|
+
"license": "ISC",
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"als-css-parser": "^0.5.0",
|
|
17
|
+
"als-document": "^1.0.1-beta",
|
|
18
|
+
"als-simple-css": "^8.0.0"
|
|
19
|
+
}
|
|
9
20
|
}
|
package/readme.md
CHANGED
|
@@ -1,88 +1,66 @@
|
|
|
1
1
|
# Als-layout
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* rootUrl parameter
|
|
5
|
-
* styles parameter
|
|
6
|
-
* new in 0.4.1
|
|
7
|
-
* Added spaces between properties on meta tags
|
|
8
|
-
* new in 0.4:
|
|
9
|
-
* minify
|
|
10
|
-
* package optimization
|
|
11
|
-
* can be used in node and in browser
|
|
12
|
-
* new in 0.3: scripts and links now array
|
|
3
|
+
Html layout constructor.
|
|
13
4
|
|
|
14
|
-
##
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
title,body,description,image,
|
|
19
|
-
lang='en',url,twiterName,
|
|
20
|
-
scripts=[],links=[],
|
|
21
|
-
favicon,footer='',header='',
|
|
22
|
-
charset='UTF-8',styles='',
|
|
23
|
-
rootUrl = ''
|
|
24
|
-
})
|
|
25
|
-
// Get updated layout
|
|
26
|
-
let pageLayout = layout.get({
|
|
27
|
-
title,body,description,image,
|
|
28
|
-
lang,url,favicon,
|
|
29
|
-
footer='',header='',
|
|
30
|
-
scripts=[],links=[],
|
|
31
|
-
charset=this.charset
|
|
32
|
-
})
|
|
33
|
-
```
|
|
5
|
+
## Change log
|
|
6
|
+
* clone - now it works
|
|
7
|
+
* script({},inner = '') default for inner for script is ''
|
|
8
|
+
* url - throwing error for not valid url
|
|
34
9
|
|
|
35
|
-
##
|
|
36
|
-
Each Layout object has defaults, which you can change if needed.
|
|
37
|
-
* defaults - defaults for options
|
|
38
|
-
* meta - defaults for meta tags
|
|
39
|
-
* tab - adding this before children tags
|
|
40
|
-
* n - added at end of line
|
|
10
|
+
## install
|
|
41
11
|
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
meta = [
|
|
45
|
-
{'http-equiv':"X-UA-Compatible", content:"IE=edge"},
|
|
46
|
-
{name:"viewport",content:"width=device-width, initial-scale=1.0"},
|
|
47
|
-
{property:"og:type", content:"website"},
|
|
48
|
-
]
|
|
49
|
-
tab=' ' // then minify=true, tab=''
|
|
50
|
-
n=` // then minify=true, n=''
|
|
51
|
-
`
|
|
12
|
+
```bash
|
|
13
|
+
npm i als-layout
|
|
52
14
|
```
|
|
53
15
|
|
|
54
|
-
|
|
16
|
+
## Basic usage
|
|
55
17
|
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
layout
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
18
|
+
```js
|
|
19
|
+
const Layout = require('als-layout')
|
|
20
|
+
const layout = new Layout()
|
|
21
|
+
.charset() // default UTF-8
|
|
22
|
+
.viewport() // default width=device-width, initial-scale=1.0
|
|
23
|
+
.title('Test title') // adding/updating title and meta[og:title]
|
|
24
|
+
.favicon('/favicon.png') // adding/updating link[rel=icon][type=image/x-icon] with new href
|
|
25
|
+
.keywords(['some','keyword']) // adding/updating meta[name=keywords]. not adding existing keywords
|
|
26
|
+
.image('/main-image.png') // adding/updating meta - og:image,twitter:image,twitter:card
|
|
27
|
+
.description('Cool site') // adding/updating meta og:description,twitter:description and description tag
|
|
28
|
+
.url('/some', 'http://site.com') // adding/updating meta[og:url] and link[rel="canonical"]
|
|
29
|
+
.style([{body:{m:0,bgc:'whitesmoke'}}]) // adding as simple-css styles to existing/new style tag
|
|
30
|
+
.style('body {margin:0; backgroung-color:whitesmoke;}',true) // adding css styles to existing/new style tag. Second parameter is minified (default=false).
|
|
31
|
+
.link('/styles.css') // adding link[rel=stylesheet] if such href not exists
|
|
32
|
+
.script({src:'/app.js'},'', true) // set script with src to head if such src not exists
|
|
33
|
+
.script({}, 'console.log("hello world")', false) // set script with script code to footer
|
|
63
34
|
|
|
64
|
-
## Minify
|
|
65
|
-
```javascript
|
|
66
|
-
let Layout = require('als-layout')
|
|
67
|
-
Layout.minify = true // false by default. if true, return minified html
|
|
68
|
-
```
|
|
69
35
|
|
|
70
|
-
|
|
36
|
+
layout.body // getter for body element (if not exists, created)
|
|
37
|
+
layout.head // getter for head element (if not exists, created)
|
|
38
|
+
layout.html // getter for html element (if not exists, created)
|
|
71
39
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
]
|
|
76
|
-
let links = [
|
|
77
|
-
{href,rel='stylesheet',crossorigin,hreflang,media,referrerpolicy,sizes,type,v}
|
|
78
|
-
]
|
|
40
|
+
layout.rawHtml // raw html
|
|
41
|
+
layout.cached // cached DOM
|
|
42
|
+
layout.clone // new layout object clone for curent object
|
|
79
43
|
```
|
|
80
44
|
|
|
81
|
-
## styles
|
|
82
|
-
You can add css styles as string which will be added inside as ``<style>/*your styles*/</style>`` inside head teag.
|
|
83
45
|
|
|
46
|
+
## Advanced usage
|
|
47
|
+
|
|
48
|
+
```js
|
|
49
|
+
const Layout = require('./lib/layout')
|
|
50
|
+
|
|
51
|
+
const raw = /*html*/`<html></html>`
|
|
52
|
+
const options = {
|
|
53
|
+
host:'http://example.com', // host for url method
|
|
54
|
+
lang:'fr' // for <html lang="fr"></html>
|
|
55
|
+
}
|
|
56
|
+
const layout = new Layout(raw,options)
|
|
57
|
+
console.log(layout.rawHtml)
|
|
58
|
+
// <!DOCTYPE html><html lang="fr"><head></head><body></body></html>
|
|
59
|
+
|
|
60
|
+
const homePage = layout.clone
|
|
61
|
+
homePage.title('Home page')
|
|
62
|
+
homePage.body.innerHTML = /*html*/`<h1>Home page</h1>`
|
|
63
|
+
console.log(homePage.rawHtml)
|
|
64
|
+
// <!DOCTYPE html><html lang="fr"><head><title>Home page</title><meta property="og:title" content="Home page"></head><body><h1>Home page</h1></body></html>
|
|
65
|
+
```
|
|
84
66
|
|
|
85
|
-
## rootUrl
|
|
86
|
-
rootUrl parameter is a url you want to add to different links.
|
|
87
|
-
* adding rootUrl to meta url,meta image and favicon url
|
|
88
|
-
* adding to all scripts, links which starts with "/"
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it } = require('node:test')
|
|
3
|
+
const buildRoot = require('../lib/build-root');
|
|
4
|
+
const { Root, cacheDoc } = require('als-document');
|
|
5
|
+
|
|
6
|
+
describe('Basic tests', function () {
|
|
7
|
+
it('should create an instance of Root if undefind', () => {
|
|
8
|
+
assert(buildRoot() instanceof Root)
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('should create an instance of Root from cached', () => {
|
|
12
|
+
const cached = cacheDoc(new Root())
|
|
13
|
+
assert(buildRoot(cached) instanceof Root)
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should create an instance of Root from ready root', () => {
|
|
17
|
+
const root = new Root()
|
|
18
|
+
assert(buildRoot(root) !== root)
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('should add !DOCTYPE', () => {
|
|
22
|
+
const root = buildRoot()
|
|
23
|
+
assert(root.childNodes[0].tagName === '!DOCTYPE')
|
|
24
|
+
});
|
|
25
|
+
})
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it } = require('node:test')
|
|
3
|
+
const Layout = require('../lib/layout');
|
|
4
|
+
const { Root,cacheDoc } = require('als-document')
|
|
5
|
+
|
|
6
|
+
describe('Layout Initialization', () => {
|
|
7
|
+
it('should create an instance of Layout', () => {
|
|
8
|
+
const layout = new Layout();
|
|
9
|
+
assert(layout instanceof Layout, 'layout is not an instance of Layout');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('should initialize default language as "en"', () => {
|
|
13
|
+
const layout = new Layout();
|
|
14
|
+
assert(layout.html.getAttribute('lang') === 'en', 'default language is not "en"');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('should allow setting a custom language', () => {
|
|
18
|
+
const customLang = 'fr';
|
|
19
|
+
const layout = new Layout(undefined, { lang: customLang });
|
|
20
|
+
assert(layout.options.lang === customLang, `language is not set to ${customLang}`);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should initialize development mode as undefined or false', () => {
|
|
24
|
+
const layout = new Layout();
|
|
25
|
+
assert.strictEqual(layout.dev, undefined, 'dev is not undefined or false by default');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should initialize host as undefined', () => {
|
|
29
|
+
const layout = new Layout();
|
|
30
|
+
assert.strictEqual(layout.host, undefined, 'host is not undefined by default');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should allow setting a custom host', () => {
|
|
34
|
+
const customHost = 'http://localhost';
|
|
35
|
+
const layout = new Layout(undefined, { host: customHost });
|
|
36
|
+
assert.strictEqual(layout.options.host, customHost, `host is not set to ${customHost}`);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('check method',() => {
|
|
40
|
+
const cached = cacheDoc(new Root())
|
|
41
|
+
const layout = new Layout(cached)
|
|
42
|
+
const root = layout.root
|
|
43
|
+
assert(root.$('html') !== null)
|
|
44
|
+
assert(root.$('head') !== null)
|
|
45
|
+
assert(root.$('body') !== null)
|
|
46
|
+
})
|
|
47
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const Layout = require('../lib/layout');
|
|
3
|
+
const { describe, it,beforeEach } = require('node:test')
|
|
4
|
+
const Simple = require('als-simple-css')
|
|
5
|
+
describe('Elements Integration', () => {
|
|
6
|
+
let layout;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => layout = new Layout());
|
|
9
|
+
|
|
10
|
+
it('should add charset correctly', () => {
|
|
11
|
+
const charset = 'test';
|
|
12
|
+
layout.charset(charset);
|
|
13
|
+
assert.strictEqual(layout.root.$('meta[charset]').getAttribute('charset'), charset, 'Charset not set correctly');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should add description correctly', () => {
|
|
17
|
+
const description = 'Test Description';
|
|
18
|
+
layout.description(description);
|
|
19
|
+
assert.strictEqual(layout.root.$('meta[name="description"]').getAttribute('content'), description, 'Description not set correctly');
|
|
20
|
+
assert.strictEqual(layout.root.$('meta[property="og:description"]').getAttribute('content'), description, 'Description not set correctly');
|
|
21
|
+
assert.strictEqual(layout.root.$('meta[property="twitter:description"]').getAttribute('content'), description, 'Description not set correctly');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should add favicon correctly', function () {
|
|
25
|
+
const faviconHref = 'favicon.ico';
|
|
26
|
+
layout.favicon(faviconHref);
|
|
27
|
+
assert.strictEqual(layout.root.$('link[rel="icon"]').getAttribute('href'), faviconHref, 'Favicon not set correctly');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should add image correctly', () => {
|
|
31
|
+
const imageUrl = 'test-image.jpg';
|
|
32
|
+
layout.image(imageUrl);
|
|
33
|
+
assert.strictEqual(layout.root.$('meta[property="og:image"]').getAttribute('content'), imageUrl, 'Image not set correctly');
|
|
34
|
+
assert.strictEqual(layout.root.$('meta[name="twitter:image"]').getAttribute('content'), imageUrl, 'Image not set correctly');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should add link correctly', () => {
|
|
38
|
+
const href = 'style.css';
|
|
39
|
+
layout.link(href);
|
|
40
|
+
assert.strictEqual(layout.root.$('link[rel="stylesheet"]').getAttribute('href'), href, 'Link not set correctly');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should add script correctly', () => {
|
|
44
|
+
const scriptContent = 'console.log("Hello, world!");';
|
|
45
|
+
layout.script({}, scriptContent);
|
|
46
|
+
assert.strictEqual(layout.root.$('script').innerHTML, scriptContent, 'Script content not set correctly');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('should add title correctly', () => {
|
|
50
|
+
const title = 'Test Title';
|
|
51
|
+
layout.title(title);
|
|
52
|
+
assert(layout.root.$('title').innerHTML === title, 'Title not set correctly');
|
|
53
|
+
assert(layout.root.$('[property="og:title"]').getAttribute('content') === title, 'Title not set correctly');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
it('should add url correctly', () => {
|
|
58
|
+
const url = 'http://localhost';
|
|
59
|
+
layout.url(url);
|
|
60
|
+
assert(layout.root.$('link[rel="canonical"]').getAttribute('href') === url, 'Canonical URL not set correctly');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should add keywords correctly', () => {
|
|
64
|
+
const keywords = ['keyword1', 'keyword2'];
|
|
65
|
+
layout.keywords(keywords);
|
|
66
|
+
assert(layout.root.$('meta[name="keywords"]').getAttribute('content') === keywords.join(), 'Keywords not set correctly');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should add viewport correctly', () => {
|
|
70
|
+
const viewportContent = 'width=device-width, initial-scale=1.0';
|
|
71
|
+
layout.viewport(viewportContent);
|
|
72
|
+
assert.strictEqual(layout.root.$('meta[name="viewport"]').getAttribute('content'), viewportContent, 'Viewport not set correctly');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should add style correctly', () => {
|
|
76
|
+
const styles = 'body { background-color: black; }';
|
|
77
|
+
layout.style(styles);
|
|
78
|
+
assert.strictEqual(layout.root.$('style').innerHTML.includes(styles), true, 'Styles not set correctly');
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('Should add simple styles',() => {
|
|
82
|
+
const styles = [{body:{bgc:'black'}}]
|
|
83
|
+
const css = new Simple(styles).stylesheet()
|
|
84
|
+
layout.style(styles);
|
|
85
|
+
assert(layout.root.$('style').innerHTML.includes(css), 'Styles not set correctly');
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should add styles to existing style tag', () => {
|
|
89
|
+
assert(layout.root.$$('style').length === 0)
|
|
90
|
+
const styles1 = 'body { background-color: black; }';
|
|
91
|
+
const styles2 = 'body { margin: 0; }';
|
|
92
|
+
layout.style(styles1);
|
|
93
|
+
assert(layout.root.$$('style').length === 1)
|
|
94
|
+
layout.style(styles2);
|
|
95
|
+
assert(layout.root.$$('style').length === 1)
|
|
96
|
+
const inner = layout.root.$('style').innerHTML
|
|
97
|
+
assert(inner.includes(styles1));
|
|
98
|
+
assert(inner.includes(styles2));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const assert = require('assert');
|
|
2
|
+
const { describe, it, beforeEach } = require('node:test')
|
|
3
|
+
const Layout = require('../lib/layout');
|
|
4
|
+
|
|
5
|
+
describe('Layout Integrative tests', () => {
|
|
6
|
+
let layout;
|
|
7
|
+
|
|
8
|
+
beforeEach(() => layout = new Layout());
|
|
9
|
+
|
|
10
|
+
it('should integrate multiple elements correctly', () => {
|
|
11
|
+
layout.title('Test Title');
|
|
12
|
+
layout.description('Test Description');
|
|
13
|
+
layout.keywords(['keyword1', 'keyword2']);
|
|
14
|
+
|
|
15
|
+
assert(layout.root.$('title').innerHTML === 'Test Title', 'Title not integrated correctly');
|
|
16
|
+
assert(layout.root.$('meta[name="description"]').getAttribute('content') === 'Test Description', 'Description not integrated correctly');
|
|
17
|
+
assert(layout.root.$('meta[name="keywords"]').getAttribute('content') === 'keyword1,keyword2', 'Keywords not integrated correctly');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should update elements correctly when added multiple times', () => {
|
|
21
|
+
layout.title('First Title');
|
|
22
|
+
assert(layout.root.$('title').innerHTML === 'First Title', 'Title did not update correctly when added multiple times');
|
|
23
|
+
layout.title('Second Title');
|
|
24
|
+
assert(layout.root.$('title').innerHTML === 'Second Title', 'Title did not update correctly when added multiple times');
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should remove elements correctly', () => {
|
|
28
|
+
layout.title('Test Title');
|
|
29
|
+
layout.root.$('title').remove();
|
|
30
|
+
assert(layout.root.$('title') === null, 'Title element was not removed correctly');
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
});
|
package/layout.js
DELETED
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
class Layout {
|
|
2
|
-
static minify=false
|
|
3
|
-
defaults = {lang:'en',scripts:[],links:[],footer:'',header:'',body:'',charset:'UTF-8',styles:''}
|
|
4
|
-
meta = [
|
|
5
|
-
{'http-equiv':"X-UA-Compatible", content:"IE=edge"},
|
|
6
|
-
{name:"viewport",content:"width=device-width, initial-scale=1.0"},
|
|
7
|
-
{property:"og:type", content:"website"},
|
|
8
|
-
]
|
|
9
|
-
tab=' '
|
|
10
|
-
n=`
|
|
11
|
-
`
|
|
12
|
-
constructor(options={}) {
|
|
13
|
-
if(Layout.minify) this.tab = this.n = ''
|
|
14
|
-
options = {...this.defaults,...options}
|
|
15
|
-
for(let key in options) {
|
|
16
|
-
this[key] = options[key]
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
get(options={}) {
|
|
21
|
-
for(let key in options) {
|
|
22
|
-
if(key == 'scripts' || key == 'links') {
|
|
23
|
-
if(!Array.isArray(options[key])) options[key] = []
|
|
24
|
-
this[key] = [...this[key],...options[key]]
|
|
25
|
-
}
|
|
26
|
-
else this[key] = options[key]
|
|
27
|
-
}
|
|
28
|
-
return this.template()
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
template(n=this.n,tab=this.tab) {
|
|
32
|
-
let {rootUrl='',favicon,title,header,body,footer,styles,htmlAtts=[],bodyAtts=[]} = this
|
|
33
|
-
let {footerScripts,headerScripts} = this.buildScripts()
|
|
34
|
-
let links = this.buildLinks()
|
|
35
|
-
let template = /*html*/`<!DOCTYPE html>`+n
|
|
36
|
-
template +=/*html*/`<html lang="${this.lang}" ${htmlAtts.join(' ')}>`+n
|
|
37
|
-
template +=/*html*/`<head>`+n
|
|
38
|
-
template += links ? links+n : ''
|
|
39
|
-
template += styles ? /*html*/`<style>${styles}</style>`+n : ''
|
|
40
|
-
template += headerScripts ? headerScripts : ''
|
|
41
|
-
template += tab+/*html*/`<meta charset="${this.charset}">`+n
|
|
42
|
-
template += this.buildMeta()+n
|
|
43
|
-
template += favicon ? tab+/*html*/`<link rel="icon" href="${rootUrl}${favicon}" type="image/x-icon" >`+n : ''
|
|
44
|
-
template += title ? tab+/*html*/`<title>${title || ''}</title>`+n : ''
|
|
45
|
-
template +=/*html*/`</head>`+n
|
|
46
|
-
template += /*html*/`<body ${ bodyAtts.join(' ')}>`+n
|
|
47
|
-
template += header ? tab+header+n : ''
|
|
48
|
-
template += tab+body+n
|
|
49
|
-
template += footer ? tab+footer+n : ''
|
|
50
|
-
template += footerScripts ? footerScripts : ''
|
|
51
|
-
template += /*html*/`</body>`+n
|
|
52
|
-
template += /*html*/`</html>`
|
|
53
|
-
return template
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
buildMeta(n=this.n,tab=this.tab) {
|
|
57
|
-
let meta = this.meta
|
|
58
|
-
let {title,url,description,image,twiterName,rootUrl} = this
|
|
59
|
-
if(rootUrl) {
|
|
60
|
-
url = rootUrl+url
|
|
61
|
-
image = rootUrl+image
|
|
62
|
-
}
|
|
63
|
-
if(description) {
|
|
64
|
-
meta.push({name:"description",content:description})
|
|
65
|
-
meta.push({property:"og:description",content:description})
|
|
66
|
-
meta.push({name:"twitter:description",content:description})
|
|
67
|
-
}
|
|
68
|
-
if(image) {
|
|
69
|
-
meta.push({property:"og:image", content:image})
|
|
70
|
-
meta.push({name:"twitter:image", content:image})
|
|
71
|
-
meta.push({name:"twitter:card", content:'summary_large_image'})
|
|
72
|
-
} else {
|
|
73
|
-
meta.push({name:"twitter:card", content:'article'})
|
|
74
|
-
}
|
|
75
|
-
if(twiterName) meta.push({name:"twitter:site",content:twiterName})
|
|
76
|
-
if(title) meta.push({property:"og:title", content:title})
|
|
77
|
-
if(url) meta.push({property:"og:url",content:url})
|
|
78
|
-
return meta.map(obj => tab+
|
|
79
|
-
/*html*/`<meta ${Object.keys(obj).map(propName => `${propName}="${obj[propName]}"`).join(' ')}>`
|
|
80
|
-
).join(n)
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
buildScripts() {
|
|
84
|
-
let footerScripts = ''
|
|
85
|
-
let headerScripts = ''
|
|
86
|
-
let excludes = ['inner','v','footer']
|
|
87
|
-
this.scripts.forEach(script => {
|
|
88
|
-
let {inner='',v,src,footer} = script
|
|
89
|
-
if(src && this.rootUrl)
|
|
90
|
-
if(src.startsWith('/')) script.src = this.rootUrl+src
|
|
91
|
-
if(src && v) script.src = script.src+'?'+v
|
|
92
|
-
script = '<script '+Object.keys(script)
|
|
93
|
-
.filter(p => !excludes.includes(p))
|
|
94
|
-
.map(prop => `${prop}="${script[prop] || ''}"`).join(' ')
|
|
95
|
-
+'>'+inner+`</script>`
|
|
96
|
-
if(footer) footerScripts += this.tab+script+this.n
|
|
97
|
-
else headerScripts += this.tab+script+this.n
|
|
98
|
-
})
|
|
99
|
-
return {footerScripts,headerScripts}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
buildLinks() {
|
|
103
|
-
return this.links.map(link => {
|
|
104
|
-
let {href,v,rel} = link
|
|
105
|
-
if(rel == undefined) link.rel = 'stylesheet'
|
|
106
|
-
if(href && this.rootUrl)
|
|
107
|
-
if(href.startsWith('/')) link.href = this.rootUrl+href
|
|
108
|
-
if(href && v) link.href = href+'?'+v
|
|
109
|
-
return this.tab+'<link '
|
|
110
|
-
+Object.entries(link).map(([key,value]) => `${key}="${value}"`).join(' ')
|
|
111
|
-
+'>'
|
|
112
|
-
}).join(this.n)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
try {module.exports = Layout} catch{}
|