als-layout 5.2.0 → 6.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.
@@ -1,37 +1,10 @@
1
- ## Change Log
2
- * V5.2.0
3
- * options object on clone, cloned too (remove the reference and deep cloning the object)
4
- * V5.1.0
5
- * status and end methods removed
6
- * status and end methods added to als-view
7
- * V5.0.0
8
- * constructor changed
9
- * options object parameter instead separated parameters
10
- * options.logger
11
- * bugs fixed
12
- * some refactoring
13
- * minifiying error catching
14
- * status and end methods added
15
- * V4.2.0
16
- * link method changed
17
- * `link(href, attributes = { rel: "stylesheet", type: "text/css" })`
18
- * no version method
19
- * no version parameter in image, link,script
20
- * script
21
- * script in footer added one after other in right order
1
+ ## Change Log for V6
22
2
 
23
- * V4.1.0
24
- * Render removed
25
-
26
- * V4.0.0
27
- * All code rebuilded and refactored
28
- * No als-simple-css for style
29
- * No charset method
30
- * minifying for style and inner scripts
31
- * updated render version
32
- * render as element's method instead layout's method
33
-
34
- * V3.0.0
35
- * render switched to als-render
36
- * updated bug with meta tags after body
3
+ The code refactored.
37
4
 
5
+ ### Removed
6
+ * no deepclone for options, using { ...Layout.options, ...options } instead
7
+ * no onload method
8
+ ### Added
9
+ * status
10
+ * end(res)
@@ -35,13 +35,3 @@ layout.rawHtml // raw HTML of the document
35
35
  layout.clone // creates a new layout object clone for current object
