als-layout 6.1.0 → 7.0.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/build.js +8 -0
- package/index.mjs +83 -0
- package/layout.js +127 -0
- package/package.json +14 -8
- package/readme.md +101 -198
- package/src/layout.js +85 -0
- package/tests/constructor.test.js +2 -2
- package/tests/description.test.js +1 -1
- package/tests/favicon.test.js +1 -1
- package/tests/image.test.js +1 -1
- package/tests/integrative.test.js +1 -1
- package/tests/keywords.test.js +1 -1
- package/tests/link.test.js +4 -14
- package/tests/script.test.js +1 -2
- package/tests/style.test.js +2 -9
- package/tests/url.test.js +3 -11
- package/tests/viewport.test.js +1 -1
- package/docs/#.md +0 -19
- package/docs/0-change-log.md +0 -10
- package/docs/1-basic-usage.md +0 -37
- package/docs/2-cloning.md +0 -35
- package/docs/3-advanced-usage.md +0 -48
- package/docs/4-res-and-status.md +0 -10
- package/docs/5-api.md +0 -97
- package/index.js +0 -3
- package/lib/elements/index.js +0 -38
- package/lib/elements/keywords.js +0 -19
- package/lib/elements/link.js +0 -13
- package/lib/elements/meta.js +0 -12
- package/lib/elements/script.js +0 -25
- package/lib/elements/style.js +0 -20
- package/lib/elements/url.js +0 -18
- package/lib/layout.js +0 -52
- package/scripts/build-readme.js +0 -8
package/package.json
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "als-layout",
|
|
3
|
-
"version": "
|
|
4
|
-
"main": "
|
|
3
|
+
"version": "7.0.0",
|
|
4
|
+
"main": "./main/layout.js",
|
|
5
|
+
"module": "./index.mjs",
|
|
5
6
|
"scripts": {
|
|
6
7
|
"test": "node --test --experimental-test-coverage ./tests/**.*",
|
|
7
8
|
"report": "node --test --experimental-test-coverage --test-reporter=lcov --test-reporter-destination=lcov.info",
|
|
8
|
-
"docs":"node ./scripts/build-readme.js"
|
|
9
|
+
"docs": "node ./scripts/build-readme.js"
|
|
9
10
|
},
|
|
10
|
-
"keywords": [
|
|
11
|
+
"keywords": [
|
|
12
|
+
"html",
|
|
13
|
+
"layout",
|
|
14
|
+
"document",
|
|
15
|
+
"meta",
|
|
16
|
+
"seo",
|
|
17
|
+
"als-document"
|
|
18
|
+
],
|
|
11
19
|
"author": "Alex Sorkin",
|
|
12
20
|
"license": "MIT",
|
|
13
|
-
"description": "
|
|
21
|
+
"description": "HTML layout constructor with dynamic meta, styles, and scripts",
|
|
14
22
|
"dependencies": {
|
|
15
|
-
"als-document": "^1.4.
|
|
16
|
-
"uglify-js": "^3.19.2",
|
|
17
|
-
"uglifycss": "^0.0.29"
|
|
23
|
+
"als-document": "^1.4.3"
|
|
18
24
|
}
|
|
19
25
|
}
|
package/readme.md
CHANGED
|
@@ -7,254 +7,157 @@ The `als-layout` library is versatile, suitable for:
|
|
|
7
7
|
- Creating templating systems where multiple page layouts share similar structures but differ in content or styling.
|
|
8
8
|
- Developing web applications that need to dynamically adjust their UI based on user interactions or data changes.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Installation and Import
|
|
13
|
+
|
|
14
|
+
Install via npm:
|
|
12
15
|
|
|
13
16
|
```bash
|
|
14
17
|
npm i als-layout
|
|
15
18
|
```
|
|
16
19
|
|
|
20
|
+
### Node (CommonJS)
|
|
17
21
|
```js
|
|
18
22
|
const Layout = require('als-layout')
|
|
19
23
|
```
|
|
20
24
|
|
|
21
|
-
|
|
25
|
+
### ESM (Module)
|
|
26
|
+
```js
|
|
27
|
+
import Layout from 'als-layout'
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Browser (with bundle)
|
|
31
|
+
```html
|
|
32
|
+
<script src="layout.js"></script>
|
|
33
|
+
<script>
|
|
34
|
+
const layout = new Layout()
|
|
35
|
+
</script>
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Change Log for V7
|
|
22
41
|
|
|
23
|
-
|
|
42
|
+
**⚠ Breaking changes: not backward compatible.**
|
|
24
43
|
|
|
25
44
|
### Removed
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
- `uglify js and css`
|
|
46
|
+
- `status` method
|
|
47
|
+
- `end` method
|
|
48
|
+
- `minified` attribute
|
|
49
|
+
- `propsToClone`
|
|
50
|
+
|
|
51
|
+
### Changed
|
|
52
|
+
- All code is merged into a single file
|
|
53
|
+
- Added **ESM module build** (`index.mjs`)
|
|
54
|
+
- Added **browser bundle** (`layout.js`) that includes `als-document` and exposes a global `Layout`
|
|
55
|
+
|
|
56
|
+
---
|
|
31
57
|
|
|
32
58
|
## Basic Usage
|
|
33
59
|
|
|
34
60
|
### Initialization
|
|
35
|
-
To start using `als-layout`, you first need to create a new instance of `Layout`. This instance will serve as the foundation for building and modifying your web page.
|
|
36
|
-
|
|
37
61
|
```js
|
|
38
|
-
const Layout = require('als-layout');
|
|
39
62
|
const layout = new Layout();
|
|
40
63
|
```
|
|
41
64
|
|
|
42
|
-
### Adding
|
|
43
|
-
Once you have your `Layout` instance, you can easily add or modify various elements of your web page. Here are some examples of how you can use the library to customize your layout:
|
|
44
|
-
|
|
65
|
+
### Adding Elements
|
|
45
66
|
```js
|
|
46
67
|
const layout = new Layout()
|
|
47
|
-
.viewport() // default width=device-width, initial-scale=1.0
|
|
48
|
-
.title('Test title') //
|
|
49
|
-
.favicon('/favicon.png') //
|
|
50
|
-
.keywords(['some', 'keyword']) //
|
|
51
|
-
.image('/main-image.png'
|
|
52
|
-
.description('Cool site') //
|
|
53
|
-
.url('/some', 'http://site.com') //
|
|
54
|
-
.style('body {margin:0; background-color:whitesmoke;}'
|
|
55
|
-
.link('/styles.css') //
|
|
56
|
-
.script({src:'/app.js'}
|
|
57
|
-
.script({}, 'console.log("hello world")', false) //
|
|
58
|
-
|
|
59
|
-
// Accessors
|
|
60
|
-
layout.body // getter for body
|
|
61
|
-
layout.head // getter for head
|
|
62
|
-
layout.html // getter for html
|
|
68
|
+
.viewport() // default: width=device-width, initial-scale=1.0
|
|
69
|
+
.title('Test title') // sets <title> and meta[og:title]
|
|
70
|
+
.favicon('/favicon.png') // adds/updates favicon link
|
|
71
|
+
.keywords(['some', 'keyword']) // updates meta[name=keywords]
|
|
72
|
+
.image('/main-image.png') // sets og:image, twitter:image, twitter:card
|
|
73
|
+
.description('Cool site') // sets meta description tags
|
|
74
|
+
.url('/some', 'http://site.com') // sets canonical + og:url
|
|
75
|
+
.style('body {margin:0; background-color:whitesmoke;}') // appends CSS to style tag
|
|
76
|
+
.link('/styles.css') // adds stylesheet link if not already present
|
|
77
|
+
.script({src:'/app.js'}) // external script
|
|
78
|
+
.script({}, 'console.log("hello world")', false) // inline script in body
|
|
79
|
+
|
|
80
|
+
// Accessors
|
|
81
|
+
layout.body // getter for <body>
|
|
82
|
+
layout.head // getter for <head>
|
|
83
|
+
layout.html // getter for <html>
|
|
63
84
|
|
|
64
85
|
// Outputs
|
|
65
|
-
layout.rawHtml // raw HTML
|
|
66
|
-
layout.clone
|
|
86
|
+
layout.rawHtml // raw HTML string
|
|
87
|
+
layout.clone // cloned Layout instance
|
|
67
88
|
```
|
|
68
89
|
|
|
90
|
+
---
|
|
69
91
|
|
|
70
|
-
## Cloning
|
|
71
|
-
|
|
72
|
-
### What is Cloning and Why is it Necessary?
|
|
73
|
-
Cloning in the `als-layout` library refers to creating a complete, independent copy of the existing `Layout` instance. This functionality is crucial when you need to generate multiple pages or versions of a page from a single base layout without affecting the original setup.
|
|
92
|
+
## Cloning
|
|
74
93
|
|
|
75
|
-
###
|
|
76
|
-
|
|
94
|
+
### Why Clone?
|
|
95
|
+
Cloning creates a complete, independent copy of a `Layout` instance. Useful when generating multiple pages from a base template.
|
|
77
96
|
|
|
97
|
+
### Example
|
|
78
98
|
```js
|
|
79
99
|
const newLayout = layout.clone;
|
|
100
|
+
newLayout.title('Cloned page');
|
|
80
101
|
```
|
|
81
102
|
|
|
82
|
-
|
|
83
|
-
- **
|
|
84
|
-
- **
|
|
85
|
-
- **Isolation:** Changes made to a cloned `Layout` do not affect the original, ensuring that each instance can be modified independently based on specific requirements.
|
|
86
|
-
|
|
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.
|
|
88
|
-
|
|
103
|
+
- **Efficiency** – avoids rebuilding from scratch
|
|
104
|
+
- **Isolation** – modifications to clones don’t affect the original
|
|
105
|
+
- **Performance** – fast even for larger layouts
|
|
89
106
|
|
|
90
|
-
|
|
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')
|
|
107
|
+
---
|
|
98
108
|
|
|
99
|
-
const layout = new Layout()
|
|
100
|
-
layout.projectName = 'Cool project'
|
|
101
|
-
|
|
102
|
-
const cloned = layout.clone
|
|
103
|
-
console.log(cloned.projectName) // 'Cool project'
|
|
104
|
-
```
|
|
105
109
|
## Advanced Usage
|
|
106
110
|
|
|
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:
|
|
108
|
-
|
|
109
111
|
```js
|
|
110
|
-
const Layout = require('als-layout')
|
|
111
|
-
|
|
112
|
-
// Starting with a basic HTML template and specifying the host for URL methods
|
|
113
112
|
const raw = /*html*/`<html></html>`
|
|
114
113
|
const host = 'http://example.com';
|
|
115
|
-
const options = {
|
|
116
|
-
logger,
|
|
117
|
-
host,
|
|
118
|
-
minified=false
|
|
119
|
-
}
|
|
120
|
-
const layout = new Layout(raw, options).lang('fr')
|
|
121
|
-
console.log(layout.rawHtml)
|
|
122
|
-
// <!DOCTYPE html><html lang="fr"><head></p></head><body></body></html>
|
|
123
|
-
|
|
124
|
-
// Cloning the initial layout to create a specialized page
|
|
125
|
-
const homePage = layout.clone
|
|
126
|
-
homeAutoReload = layout.clone
|
|
127
|
-
homePage.title('Home page')
|
|
128
|
-
homePage.body.innerHTML = /*html*/`<h1>Home page</h1>`
|
|
129
|
-
console.log(homePage.rawHtml)
|
|
130
|
-
// <!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>
|
|
131
|
-
|
|
132
|
-
// Adding script that reloads the page every minute
|
|
133
|
-
homeAutoReload.script({}, 'setTimeout(function() { window.location.reload(); }, 60000);', false)
|
|
134
|
-
console.log(homeAutoReload.rawHtml)
|
|
135
|
-
// <!DOCTYPE html><html lang="fr"><head><title>Automatic Reload Page</title><meta property="og:title" content="Automatic Reload Page"></head><body><script>setTimeout(function() { window.location.reload(); }, 60000);</script></body></html>
|
|
136
|
-
|
|
137
|
-
// Demonstrating dynamic stylesheet linkage with versioning
|
|
138
|
-
homePage.link('/css/main.css')
|
|
139
|
-
console.log(homePage.rawHtml)
|
|
140
|
-
// Includes link to the stylesheet with version parameter to ensure fresh cache
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
In this example:
|
|
144
|
-
|
|
145
|
-
- We start with a basic HTML template and use the `lang` method to set the language.
|
|
146
|
-
- We use the `clone` method to create two versions of the base layout: one for the home page and another that automatically reloads every minute.
|
|
147
|
-
- We manipulate the `body` of the `homePage` to include custom HTML.
|
|
148
|
-
- We add a script to `homeAutoReload` that sets up an automatic page reload, showcasing how to insert JavaScript dynamically.
|
|
149
|
-
- We dynamically add a versioned link to a stylesheet in the `homePage`, demonstrating control over caching and resource management.
|
|
150
114
|
|
|
151
|
-
|
|
115
|
+
const layout = new Layout(raw, host).lang('fr');
|
|
116
|
+
console.log(layout.rawHtml);
|
|
117
|
+
// <!DOCTYPE html><html lang="fr"><head></head><body></body></html>
|
|
152
118
|
|
|
119
|
+
const homePage = layout.clone;
|
|
120
|
+
homePage.title('Home page');
|
|
121
|
+
homePage.body.innerHTML = `<h1>Home page</h1>`;
|
|
122
|
+
console.log(homePage.rawHtml);
|
|
153
123
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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)`
|
|
124
|
+
const autoReload = layout.clone;
|
|
125
|
+
autoReload.script({}, 'setTimeout(() => window.location.reload(), 60000);', false);
|
|
126
|
+
```
|
|
161
127
|
|
|
128
|
+
---
|
|
162
129
|
|
|
163
|
-
The main idea, is to add quick way to response with layout.
|
|
164
130
|
## API
|
|
165
131
|
|
|
166
132
|
### Constructor
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
- **html**: The initial HTML document string.
|
|
173
|
-
- **options**: Configuration options for the layout instance.
|
|
174
|
-
- **minified**: Boolean (default: `false`). Determines if inline CSS and JavaScript should be minified.
|
|
175
|
-
- **logger**: Function (default: `console`). Function for errors in minifiying and url method.
|
|
176
|
-
- **host**: String (defult:`undefined`). String for url method.
|
|
177
|
-
|
|
133
|
+
```js
|
|
134
|
+
new Layout(html?: string, host?: string)
|
|
135
|
+
```
|
|
136
|
+
- **html**: optional HTML string
|
|
137
|
+
- **host**: base host for resolving relative URLs
|
|
178
138
|
|
|
179
139
|
### Properties
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
Returns the inner HTML of the document.
|
|
184
|
-
|
|
185
|
-
#### `layout.clone`
|
|
186
|
-
|
|
187
|
-
Creates a clone of the current layout instance, preserving `options`.
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
#### `__status`
|
|
191
|
-
|
|
192
|
-
Stores the statusCode for `end` method.
|
|
140
|
+
- `layout.rawHtml` – returns the current document as HTML string
|
|
141
|
+
- `layout.clone` – returns a cloned instance
|
|
193
142
|
|
|
194
143
|
### Methods
|
|
144
|
+
- `lang(lang: string)` – sets `<html lang>`
|
|
145
|
+
- `title(title: string)` – sets document `<title>` and Open Graph title
|
|
146
|
+
- `description(description: string)` – sets description meta tags
|
|
147
|
+
- `favicon(href: string)` – sets or updates favicon
|
|
148
|
+
- `meta(props: object)` – sets or updates a `<meta>` tag
|
|
149
|
+
- `keywords(keywords: string[])` – sets or appends to `meta[name=keywords]`
|
|
150
|
+
- `viewport(viewport: string = 'width=device-width, initial-scale=1.0')` – sets viewport meta
|
|
151
|
+
- `image(image: string)` – sets OG and Twitter image meta tags
|
|
152
|
+
- `style(styles: string)` – appends CSS styles into `<style>`
|
|
153
|
+
- `url(url: string, host?: string)` – sets canonical + og:url
|
|
154
|
+
- `script(attrs: object = {}, innerHTML: string = '', head: boolean = true)` – adds `<script>` tag
|
|
155
|
+
- `link(href: string, attributes: object = { rel: "stylesheet", type: "text/css" })` – adds stylesheet link
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Notes
|
|
160
|
+
- Version 7 removed **status**, **end**, **minified**, and **propsToClone**.
|
|
161
|
+
- Use `clone` for creating independent layouts instead of extending clone properties.
|
|
162
|
+
- Use `layout.js` in the browser for quick setup (exposes global `Layout`).
|
|
195
163
|
|
|
196
|
-
#### `lang(lang: string): this`
|
|
197
|
-
|
|
198
|
-
Sets the `lang` attribute on the `<html>` element.
|
|
199
|
-
|
|
200
|
-
#### `title(title: string): this`
|
|
201
|
-
|
|
202
|
-
Sets the document title and creates an Open Graph title meta tag.
|
|
203
|
-
|
|
204
|
-
#### `description(description: string): this`
|
|
205
|
-
|
|
206
|
-
Adds description meta tags for SEO and social platforms.
|
|
207
|
-
|
|
208
|
-
#### `favicon(href: string): this`
|
|
209
|
-
|
|
210
|
-
Sets or updates the favicon URL.
|
|
211
|
-
|
|
212
|
-
#### `meta(props: object): this`
|
|
213
|
-
|
|
214
|
-
Adds or updates a `<meta>` tag with specified attributes.
|
|
215
|
-
|
|
216
|
-
- **props**: An object where each key-value pair corresponds to a meta attribute.
|
|
217
|
-
|
|
218
|
-
#### `keywords(keywords: array): this`
|
|
219
|
-
|
|
220
|
-
Sets or appends keywords in the `content` attribute of a `<meta name="keywords">` tag.
|
|
221
|
-
|
|
222
|
-
#### `viewport(viewport: string): this`
|
|
223
|
-
|
|
224
|
-
Sets the viewport meta tag. Default: `width=device-width, initial-scale=1.0`.
|
|
225
|
-
|
|
226
|
-
#### `image(image: string): this`
|
|
227
|
-
|
|
228
|
-
Sets meta tags for Open Graph and Twitter cards for an image.
|
|
229
|
-
|
|
230
|
-
#### `style(styles: string, minified: boolean = this.options.minified): this`
|
|
231
|
-
|
|
232
|
-
Adds inline CSS to the document. If `minified` is true, the CSS is minified.
|
|
233
|
-
|
|
234
|
-
- **styles**: CSS string.
|
|
235
|
-
- **minified**: Boolean (optional).
|
|
236
|
-
|
|
237
|
-
#### `url(url: string, host: string = this.URL): this`
|
|
238
|
-
|
|
239
|
-
Sets the canonical URL and Open Graph URL meta tags.
|
|
240
|
-
|
|
241
|
-
#### `script(attrs: object = {}, innerHTML: string = '', head: boolean = true, minified: boolean = this.options.minified): this`
|
|
242
|
-
|
|
243
|
-
Adds a `<script>` tag to the document.
|
|
244
|
-
|
|
245
|
-
- **attrs**: Attributes for the `<script>` tag.
|
|
246
|
-
- **innerHTML**: Inline JavaScript (optional).
|
|
247
|
-
- **head**: Boolean (default: `true`). If `false`, the script is added to `<body>`.
|
|
248
|
-
- **minified**: Boolean. If true, inline JavaScript is minified.
|
|
249
|
-
|
|
250
|
-
#### `link(href: string, attributes: object = { rel: "stylesheet", type: "text/css" }): this`
|
|
251
|
-
|
|
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)
|
package/src/layout.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
const { Document, SingleNode, Node } = require('als-document');
|
|
2
|
+
class Layout extends Document {
|
|
3
|
+
constructor(html, host) { super(html, host); this.root = this.html; }
|
|
4
|
+
get rawHtml() { return this.innerHTML }
|
|
5
|
+
get clone() { return new this.constructor(new Document(this, this.URL), this.host) }
|
|
6
|
+
lang(lang) { this.html.setAttribute('lang', lang); return this }
|
|
7
|
+
link(href, attributes = { rel: "stylesheet", type: "text/css" }) {
|
|
8
|
+
if (!href || typeof href !== 'string') throw new Error(`href attribute must be a string`)
|
|
9
|
+
if (!this.root.querySelector(`link[rel=stylesheet][href^="${href}"]`))
|
|
10
|
+
this.head.insert(2, new SingleNode('link', { href, ...attributes }))
|
|
11
|
+
return this
|
|
12
|
+
}
|
|
13
|
+
keywords(keywords = []) {
|
|
14
|
+
let el = this.root.$('meta[name=keywords]') || new SingleNode('meta', { name: 'keywords' })
|
|
15
|
+
keywords = new Set([...(el.getAttribute('content') || '').split(','),...keywords.map(k => k.trim())].filter(Boolean))
|
|
16
|
+
if (keywords.size) el.setAttribute('content', Array.from(keywords).join(','))
|
|
17
|
+
if(keywords.size && !el.parent) this.head.insert(2, el)
|
|
18
|
+
return this
|
|
19
|
+
}
|
|
20
|
+
style(styles) {
|
|
21
|
+
if (typeof styles !== 'string') throw 'styles parameter should be string';
|
|
22
|
+
let el = this.root.$('style') || this.head.insert(2, new Node('style'))
|
|
23
|
+
el.innerHTML = el.innerHTML + '\n' + styles;
|
|
24
|
+
return this
|
|
25
|
+
}
|
|
26
|
+
url(url, host = this.URL) {
|
|
27
|
+
try {
|
|
28
|
+
url = (host ? new URL(url, host) : new URL(url)).href.replace(/\/$/, '')
|
|
29
|
+
this.meta({ property: 'og:url', content: url })
|
|
30
|
+
const el = this.root.$('link[rel="canonical"]') || this.head.insert(2, new SingleNode('link', { rel: 'canonical', href: url }))
|
|
31
|
+
el.setAttribute('href', url)
|
|
32
|
+
} catch (error) { error.info = `url "${url}" with host "${host}" is not valid url`; throw error; }
|
|
33
|
+
return this
|
|
34
|
+
}
|
|
35
|
+
meta(props) {
|
|
36
|
+
const entries = Object.entries(props)
|
|
37
|
+
const [name, value] = entries[0]
|
|
38
|
+
const metaElement = this.root.querySelector(`meta[${name}="${value}"]`) || this.head.insert(2, new SingleNode('meta', props))
|
|
39
|
+
entries.forEach(([name, v]) => metaElement.setAttribute(name, props[name]))
|
|
40
|
+
return this
|
|
41
|
+
}
|
|
42
|
+
script(attrs = {}, innerHTML = '', head = true) {
|
|
43
|
+
if (typeof attrs !== 'object' || attrs === null || Array.isArray(attrs)) attrs = {}
|
|
44
|
+
if (attrs.src && this.root.querySelector(`script[src="${attrs.src}"]`)) return this
|
|
45
|
+
if (Object.keys(attrs).length || innerHTML) {
|
|
46
|
+
const script = new Node('script', attrs)
|
|
47
|
+
if (innerHTML) script.innerHTML = innerHTML
|
|
48
|
+
if (head) this.head.insert(2, script)
|
|
49
|
+
else this.html.insert(2, script)
|
|
50
|
+
}
|
|
51
|
+
return this
|
|
52
|
+
}
|
|
53
|
+
description(description) {
|
|
54
|
+
this.meta({ name: 'description', content: description })
|
|
55
|
+
this.meta({ property: 'og:description', content: description })
|
|
56
|
+
this.meta({ property: 'twitter:description', content: description })
|
|
57
|
+
return this
|
|
58
|
+
}
|
|
59
|
+
favicon(href) {
|
|
60
|
+
const el = this.root.$('link[rel=icon][type=image/x-icon]')
|
|
61
|
+
if (el) el.setAttribute('href', href)
|
|
62
|
+
else this.head.insert(2, new SingleNode('link', { rel: 'icon', href, type: 'image/x-icon' }))
|
|
63
|
+
return this
|
|
64
|
+
}
|
|
65
|
+
viewport(viewport = 'width=device-width, initial-scale=1.0') {
|
|
66
|
+
const el = this.root.$('meta[name="viewport"]')
|
|
67
|
+
if (el) el.setAttribute('content', viewport)
|
|
68
|
+
else this.head.insert(2, new SingleNode('meta', { name: 'viewport', content: viewport }))
|
|
69
|
+
return this
|
|
70
|
+
}
|
|
71
|
+
image(image) {
|
|
72
|
+
this.meta({ property: 'og:image', content: image })
|
|
73
|
+
this.meta({ name: 'twitter:image', content: image })
|
|
74
|
+
this.meta({ name: 'twitter:card', content: 'summary_large_image' })
|
|
75
|
+
return this
|
|
76
|
+
}
|
|
77
|
+
title(title) {
|
|
78
|
+
super.title // create title tag if not exists
|
|
79
|
+
super.title = title
|
|
80
|
+
this.meta({ property: 'og:title', content: title })
|
|
81
|
+
return this
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = Layout
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
const { describe, it } = require('node:test')
|
|
3
|
-
const Layout = require('../
|
|
3
|
+
const Layout = require('../src/layout.js');
|
|
4
4
|
|
|
5
5
|
describe('Layout Initialization', () => {
|
|
6
6
|
it('should create an instance of Layout', () => {
|
|
@@ -22,7 +22,7 @@ describe('Layout Initialization', () => {
|
|
|
22
22
|
|
|
23
23
|
it('should allow setting a custom host', () => {
|
|
24
24
|
const customHost = 'http://localhost';
|
|
25
|
-
const layout = new Layout(undefined,
|
|
25
|
+
const layout = new Layout(undefined,customHost);
|
|
26
26
|
assert.strictEqual(layout.URL, customHost, `host is not set to ${customHost}`);
|
|
27
27
|
});
|
|
28
28
|
});
|
package/tests/favicon.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
const { describe, it, beforeEach } = require('node:test')
|
|
3
3
|
const { SingleNode } = require('als-document')
|
|
4
|
-
const Layout = require('../
|
|
4
|
+
const Layout = require('../src/layout.js');
|
|
5
5
|
|
|
6
6
|
describe('Favicon tests', () => {
|
|
7
7
|
let layout;
|
package/tests/image.test.js
CHANGED
package/tests/keywords.test.js
CHANGED
package/tests/link.test.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
const { describe, it, beforeEach } = require('node:test')
|
|
3
|
-
const Layout = require('../
|
|
4
|
-
|
|
3
|
+
const Layout = require('../src/layout.js');
|
|
5
4
|
|
|
6
5
|
describe('Link', () => {
|
|
7
6
|
let layout;
|
|
@@ -13,12 +12,9 @@ describe('Link', () => {
|
|
|
13
12
|
assert.strictEqual(layout.root.$('link[rel="stylesheet"]').getAttribute('href'), href, 'Link href should match the provided href');
|
|
14
13
|
});
|
|
15
14
|
|
|
16
|
-
it('should
|
|
17
|
-
layout.link(''
|
|
18
|
-
layout.link(null
|
|
19
|
-
// layout.link('style.css', '');
|
|
20
|
-
// layout.link('style.css', null);
|
|
21
|
-
assert.strictEqual(layout.root.$('link[rel="stylesheet"]'), null, 'Should not add a link when href or version are invalid');
|
|
15
|
+
it('should throw error for invalid href', () => {
|
|
16
|
+
assert.throws(() => layout.link(''));
|
|
17
|
+
assert.throws(() => layout.link(null));
|
|
22
18
|
});
|
|
23
19
|
|
|
24
20
|
it('should add link correctly', () => {
|
|
@@ -34,10 +30,4 @@ describe('Link', () => {
|
|
|
34
30
|
assert.strictEqual(layout.root.$$(`link[rel="stylesheet"][href="${href}"]`).length, 1, 'Should not add duplicate link without version');
|
|
35
31
|
});
|
|
36
32
|
|
|
37
|
-
it('should not add a link if href is undefined or null', () => {
|
|
38
|
-
layout.link(undefined, '1.0');
|
|
39
|
-
layout.link(null);
|
|
40
|
-
assert.strictEqual(layout.root.$('link[rel="stylesheet"]'), null, 'Should not add a link when href is undefined or null');
|
|
41
|
-
});
|
|
42
|
-
|
|
43
33
|
})
|
package/tests/script.test.js
CHANGED
package/tests/style.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
const { describe, it, beforeEach } = require('node:test')
|
|
3
|
-
const Layout = require('../
|
|
4
|
-
|
|
3
|
+
const Layout = require('../src/layout.js');
|
|
4
|
+
|
|
5
5
|
describe('Styles', () => {
|
|
6
6
|
let layout;
|
|
7
7
|
beforeEach(() => layout = new Layout());
|
|
@@ -31,11 +31,4 @@ describe('Styles', () => {
|
|
|
31
31
|
assert(inner.includes(styles2));
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
it('should add minified style', () => {
|
|
35
|
-
const styles = 'body { color: blue; } div { font-size: 12px; }';
|
|
36
|
-
const uglified = uglifycss.processString(styles);
|
|
37
|
-
layout.style(styles,true);
|
|
38
|
-
assert(layout.root.$('style').innerHTML === uglified, 'Styles should be minified');
|
|
39
|
-
});
|
|
40
|
-
|
|
41
34
|
})
|
package/tests/url.test.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
const { describe, it, beforeEach } = require('node:test')
|
|
3
|
-
const Layout = require('../
|
|
3
|
+
const Layout = require('../src/layout.js');
|
|
4
4
|
|
|
5
5
|
describe('Url', () => {
|
|
6
6
|
let layout;
|
|
7
7
|
beforeEach(() => layout = new Layout());
|
|
8
8
|
|
|
9
|
-
it('should
|
|
10
|
-
layout.url('not-a-url', 'aa');
|
|
11
|
-
assert.strictEqual(layout.root.$('link[rel="canonical"]'), null, 'Canonical URL should not be added for invalid URLs');
|
|
9
|
+
it('should throw error if canonical URL if URL is invalid', () => {
|
|
10
|
+
assert.throws(() => layout.url('not-a-url', 'aa'));
|
|
12
11
|
});
|
|
13
12
|
|
|
14
13
|
it('should add url correctly', () => {
|
|
@@ -40,11 +39,4 @@ describe('Url', () => {
|
|
|
40
39
|
assert.strictEqual(layout.root.$('link[rel="canonical"]').getAttribute('href'), newUrl, 'Canonical link should be updated with new URL');
|
|
41
40
|
});
|
|
42
41
|
|
|
43
|
-
it('should handle invalid URL with valid host', () => {
|
|
44
|
-
layout.url('http://', 'http://example.com');
|
|
45
|
-
assert.strictEqual(layout.root.$('meta[property="og:url"]'), null, 'Meta og:url should not be added for invalid URL');
|
|
46
|
-
assert.strictEqual(layout.root.$('link[rel="canonical"]'), null, 'Canonical link should not be added for invalid URL');
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
|
|
50
42
|
})
|
package/tests/viewport.test.js
CHANGED