36
36
  ```
37
37
 
38
- ### onload
39
-
40
- By adding onload attribute, you can run scripts for each element after dom content has loaded.
41
-
42
- Example how it works:
43
- ```js
44
- const layout = new Layout().viewport().title('On load').onload()
45
- layout.body.innerHTML = /*html*/`<div onload="this.innerHTML = 'new content'">original content</div>`
46
- ```
47
-
package/docs/2-cloning.md CHANGED
@@ -17,3 +17,19 @@ const newLayout = layout.clone;
17
17
 
18
18
  Cloning is particularly useful in scenarios where templates or base layouts are used repeatedly with slight variations, providing a robust and scalable solution for web page generation.
19
19
 
20
+
21
+ ### propsToClone
22
+
23
+ You can add properties to add when cloning the object, by adding name of properties to `Layout.propsToClone` which is array.
24
+
25
+ Example:
26
+
27
+ ```js
28
+ Layout.propsToClone.push('projectName')
29
+
30
+ const layout = new Layout()
31
+ layout.projectName = 'Cool project'
32
+
33
+ const cloned = layout.clone
34
+ console.log(cloned.projectName) // 'Cool project'
35
+ ```
@@ -0,0 +1,10 @@
1
+ ## Response with status
2
+
3
+ In version 6, added two methods:
4
+ 1. `status(statusCode)` - adds the statusCode to `__status` property
5
+ 2. `end(res)`
6
+ 1. writes head with `__status, { 'Content-Type': 'text/html' }`
7
+ 2. runs `res.end(this.rawHtml)`
8
+
9
+
10
+ The main idea, is to add quick way to response with layout.
@@ -23,16 +23,17 @@ Returns the inner HTML of the document.
23
23
 
24
24
  Creates a clone of the current layout instance, preserving `options`.
25
25
 
26
+
27
+ #### `__status`
28
+
29
+ Stores the statusCode for `end` method.
30
+
26
31
  ### Methods
27
32
 
28
33
  #### `lang(lang: string): this`
29
34
 
30
35
  Sets the `lang` attribute on the `<html>` element.
31
36
 
32
- #### `onload(): this`
33
-
34
- Adds an `onload` script to the document. If called multiple times, the script is only added once.
35
-
36
37
  #### `title(title: string): this`
37
38
 
38
39
  Sets the document title and creates an Open Graph title meta tag.
@@ -86,3 +87,11 @@ Adds a `<script>` tag to the document.
86
87
  #### `link(href: string, attributes: object = { rel: "stylesheet", type: "text/css" }): this`
87
88
 
88
89
  Adds a `<link>` tag for external stylesheets.
90
+
91
+ #### `status(statusCode:number): this`
92
+
93
+ Adds the statusCode to `__status` property
94
+
95
+ #### `end(res):undefined`
96
+
97
+ Response with res(rawHtml)
@@ -0,0 +1,38 @@
1
+ const meta = require('./meta')
2
+ const script = require('./script')
3
+ const style = require('./style')
4
+ const addKeywords = require('./keywords')
5
+ const addLink = require('./link')
6
+ const addUrl = require('./url')
7
+
8
+ const { SingleNode } = require('als-document');
9
+
10
+ function getDescription(description,layout) {
11
+ meta({ name: 'description', content: description },layout)
12
+ meta({ property: 'og:description', content: description },layout)
13
+ meta({ property: 'twitter:description', content: description },layout)
14
+ return layout
15
+ }
16
+
17
+ function getFavicon(href,layout) {
18
+ const faviconElement = layout.root.$('link[rel=icon][type=image/x-icon]')
19
+ if (faviconElement) faviconElement.setAttribute('href', href)
20
+ else layout.head.insert(2, new SingleNode('link', { rel: 'icon', href, type: 'image/x-icon' }))
21
+ return layout
22
+ }
23
+
24
+ function getViewport(viewport = 'width=device-width, initial-scale=1.0',layout) {
25
+ const element = layout.root.$('meta[name="viewport"]')
26
+ if (element) element.setAttribute('content', viewport)
27
+ else layout.head.insert(2, new SingleNode('meta', { name: 'viewport', content: viewport }))
28
+ return layout
29
+ }
30
+
31
+ function getImage(image,layout) {
32
+ meta({ property: 'og:image', content: image },layout)
33
+ meta({ name: 'twitter:image', content: image },layout)
34
+ meta({ name: 'twitter:card', content: 'summary_large_image' },layout)
35
+ return layout
36
+ }
37
+
38
+ module.exports = { meta, getDescription, getFavicon, getViewport, getImage, script, style, addKeywords, addLink, addUrl }
@@ -0,0 +1,19 @@
1
+ const { SingleNode } = require('als-document');
2
+
3
+ function keywords(keywords = [],layout) {
4
+ let keywordsElement = layout.root.$('meta[name=keywords]')
5
+ if (!keywordsElement) keywordsElement = new SingleNode('meta', { name: 'keywords' })
6
+ const content = keywordsElement.getAttribute('content')
7
+ const existingKeywords = content ? content.split(',') : []
8
+ keywords.forEach(keyword => {
9
+ keyword = keyword.trim()
10
+ if (!existingKeywords.includes(keyword)) existingKeywords.push(keyword)
11
+ });
12
+ if (existingKeywords.length) {
13
+ keywordsElement.setAttribute('content', existingKeywords.join())
14
+ layout.head.insert(2, keywordsElement)
15
+ }
16
+ return layout
17
+ }
18
+
19
+ module.exports = keywords
@@ -0,0 +1,13 @@
1
+ const { SingleNode } = require('als-document');
2
+
3
+ function link(href, attributes = { rel: "stylesheet", type: "text/css" },layout) {
4
+ if (!href || typeof href !== 'string') return layout
5
+ const selector = `link[rel=stylesheet][href^="${href}"]`
6
+ let linkElement = layout.root.$(selector)
7
+ if (linkElement) return
8
+ linkElement = new SingleNode('link', { href, ...attributes })
9
+ layout.head.insert(2, linkElement)
10
+ return layout
11
+ }
12
+
13
+ module.exports = link
@@ -0,0 +1,12 @@
1
+ const { SingleNode } = require('als-document');
2
+
3
+ function meta(props,layout) {
4
+ const entries = Object.entries(props)
5
+ const [name, value] = entries[0]
6
+ const selector = `meta[${name}="${value}"]`
7
+ const metaElement = layout.root.$(selector)
8
+ if (metaElement) entries.forEach(([name, v]) => metaElement.setAttribute(name, props[name]))
9
+ else layout.head.insert(2, new SingleNode('meta', props))
10
+ }
11
+
12
+ module.exports = meta
@@ -0,0 +1,18 @@
1
+ const meta = require('./meta');
2
+ const { SingleNode } = require('als-document');
3
+
4
+ function addUrl(url, host,layout) {
5
+ try {
6
+ url = (host ? new URL(url, host) : new URL(url)).href.replace(/\/$/, '')
7
+ meta({ 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(error)
13
+ layout.logger.log(`url "${url}" with host "${host}" is not valid url`)
14
+ }
15
+ return layout
16
+ }
17
+
18
+ module.exports = addUrl
package/lib/layout.js CHANGED
@@ -1,11 +1,12 @@
1
- const { Document, SingleNode } = require('als-document');
2
- const script = require('./script');
3
- const style = require('./style');
4
- const onloadScript = require('./onload');
5
- const deepClone = require('als-deep-clone')
1
+ const { Document } = require('als-document');
2
+ const { meta, getDescription, getFavicon, getViewport, getImage, script, style, addKeywords, addLink, addUrl } = require('./elements/index')
6
3
 
7
4
  class Layout extends Document {
5
+ static propsToClone = [];
6
+ static options = { logger: console, host: '', minified: false }
7
+
8
8
  constructor(html, options = {}) {
9
+ options = { ...Layout.options, ...options }
9
10
  super(html, options.host);
10
11
  this.options = options
11
12
  this.logger = options.logger || console
@@ -13,104 +14,37 @@ class Layout extends Document {
13
14
  }
14
15
 
15
16
  get rawHtml() { return this.innerHTML }
16
- get clone() { return new this.constructor(new Document(this, this.URL), deepClone(this.options)) }
17
- lang(lang) { this.html.setAttribute('lang', lang); return this }
18
-
19
- onload() {
20
- const id = 'onload-script'
21
- if(this.$('#'+id)) return
22
- this.script({id}, onloadScript);
23
- return this
24
- }
25
-
26
- title(title) {
27
- super.title // create title tag if not exists
28
- super.title = title
29
- this.meta({ property: 'og:title', content: title })
30
- return this
31
- }
32
-
33
- description(description) {
34
- this.meta({ name: 'description', content: description })
35
- this.meta({ property: 'og:description', content: description })
36
- this.meta({ property: 'twitter:description', content: description })
37
- return this
38
- }
39
-
40
- favicon(href) {
41
- const faviconElement = this.root.$('link[rel=icon][type=image/x-icon]')
42
- if (faviconElement) faviconElement.setAttribute('href', href)
43
- else this.head.insert(2, new SingleNode('link', { rel: 'icon', href, type: 'image/x-icon' }))
44
- return this
45
- }
46
-
47
- meta(props) {
48
- const entries = Object.entries(props)
49
- const [name, value] = entries[0]
50
- const selector = `meta[${name}="${value}"]`
51
- const metaElement = this.root.$(selector)
52
- if (metaElement) entries.forEach(([name, v]) => metaElement.setAttribute(name, props[name]))
53
- else this.head.insert(2, new SingleNode('meta', props))
54
- }
55
-
56
- keywords(keywords = []) {
57
- let keywordsElement = this.root.$('meta[name=keywords]')
58
- if (!keywordsElement) keywordsElement = new SingleNode('meta', { name: 'keywords' })
59
- const content = keywordsElement.getAttribute('content')
60
- const existingKeywords = content ? content.split(',') : []
61
- keywords.forEach(keyword => {
62
- keyword = keyword.trim()
63
- if (!existingKeywords.includes(keyword)) existingKeywords.push(keyword)
64
- });
65
- if (existingKeywords.length) {
66
- keywordsElement.setAttribute('content', existingKeywords.join())
67
- this.head.insert(2, keywordsElement)
68
- }
69
- return this
70
- }
71
-
72
- viewport(viewport = 'width=device-width, initial-scale=1.0') {
73
- const element = this.root.$('meta[name="viewport"]')
74
- if (element) element.setAttribute('content', viewport)
75
- else this.head.insert(2, new SingleNode('meta', { name: 'viewport', content: viewport }))
76
- return this
77
- }
78
-
79
- image(image) {
80
- this.meta({ property: 'og:image', content: image })
81
- this.meta({ name: 'twitter:image', content: image })
82
- this.meta({ name: 'twitter:card', content: 'summary_large_image' })
83
- return this
17
+ get clone() {
18
+ const clone = new this.constructor(new Document(this, this.URL), this.options)
19
+ Layout.propsToClone.forEach(prop => clone[prop] = this[prop])
20
+ return clone
84
21
  }
85
22
 
86
- style(styles, minified = this.options.minified) {
87
- return style(styles,minified,this)
88
- }
89
-
90
- url(url, host = this.URL) {
91
- try {
92
- url = new URL(url, host).href.replace(/\/$/, '')
93
- this.meta({ property: 'og:url', content: url })
94
- const canonicalElement = this.root.$('link[rel="canonical"]')
95
- if (canonicalElement) canonicalElement.setAttribute('href', url)
96
- else this.head.insert(2, new SingleNode('link', { rel: 'canonical', href: url }))
97
- } catch (error) {
98
- this.logger.log(`url ${url} with host ${host} is not valid url`)
99
- }
100
- return this
23
+ __status = 200;
24
+ status(status) { this.__status = status; return this }
25
+ end(res) {
26
+ res.writeHead(this.__status, { 'Content-Type': 'text/html' })
27
+ res.end(this.rawHtml)
101
28
  }
102
29
 
30
+ lang(lang) { this.html.setAttribute('lang', lang); return this }
31
+ link(href, attributes) { return addLink(href, attributes, this) }
32
+ keywords(keywords = []) { return addKeywords(keywords, this) }
33
+ style(styles, minified = this.options.minified) { return style(styles, minified, this) }
34
+ url(url, host = this.URL) { return addUrl(url, host, this) }
35
+ meta(props) { return meta(props, this) }
36
+ description(description) { return getDescription(description,this) }
37
+ favicon(href) { return getFavicon(href,this) }
38
+ viewport(viewport) { return getViewport(viewport,this) }
39
+ image(image) { return getImage(image,this) }
103
40
  script(attrs = {}, innerHTML = '', head = true, minified = this.options.minified) {
104
- return script(attrs,innerHTML,head,minified,this)
41
+ return script(attrs, innerHTML, head, minified, this)
105
42
  }
106
43
 
107
- link(href, attributes = { rel: "stylesheet", type: "text/css" }) {
108
- if (!href || typeof href !== 'string') return this
109
- const selector = `link[rel=stylesheet][href^="${href}"]`
110
- let linkElement = this.root.$(selector)
111
- if (linkElement) return
112
- linkElement = new SingleNode('link', { href, ...attributes })
113
- this.head.insert(2, linkElement)
44
+ title(title) {
45
+ super.title // create title tag if not exists
46
+ super.title = title
47
+ this.meta({ property: 'og:title', content: title })
114
48
  return this
115
49
  }
116
50
  }
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "als-layout",
3
- "version": "5.2.0",
3
+ "version": "6.1.0",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
- "test": "node --test --experimental-test-coverage",
7
- "report": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info"
6
+ "test": "node --test --experimental-test-coverage ./tests/**.*",
7
+ "report": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info",
8
+ "docs":"node ./scripts/build-readme.js"
8
9
  },
9
10
  "keywords": [],
10
11
  "author": "Alex Sorkin",
11
12
  "license": "MIT",
12
13
  "description": "Html layout constructor",
13
14
  "dependencies": {
14
- "als-deep-clone": "^1.0.0",
15
15
  "als-document": "^1.4.0",
16
16
  "uglify-js": "^3.19.2",
17
17
  "uglifycss": "^0.0.29"
package/readme.md CHANGED
@@ -17,45 +17,18 @@ npm i als-layout
17
17
  ```js
18
18
  const Layout = require('als-layout')
19
19
  ```
20
-
21
- ## Change Log
22
- * V5.2.0
23
- * options object on clone, cloned too (remove the reference and deep cloning the object)
24
- * V5.1.0
25
- * status and end methods removed
26
- * status and end methods added to als-view
27
- * V5.0.0
28
- * constructor changed
29
- * options object parameter instead separated parameters
30
- * options.logger
31
- * bugs fixed
32
- * some refactoring
33
- * minifiying error catching
34
- * status and end methods added
35
- * V4.2.0
36
- * link method changed
37
- * `link(href, attributes = { rel: "stylesheet", type: "text/css" })`
38
- * no version method
39
- * no version parameter in image, link,script
40
- * script
41
- * script in footer added one after other in right order
42
-
43
- * V4.1.0
44
- * Render removed
45
-
46
- * V4.0.0
47
- * All code rebuilded and refactored
48
- * No als-simple-css for style
49
- * No charset method
50
- * minifying for style and inner scripts
51
- * updated render version
52
- * render as element's method instead layout's method
53
-
54
- * V3.0.0
55
- * render switched to als-render
56
- * updated bug with meta tags after body
57
-
58
-
20
+
21
+ ## Change Log for V6
22
+
23
+ The code refactored.
24
+
25
+ ### Removed
26
+ * no deepclone for options, using { ...Layout.options, ...options } instead
27
+ * no onload method
28
+ ### Added
29
+ * status
30
+ * end(res)
31
+
59
32
  ## Basic Usage
60
33
 
61
34
  ### Initialization
@@ -93,17 +66,7 @@ layout.rawHtml // raw HTML of the document
93
66
  layout.clone // creates a new layout object clone for current object
94
67
  ```
95
68
 
96
- ### onload
97
-
98
- By adding onload attribute, you can run scripts for each element after dom content has loaded.
99
-
100
- Example how it works:
101
- ```js
102
- const layout = new Layout().viewport().title('On load').onload()
103
- layout.body.innerHTML = /*html*/`<div onload="this.innerHTML = 'new content'">original content</div>`
104
- ```
105
69
 
106
-
107
70
  ## Cloning Functionality
108
71
 
109
72
  ### What is Cloning and Why is it Necessary?
@@ -123,7 +86,22 @@ const newLayout = layout.clone;
123
86
 
124
87
  Cloning is particularly useful in scenarios where templates or base layouts are used repeatedly with slight variations, providing a robust and scalable solution for web page generation.
125
88
 
126
-
89
+
90
+ ### propsToClone
91
+
92
+ You can add properties to add when cloning the object, by adding name of properties to `Layout.propsToClone` which is array.
93
+
94
+ Example:
95
+
96
+ ```js
97
+ Layout.propsToClone.push('projectName')
98
+
99
+ const layout = new Layout()
100
+ layout.projectName = 'Cool project'
101
+
102
+ const cloned = layout.clone
103
+ console.log(cloned.projectName) // 'Cool project'
104
+ ```
127
105
  ## Advanced Usage
128
106
 
129
107
  The `als-layout` library allows for sophisticated manipulation of web page layouts, providing robust tools for creating dynamic and complex web pages. Below is an advanced example demonstrating various capabilities of the library:
@@ -172,7 +150,17 @@ In this example:
172
150
 
173
151
  This advanced example illustrates how `als-layout` can be used to handle complex scenarios and requirements in web development, enhancing the flexibility and power at your disposal.
174
152
 
175
-
153
+
154
+ ## Response with status
155
+
156
+ In version 6, added two methods:
157
+ 1. `status(statusCode)` - adds the statusCode to `__status` property
158
+ 2. `end(res)`
159
+ 1. writes head with `__status, { 'Content-Type': 'text/html' }`
160
+ 2. runs `res.end(this.rawHtml)`
161
+
162
+
163
+ The main idea, is to add quick way to response with layout.
176
164
  ## API
177
165
 
178
166
  ### Constructor
@@ -198,16 +186,17 @@ Returns the inner HTML of the document.
198
186
 
199
187
  Creates a clone of the current layout instance, preserving `options`.
200
188
 
189
+
190
+ #### `__status`
191
+
192
+ Stores the statusCode for `end` method.
193
+
201
194
  ### Methods
202
195
 
203
196
  #### `lang(lang: string): this`
204
197
 
205
198
  Sets the `lang` attribute on the `<html>` element.
206
199
 
207
- #### `onload(): this`
208
-
209
- Adds an `onload` script to the document. If called multiple times, the script is only added once.
210
-
211
200
  #### `title(title: string): this`
212
201
 
213
202
  Sets the document title and creates an Open Graph title meta tag.
@@ -261,3 +250,11 @@ Adds a `<script>` tag to the document.
261
250
  #### `link(href: string, attributes: object = { rel: "stylesheet", type: "text/css" }): this`
262
251
 
263
252
  Adds a `<link>` tag for external stylesheets.
253
+
254
+ #### `status(statusCode:number): this`
255
+
256
+ Adds the statusCode to `__status` property
257
+
258
+ #### `end(res):undefined`
259
+
260
+ Response with res(rawHtml)
@@ -1,8 +1,8 @@
1
1
  const { readFileSync, writeFileSync, readdirSync } = require('fs')
2
2
  const { join } = require('path')
3
3
 
4
- const docsDir = join(__dirname, 'docs')
4
+ const docsDir = join(__dirname, '..','docs')
5
5
  const files = readdirSync(docsDir)
6
6
  const content = files.map(file => readFileSync(join(docsDir, file), 'utf-8')).join('\n');
7
- writeFileSync(join(__dirname, 'readme.md'), content, 'utf-8')
7
+ writeFileSync(join(__dirname, '..','readme.md'), content, 'utf-8')
8
8
 
@@ -30,12 +30,6 @@ describe('Layout Integrative tests', () => {
30
30
  assert(layout.root.$('title') === null, 'Title element was not removed correctly');
31
31
  });
32
32
 
33
- it('onload', () => {
34
- layout.onload()
35
- const script = layout.root.$('script')
36
- assert(script.innerHTML.includes(`document.addEventListener('DOMContentLoaded'`))
37
- })
38
-
39
33
  });
40
34
 
41
35
  describe('HTML Structure Initialization', () => {
@@ -81,7 +75,7 @@ describe('Clone Testing', () => {
81
75
 
82
76
  it('should clone the layout correctly', () => {
83
77
  const clone = layout.clone;
84
- console.log(clone.constructor.name)
78
+ // console.log(clone.constructor.name)
85
79
  assert(clone instanceof Layout, 'Clone should be an instance of Layout');
86
80
  assert.notStrictEqual(clone, layout, 'Clone should not be the same instance as the original');
87
81
  });
package/lib/onload.js DELETED
@@ -1,11 +0,0 @@
1
- const onloadScript = /*js*/`document.addEventListener('DOMContentLoaded', function() {
2
- const elements = document.querySelectorAll('[onload]');
3
- elements.forEach(element => {
4
- const onloadCode = element.getAttribute('onload');
5
- const func = Function('"use strict"; return function() { ' + onloadCode + ' }');
6
- func().call(element);
7
- element.removeAttribute('onload');
8
- });
9
- });`;
10
-
11
- module.exports = onloadScript
File without changes
File without changes