mirror of
https://github.com/modernweb-dev/rocket.git
synced 2026-03-21 15:54:57 +00:00
Compare commits
12 Commits
@rocket/bl
...
@rocket/la
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eb6a23dc6a | ||
|
|
23027bb684 | ||
|
|
cd22231806 | ||
|
|
b1f61c7759 | ||
|
|
741f695106 | ||
|
|
156719f977 | ||
|
|
295cfbdbd8 | ||
|
|
7dd6f4c64f | ||
|
|
b68923b608 | ||
|
|
86c3a4b0e8 | ||
|
|
897892d6f9 | ||
|
|
7b2dc6430f |
@@ -22,7 +22,7 @@ module.exports = function (eleventyConfig) {
|
|||||||
|
|
||||||
By providing a `setupUnifiedPlugins` function as an option to `eleventy-plugin-mdjs` you can set options for all unified/remark plugins.
|
By providing a `setupUnifiedPlugins` function as an option to `eleventy-plugin-mdjs` you can set options for all unified/remark plugins.
|
||||||
|
|
||||||
We do use [plugins-manager](../plugins-manager/overview.md).
|
We do use [plugins-manager](../tools/plugins-manager.md).
|
||||||
|
|
||||||
This example adds a CSS class to the `htmlHeading` plugin so heading links can be selected in CSS.
|
This example adds a CSS class to the `htmlHeading` plugin so heading links can be selected in CSS.
|
||||||
|
|
||||||
|
|||||||
38
docs/docs/tools/check-html-links.md
Normal file
38
docs/docs/tools/check-html-links.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Tools >> Check Html Links ||30
|
||||||
|
|
||||||
|
A fast checker for broken links/references in html.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Checks all html files for broken local links/references (in href, src, srcset)
|
||||||
|
- Focuses on the broken reference targets and groups references to it
|
||||||
|
- Fast (can process 500-1000 documents in ~2-3 seconds)
|
||||||
|
- Has only 3 dependencies (and 19 in the full tree)
|
||||||
|
- Uses [sax-wasm](https://github.com/justinwilaby/sax-wasm) for parsing streamed html
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
npm i -D check-html-links
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
npx check-html-links _site
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Output
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Comparision
|
||||||
|
|
||||||
|
Checking the output of [11ty-website](https://github.com/11ty/11ty-website) with 13 missing reference targets (used by 516 links) while checking 501 files. (on January 17, 2021)
|
||||||
|
|
||||||
|
| Tool | Lines printed | Times | Lang | Dependency Tree |
|
||||||
|
| ---------------- | ------------- | ------ | ---- | --------------- |
|
||||||
|
| check-html-links | 38 | ~2.5s | node | 19 |
|
||||||
|
| link-checker | 3000+ | ~11s | node | 106 |
|
||||||
|
| hyperlink | 68 | 4m 20s | node | 481 |
|
||||||
|
| htmltest | 1000+ | ~0.7s | GO | - |
|
||||||
BIN
docs/docs/tools/images/check-html-links-screenshot.png
Normal file
BIN
docs/docs/tools/images/check-html-links-screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 214 KiB |
@@ -1,4 +1,4 @@
|
|||||||
# Tools >> Rollup Config
|
# Tools >> Rollup Config ||20
|
||||||
|
|
||||||
Rollup configuration to help you get started building modern web applications.
|
Rollup configuration to help you get started building modern web applications.
|
||||||
You write modern javascript using the latest browser-features, rollup will optimize your code for production and ensure it runs on all supported browsers.
|
You write modern javascript using the latest browser-features, rollup will optimize your code for production and ensure it runs on all supported browsers.
|
||||||
@@ -52,7 +52,7 @@ You write modern javascript using the latest browser-features, rollup will optim
|
|||||||
|
|
||||||
Our config sets you up with good defaults for most projects. Additionally you can add more plugins and adjust predefined plugins or even remove them if needed.
|
Our config sets you up with good defaults for most projects. Additionally you can add more plugins and adjust predefined plugins or even remove them if needed.
|
||||||
|
|
||||||
We use the [plugins-manager](../plugins-manager/overview.md) for it.
|
We use the [plugins-manager](./plugins-manager.md) for it.
|
||||||
|
|
||||||
### Customizing the babel config
|
### Customizing the babel config
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@ SPA and MPA plugins:
|
|||||||
- [polyfills-loader](https://modern-web.dev/docs/building/rollup-plugin-polyfills-loader/)
|
- [polyfills-loader](https://modern-web.dev/docs/building/rollup-plugin-polyfills-loader/)
|
||||||
- [workbox](https://www.npmjs.com/package/rollup-plugin-workbox)
|
- [workbox](https://www.npmjs.com/package/rollup-plugin-workbox)
|
||||||
|
|
||||||
You can customize options for these plugins by using [adjustPluginOptions](../plugins-manager/overview.md#adjusting-plugin-options).
|
You can customize options for these plugins by using [adjustPluginOptions](./plugins-manager.md#adjusting-plugin-options).
|
||||||
|
|
||||||
```js
|
```js
|
||||||
import { createSpaConfig } from '@rocket/building-rollup';
|
import { createSpaConfig } from '@rocket/building-rollup';
|
||||||
|
|||||||
24
package.json
24
package.json
@@ -21,6 +21,7 @@
|
|||||||
"lint": "run-p lint:*",
|
"lint": "run-p lint:*",
|
||||||
"lint:eslint": "eslint --ext .ts,.js,.mjs,.cjs .",
|
"lint:eslint": "eslint --ext .ts,.js,.mjs,.cjs .",
|
||||||
"lint:prettier": "node node_modules/prettier/bin-prettier.js \"**/*.{ts,js,mjs,cjs,md}\" --check --ignore-path .eslintignore",
|
"lint:prettier": "node node_modules/prettier/bin-prettier.js \"**/*.{ts,js,mjs,cjs,md}\" --check --ignore-path .eslintignore",
|
||||||
|
"lint:rocket": "rocket lint",
|
||||||
"lint:types": "npm run types",
|
"lint:types": "npm run types",
|
||||||
"lint:versions": "node scripts/lint-versions.js",
|
"lint:versions": "node scripts/lint-versions.js",
|
||||||
"postinstall": "npm run setup",
|
"postinstall": "npm run setup",
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
"rocket:build": "node packages/cli/src/cli.js build",
|
"rocket:build": "node packages/cli/src/cli.js build",
|
||||||
"search": "node packages/cli/src/cli.js search",
|
"search": "node packages/cli/src/cli.js search",
|
||||||
"setup": "npm run setup:ts-configs",
|
"setup": "npm run setup:ts-configs",
|
||||||
|
"setup:patches": "npx patch-package",
|
||||||
"setup:ts-configs": "node scripts/generate-ts-configs.mjs",
|
"setup:ts-configs": "node scripts/generate-ts-configs.mjs",
|
||||||
"start": "node packages/cli/src/cli.js start",
|
"start": "node packages/cli/src/cli.js start",
|
||||||
"test": "yarn test:node && yarn test:web",
|
"test": "yarn test:node && yarn test:web",
|
||||||
@@ -50,22 +52,22 @@
|
|||||||
"@types/chai": "^4.2.14",
|
"@types/chai": "^4.2.14",
|
||||||
"@types/fs-extra": "^9.0.6",
|
"@types/fs-extra": "^9.0.6",
|
||||||
"@types/mocha": "^8.2.0",
|
"@types/mocha": "^8.2.0",
|
||||||
"@types/node": "^14.14.16",
|
"@types/node": "^14.14.20",
|
||||||
"@types/sinon": "^9.0.10",
|
"@types/sinon": "^9.0.10",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.11.1",
|
"@typescript-eslint/eslint-plugin": "^4.13.0",
|
||||||
"@typescript-eslint/parser": "^4.11.1",
|
"@typescript-eslint/parser": "^4.13.0",
|
||||||
"@web/test-runner": "^0.11.5",
|
"@web/test-runner": "^0.12.2",
|
||||||
"@web/test-runner-commands": "^0.3.0",
|
"@web/test-runner-commands": "^0.4.0",
|
||||||
"@web/test-runner-playwright": "^0.7.0",
|
"@web/test-runner-playwright": "^0.8.0",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"concurrently": "^5.3.0",
|
"concurrently": "^5.3.0",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
"deepmerge": "^4.2.2",
|
"deepmerge": "^4.2.2",
|
||||||
"esbuild": "^0.8.26",
|
"esbuild": "^0.8.31",
|
||||||
"eslint": "^7.16.0",
|
"eslint": "^7.17.0",
|
||||||
"eslint-config-prettier": "^7.1.0",
|
"eslint-config-prettier": "^7.1.0",
|
||||||
"hanbi": "^0.4.1",
|
"hanbi": "^0.4.1",
|
||||||
"husky": "^4.3.6",
|
"husky": "^4.3.7",
|
||||||
"lint-staged": "^10.5.3",
|
"lint-staged": "^10.5.3",
|
||||||
"mocha": "^8.2.1",
|
"mocha": "^8.2.1",
|
||||||
"node-fetch": "^2.6.1",
|
"node-fetch": "^2.6.1",
|
||||||
@@ -76,9 +78,9 @@
|
|||||||
"puppeteer": "^5.5.0",
|
"puppeteer": "^5.5.0",
|
||||||
"remark-emoji": "^2.1.0",
|
"remark-emoji": "^2.1.0",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rollup": "^2.35.1",
|
"rollup": "^2.36.1",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"sinon": "^9.2.2",
|
"sinon": "^9.2.3",
|
||||||
"ts-node": "^9.1.1",
|
"ts-node": "^9.1.1",
|
||||||
"typescript": "^4.1.3"
|
"typescript": "^4.1.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @rocket/building-rollup
|
# @rocket/building-rollup
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 897892d: bump dependencies
|
||||||
|
|
||||||
## 0.1.1
|
## 0.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rocket/building-rollup",
|
"name": "@rocket/building-rollup",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -57,7 +57,7 @@
|
|||||||
"@web/rollup-plugin-html": "^1.4.0",
|
"@web/rollup-plugin-html": "^1.4.0",
|
||||||
"@web/rollup-plugin-import-meta-assets": "^1.0.4",
|
"@web/rollup-plugin-import-meta-assets": "^1.0.4",
|
||||||
"@web/rollup-plugin-polyfills-loader": "^1.0.3",
|
"@web/rollup-plugin-polyfills-loader": "^1.0.3",
|
||||||
"browserslist": "^4.16.0",
|
"browserslist": "^4.16.1",
|
||||||
"rollup-plugin-terser": "^7.0.2",
|
"rollup-plugin-terser": "^7.0.2",
|
||||||
"rollup-plugin-workbox": "^6.1.0"
|
"rollup-plugin-workbox": "^6.1.0"
|
||||||
}
|
}
|
||||||
|
|||||||
6
packages/check-html-links/CHANGELOG.md
Normal file
6
packages/check-html-links/CHANGELOG.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
# check-html-links
|
||||||
|
|
||||||
|
## 0.1.0
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- cd22231: Initial release
|
||||||
28
packages/check-html-links/README.md
Normal file
28
packages/check-html-links/README.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Check Html Links
|
||||||
|
|
||||||
|
A fast checker for broken links/references in html.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```
|
||||||
|
npm i -D check-html-links
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
npx check-html-links _site
|
||||||
|
```
|
||||||
|
|
||||||
|
For docs please see our homepage [https://rocket.modern-web.dev/docs/tools/check-html-links/](https://rocket.modern-web.dev/docs/tools/check-html-links/).
|
||||||
|
|
||||||
|
## Comparision
|
||||||
|
|
||||||
|
Checking the output of [11ty-website](https://github.com/11ty/11ty-website) with 13 missing reference targets (used by 516 links) while checking 501 files. (on January 17, 2021)
|
||||||
|
|
||||||
|
| Tool | Lines printed | Times | Lang | Dependency Tree |
|
||||||
|
| ---------------- | ------------- | ------ | ---- | --------------- |
|
||||||
|
| check-html-links | 38 | ~2.5s | node | 19 |
|
||||||
|
| link-checker | 3000+ | ~11s | node | 106 |
|
||||||
|
| hyperlink | 68 | 4m 20s | node | 481 |
|
||||||
|
| htmltest | 1000+ | ~0.7s | GO | - |
|
||||||
2
packages/check-html-links/index.js
Normal file
2
packages/check-html-links/index.js
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export { validateFolder } from './src/validateFolder.js';
|
||||||
|
export { formatErrors } from './src/formatErrors.js';
|
||||||
43
packages/check-html-links/package.json
Normal file
43
packages/check-html-links/package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "check-html-links",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"description": "A fast low dependency checker of html links/references",
|
||||||
|
"license": "MIT",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/modernweb-dev/rocket.git",
|
||||||
|
"directory": "packages/check-html-links"
|
||||||
|
},
|
||||||
|
"author": "Modern Web <hello@modern-web.dev> (https://modern-web.dev/)",
|
||||||
|
"homepage": "https://rocket.modern-web.dev/docs/tools/check-html-links/",
|
||||||
|
"bin": {
|
||||||
|
"check-html-links": "src/cli.js"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": "./index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "mocha --timeout 5000 test-node/**/*.test.{js,cjs} test-node/*.test.{js,cjs}",
|
||||||
|
"test:watch": "onchange 'src/**/*.{js,cjs}' 'test-node/**/*.{js,cjs}' -- npm test",
|
||||||
|
"types:copy": "copyfiles \"./types/**/*.d.ts\" dist-types/"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"*.js",
|
||||||
|
"dist",
|
||||||
|
"dist-types",
|
||||||
|
"src"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"chalk": "^4.0.0",
|
||||||
|
"glob": "^7.0.0",
|
||||||
|
"sax-wasm": "^2.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/glob": "^7.0.0"
|
||||||
|
},
|
||||||
|
"types": "dist-types/index.d.ts"
|
||||||
|
}
|
||||||
45
packages/check-html-links/src/cli.js
Executable file
45
packages/check-html-links/src/cli.js
Executable file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
import path from 'path';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { validateFiles } from './validateFolder.js';
|
||||||
|
import { formatErrors } from './formatErrors.js';
|
||||||
|
import { listFiles } from './listFiles.js';
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const userRootDir = process.argv[2];
|
||||||
|
const rootDir = userRootDir ? path.resolve(userRootDir) : process.cwd();
|
||||||
|
const performanceStart = process.hrtime();
|
||||||
|
|
||||||
|
console.log('👀 Checking if all internal links work...');
|
||||||
|
const files = await listFiles('**/*.html', rootDir);
|
||||||
|
const errors = await validateFiles(files, rootDir);
|
||||||
|
const performance = process.hrtime(performanceStart);
|
||||||
|
if (errors.length > 0) {
|
||||||
|
let referenceCount = 0;
|
||||||
|
for (const error of errors) {
|
||||||
|
referenceCount += error.usage.length;
|
||||||
|
}
|
||||||
|
const output = [
|
||||||
|
`❌ Found ${chalk.red.bold(
|
||||||
|
errors.length.toString(),
|
||||||
|
)} missing reference targets (used by ${referenceCount} links) while checking ${
|
||||||
|
files.length
|
||||||
|
} files:`,
|
||||||
|
...formatErrors(errors)
|
||||||
|
.split('\n')
|
||||||
|
.map(line => ` ${line}`),
|
||||||
|
`Checking links duration: ${performance[0]}s ${performance[1] / 1000000}ms`,
|
||||||
|
];
|
||||||
|
console.error(output.join('\n'));
|
||||||
|
process.exit(1);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`✅ All internal links are valid. (executed in %ds %dms)`,
|
||||||
|
performance[0],
|
||||||
|
performance[1] / 1000000,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
46
packages/check-html-links/src/formatErrors.js
Normal file
46
packages/check-html-links/src/formatErrors.js
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
|
||||||
|
/** @typedef {import('../types/main').Error} Error */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Error[]} errors
|
||||||
|
* @param {*} relativeFrom
|
||||||
|
*/
|
||||||
|
export function formatErrors(errors, relativeFrom = process.cwd()) {
|
||||||
|
let output = [];
|
||||||
|
let number = 0;
|
||||||
|
for (const error of errors) {
|
||||||
|
number += 1;
|
||||||
|
const filePath = path.relative(relativeFrom, error.filePath);
|
||||||
|
if (error.onlyAnchorMissing === true) {
|
||||||
|
output.push(
|
||||||
|
`${number}. missing ${chalk.red.bold(
|
||||||
|
`id="${error.usage[0].anchor}"`,
|
||||||
|
)} in ${chalk.cyanBright(filePath)}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const firstAttribute = error.usage[0].attribute;
|
||||||
|
const title =
|
||||||
|
firstAttribute === 'src' || firstAttribute === 'srcset' ? 'file' : 'reference target';
|
||||||
|
|
||||||
|
output.push(`${number}. missing ${title} ${chalk.red.bold(filePath)}`);
|
||||||
|
}
|
||||||
|
const usageLength = error.usage.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < 3 && i < usageLength; i += 1) {
|
||||||
|
const usage = error.usage[i];
|
||||||
|
const usagePath = path.relative(relativeFrom, usage.file);
|
||||||
|
const clickAbleLink = chalk.cyanBright(`${usagePath}:${usage.line + 1}:${usage.character}`);
|
||||||
|
const attributeStart = chalk.gray(`${usage.attribute}="`);
|
||||||
|
const attributeEnd = chalk.gray('"');
|
||||||
|
output.push(` from ${clickAbleLink} via ${attributeStart}${usage.value}${attributeEnd}`);
|
||||||
|
}
|
||||||
|
if (usageLength > 3) {
|
||||||
|
const more = chalk.red((usageLength - 3).toString());
|
||||||
|
output.push(` ... ${more} more references to this target`);
|
||||||
|
}
|
||||||
|
output.push('');
|
||||||
|
}
|
||||||
|
return output.join('\n');
|
||||||
|
}
|
||||||
35
packages/check-html-links/src/listFiles.js
Normal file
35
packages/check-html-links/src/listFiles.js
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
import glob from 'glob';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lists all files using the specified glob, starting from the given root directory.
|
||||||
|
*
|
||||||
|
* Will return all matching file paths fully resolved.
|
||||||
|
*
|
||||||
|
* @param {string} fromGlob
|
||||||
|
* @param {string} rootDir
|
||||||
|
*/
|
||||||
|
export function listFiles(fromGlob, rootDir) {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
glob(
|
||||||
|
fromGlob,
|
||||||
|
{ cwd: rootDir },
|
||||||
|
/**
|
||||||
|
* @param {Error | null} er
|
||||||
|
* @param {string[]} files
|
||||||
|
*/
|
||||||
|
(er, files) => {
|
||||||
|
// remember, each filepath returned is relative to rootDir
|
||||||
|
resolve(
|
||||||
|
files
|
||||||
|
// fully resolve the filename relative to rootDir
|
||||||
|
.map(filePath => path.resolve(rootDir, filePath))
|
||||||
|
// filter out directories
|
||||||
|
.filter(filePath => !fs.lstatSync(filePath).isDirectory()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
287
packages/check-html-links/src/validateFolder.js
Normal file
287
packages/check-html-links/src/validateFolder.js
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
|
import fs from 'fs';
|
||||||
|
import saxWasm from 'sax-wasm';
|
||||||
|
import { createRequire } from 'module';
|
||||||
|
|
||||||
|
import { listFiles } from './listFiles.js';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
/** @typedef {import('../types/main').Link} Link */
|
||||||
|
/** @typedef {import('../types/main').LocalFile} LocalFile */
|
||||||
|
/** @typedef {import('../types/main').Usage} Usage */
|
||||||
|
/** @typedef {import('../types/main').Error} Error */
|
||||||
|
/** @typedef {import('sax-wasm').Attribute} Attribute */
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url);
|
||||||
|
const { SaxEventType, SAXParser } = saxWasm;
|
||||||
|
|
||||||
|
const saxPath = require.resolve('sax-wasm/lib/sax-wasm.wasm');
|
||||||
|
const saxWasmBuffer = fs.readFileSync(saxPath);
|
||||||
|
const parserReferences = new SAXParser(SaxEventType.Attribute);
|
||||||
|
const parserIds = new SAXParser(SaxEventType.Attribute /*, { highWaterMark: 256 * 1024 } */);
|
||||||
|
|
||||||
|
/** @type {Error[]} */
|
||||||
|
let checkLocalFiles = [];
|
||||||
|
|
||||||
|
/** @type {Error[]} */
|
||||||
|
let errors = [];
|
||||||
|
|
||||||
|
/** @type {Map<string, string[]>} */
|
||||||
|
let idCache = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} htmlFilePath
|
||||||
|
*/
|
||||||
|
function extractReferences(htmlFilePath) {
|
||||||
|
/** @type {Link[]} */
|
||||||
|
const links = [];
|
||||||
|
/** @type {string[]} */
|
||||||
|
const ids = [];
|
||||||
|
parserReferences.eventHandler = (ev, _data) => {
|
||||||
|
if (ev === SaxEventType.Attribute) {
|
||||||
|
const data = /** @type {Attribute} */ (/** @type {any} */ (_data));
|
||||||
|
const attributeName = data.name.toString();
|
||||||
|
const value = data.value.toString();
|
||||||
|
const entry = {
|
||||||
|
attribute: attributeName,
|
||||||
|
value,
|
||||||
|
htmlFilePath,
|
||||||
|
...data.value.start,
|
||||||
|
};
|
||||||
|
if (attributeName === 'href' || attributeName === 'src') {
|
||||||
|
links.push(entry);
|
||||||
|
}
|
||||||
|
if (attributeName === 'srcset') {
|
||||||
|
if (value.includes(',')) {
|
||||||
|
const srcsets = value.split(',').map(el => el.trim());
|
||||||
|
for (const srcset of srcsets) {
|
||||||
|
if (srcset.includes(' ')) {
|
||||||
|
const srcsetParts = srcset.split(' ');
|
||||||
|
links.push({ ...entry, value: srcsetParts[0] });
|
||||||
|
} else {
|
||||||
|
links.push({ ...entry, value: srcset });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (value.includes(' ')) {
|
||||||
|
const srcsetParts = value.split(' ');
|
||||||
|
links.push({ ...entry, value: srcsetParts[0] });
|
||||||
|
} else {
|
||||||
|
links.push(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attributeName === 'id') {
|
||||||
|
ids.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const readable = fs.createReadStream(htmlFilePath);
|
||||||
|
readable.on('data', chunk => {
|
||||||
|
// @ts-expect-error
|
||||||
|
parserReferences.write(chunk);
|
||||||
|
});
|
||||||
|
readable.on('end', () => {
|
||||||
|
parserReferences.end();
|
||||||
|
idCache.set(htmlFilePath, ids);
|
||||||
|
resolve({ links });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {string} id
|
||||||
|
*/
|
||||||
|
function idExists(filePath, id) {
|
||||||
|
if (idCache.has(filePath)) {
|
||||||
|
const cachedIds = idCache.get(filePath);
|
||||||
|
// return cachedIds.includes(id);
|
||||||
|
return new Promise(resolve => resolve(cachedIds?.includes(id)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {string[]} */
|
||||||
|
const ids = [];
|
||||||
|
parserIds.eventHandler = (ev, _data) => {
|
||||||
|
const data = /** @type {Attribute} */ (/** @type {any} */ (_data));
|
||||||
|
if (ev === SaxEventType.Attribute) {
|
||||||
|
if (data.name.toString() === 'id') {
|
||||||
|
ids.push(data.value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const readable = fs.createReadStream(filePath);
|
||||||
|
readable.on('data', chunk => {
|
||||||
|
// @ts-expect-error
|
||||||
|
parserIds.write(chunk);
|
||||||
|
});
|
||||||
|
readable.on('end', () => {
|
||||||
|
parserIds.end();
|
||||||
|
idCache.set(filePath, ids);
|
||||||
|
resolve(ids.includes(id));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} filePath
|
||||||
|
* @param {string} anchor
|
||||||
|
* @param {Usage} usageObj
|
||||||
|
*/
|
||||||
|
function addLocalFile(filePath, anchor, usageObj) {
|
||||||
|
const foundIndex = checkLocalFiles.findIndex(item => {
|
||||||
|
return item.filePath === filePath;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (foundIndex === -1) {
|
||||||
|
checkLocalFiles.push({
|
||||||
|
filePath,
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [usageObj],
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
checkLocalFiles[foundIndex].usage.push(usageObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} inValue
|
||||||
|
*/
|
||||||
|
function getValueAndAnchor(inValue) {
|
||||||
|
let value = inValue.replace(/&#/g, '--__check-html-links__--');
|
||||||
|
let anchor = '';
|
||||||
|
|
||||||
|
if (value.includes('#')) {
|
||||||
|
[value, anchor] = value.split('#');
|
||||||
|
}
|
||||||
|
if (value.includes('?')) {
|
||||||
|
value = value.split('?')[0];
|
||||||
|
}
|
||||||
|
if (anchor.includes(':~:')) {
|
||||||
|
anchor = anchor.split(':~:')[0];
|
||||||
|
}
|
||||||
|
if (value.includes(':~:')) {
|
||||||
|
value = value.split(':~:')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
value = value.replace(/--__check-html-links__--/g, '&#');
|
||||||
|
anchor = anchor.replace(/--__check-html-links__--/g, '&#');
|
||||||
|
value = value.trim();
|
||||||
|
anchor = anchor.trim();
|
||||||
|
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
anchor,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Link[]} links
|
||||||
|
* @param {object} options
|
||||||
|
* @param {string} options.htmlFilePath
|
||||||
|
* @param {string} options.rootDir
|
||||||
|
*/
|
||||||
|
async function resolveLinks(links, { htmlFilePath, rootDir }) {
|
||||||
|
for (const hrefObj of links) {
|
||||||
|
const { value, anchor } = getValueAndAnchor(hrefObj.value);
|
||||||
|
|
||||||
|
const usageObj = {
|
||||||
|
attribute: hrefObj.attribute,
|
||||||
|
value: hrefObj.value,
|
||||||
|
file: htmlFilePath,
|
||||||
|
line: hrefObj.line,
|
||||||
|
character: hrefObj.character,
|
||||||
|
anchor,
|
||||||
|
};
|
||||||
|
|
||||||
|
let valueFile = value.endsWith('/') ? path.join(value, 'index.html') : value;
|
||||||
|
|
||||||
|
if (value.includes('mailto:')) {
|
||||||
|
// ignore for now - could add a check to validate if the email address is valid
|
||||||
|
} else if (valueFile === '' && anchor !== '') {
|
||||||
|
addLocalFile(htmlFilePath, anchor, usageObj);
|
||||||
|
} else if (value.startsWith('//') || value.startsWith('http')) {
|
||||||
|
// TODO: handle external urls
|
||||||
|
// external url - we do not handle that (yet)
|
||||||
|
} else if (value.startsWith('/')) {
|
||||||
|
const filePath = path.join(rootDir, valueFile);
|
||||||
|
addLocalFile(filePath, anchor, usageObj);
|
||||||
|
} else if (value === '' && anchor === '') {
|
||||||
|
// no need to check it
|
||||||
|
} else {
|
||||||
|
const filePath = path.join(path.dirname(htmlFilePath), valueFile);
|
||||||
|
addLocalFile(filePath, anchor, usageObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { checkLocalFiles: [...checkLocalFiles] };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {Error[]} checkLocalFiles
|
||||||
|
*/
|
||||||
|
async function validateLocalFiles(checkLocalFiles) {
|
||||||
|
for (const localFileObj of checkLocalFiles) {
|
||||||
|
if (
|
||||||
|
!fs.existsSync(localFileObj.filePath) ||
|
||||||
|
fs.lstatSync(localFileObj.filePath).isDirectory()
|
||||||
|
) {
|
||||||
|
errors.push(localFileObj);
|
||||||
|
} else {
|
||||||
|
for (let i = 0; i < localFileObj.usage.length; i += 1) {
|
||||||
|
const usage = localFileObj.usage[i];
|
||||||
|
if (usage.anchor === '') {
|
||||||
|
localFileObj.usage.splice(i, 1);
|
||||||
|
i -= 1;
|
||||||
|
} else {
|
||||||
|
const isValidAnchor = await idExists(localFileObj.filePath, usage.anchor);
|
||||||
|
if (isValidAnchor) {
|
||||||
|
localFileObj.usage.splice(i, 1);
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (localFileObj.usage.length > 0) {
|
||||||
|
if (localFileObj.usage.length === 1 && localFileObj.usage[0].anchor) {
|
||||||
|
localFileObj.onlyAnchorMissing = true;
|
||||||
|
}
|
||||||
|
errors.push(localFileObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string[]} files
|
||||||
|
* @param {string} rootDir
|
||||||
|
*/
|
||||||
|
export async function validateFiles(files, rootDir) {
|
||||||
|
await parserReferences.prepareWasm(saxWasmBuffer);
|
||||||
|
await parserIds.prepareWasm(saxWasmBuffer);
|
||||||
|
|
||||||
|
errors = [];
|
||||||
|
checkLocalFiles = [];
|
||||||
|
idCache = new Map();
|
||||||
|
for (const htmlFilePath of files) {
|
||||||
|
const { links } = await extractReferences(htmlFilePath);
|
||||||
|
await resolveLinks(links, { htmlFilePath, rootDir });
|
||||||
|
}
|
||||||
|
await validateLocalFiles(checkLocalFiles);
|
||||||
|
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} inRootDir
|
||||||
|
*/
|
||||||
|
export async function validateFolder(inRootDir) {
|
||||||
|
const rootDir = path.resolve(inRootDir);
|
||||||
|
const files = await listFiles('**/*.html', rootDir);
|
||||||
|
const errors = await validateFiles(files, rootDir);
|
||||||
|
return errors;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<img src="/missing.png" alt="" />
|
||||||
|
<img src="./missing.png" alt="" />
|
||||||
|
<img src="/absolute/missing.png" alt="" />
|
||||||
|
<img src="./relative/missing.png" alt="" />
|
||||||
|
|
||||||
|
<!-- valid -->
|
||||||
|
<img src="/empty.png" alt="" />
|
||||||
|
<img src="./empty.png" alt="" />
|
||||||
|
<img src="./empty.png " alt="" />
|
||||||
|
<img src=" ./empty.png " alt="" />
|
||||||
|
<img src="./empty.png?data=in&query=params" alt="" />
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<a href="./page.html"></a>
|
||||||
|
<a href="./page.html#first-headline"></a>
|
||||||
|
<a href="./page.html#missing-headline"></a>
|
||||||
|
<a href="./missing-page.html#missing-headline"></a>
|
||||||
|
<a href="#local"></a>
|
||||||
|
<a href="#local-missing"></a>
|
||||||
|
|
||||||
|
<p id="local"></p>
|
||||||
|
|
||||||
|
<a href="#audit-your-angular-app's-accessibility-with-codelyzer">
|
||||||
|
Audit your Angular app's accessibility with codelyzer
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<h1 id="audit-your-angular-app's-accessibility-with-codelyzer">Audit your Angular app's accessibility with codelyzer</h1>
|
||||||
|
|
||||||
|
<a href="#local:~:text=put%20your%20labels%20above%20your%20inputs">Sign-in form best practices</a>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<h1 id="first-headline">First Headline</h1>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<a href="./page/index.html"></a>
|
||||||
|
<a href="./page/#my-anchor"></a>
|
||||||
|
<a href="./missing-folder/"></a>
|
||||||
|
<a href="./missing-folder/#my-anchor"></a>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<p id="my-anchor"></p>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<a href="/absolute/index.html"></a>
|
||||||
|
<a href="./relative/index.html"></a>
|
||||||
|
|
||||||
|
<!-- valid -->
|
||||||
|
<a href="./page.html"></a>
|
||||||
|
<a href=" ./page.html "></a>
|
||||||
|
<a href=" /page.html "></a>
|
||||||
|
<a href="//domain.com/something/"></a>
|
||||||
|
<a href="http://domain.com/something/"></a>
|
||||||
|
<a href="https://domain.com/something/"></a>
|
||||||
|
<a href=""></a>
|
||||||
|
<a href=":~:text=put%20your%20labels%20above%20your%20inputs">Sign-in form best practices</a>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
<a href="/absolute-page/index.html">absolute page</a>
|
||||||
|
<a href="./relative-page/index.html">relative page</a>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<a href="/foo"></a>
|
||||||
|
<a href="./foo"></a>
|
||||||
|
<a href="./foo#my-anchor"></a>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<picture>
|
||||||
|
<source srcset="/images/empty-300.png 300w, /images/empty-600.png?data=in&query=params 600w" type="image/jpeg" sizes="(min-width: 62.5em) 25vw, (min-width: 30.625em) 50vw, 100vw">
|
||||||
|
<img src="/images/empty.png" alt="Empty" width="300" height="225">
|
||||||
|
</picture>
|
||||||
|
|
||||||
|
<picture>
|
||||||
|
<source srcset="/images/missing-300.png 300w, /images/missing-600.png 600w" type="image/jpeg" sizes="(min-width: 62.5em) 25vw, (min-width: 30.625em) 50vw, 100vw">
|
||||||
|
<img src="/images/missing.png" alt="Empty" width="300" height="225">
|
||||||
|
</picture>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<a href="mailto:foo@bar.com"></a>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<a href="../price/"></a>
|
||||||
|
|
||||||
|
<img src="./images/team.png" />
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/aboot"></a>
|
||||||
|
</footer>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<a href="/price/#my-teams"></a>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/aboot"></a>
|
||||||
|
</footer>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<a href="./prce"></a>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/aboot"></a>
|
||||||
|
</footer>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<footer>
|
||||||
|
<a href="/aboot"></a>
|
||||||
|
</footer>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<h1 id="overview"></h1>
|
||||||
|
|
||||||
|
<h2 id="teams"></h2>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<a href="/aboot"></a>
|
||||||
|
</footer>
|
||||||
38
packages/check-html-links/test-node/formatErrors.test.js
Normal file
38
packages/check-html-links/test-node/formatErrors.test.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import chai from 'chai';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { execute } from './test-helpers.js';
|
||||||
|
import { formatErrors } from 'check-html-links';
|
||||||
|
|
||||||
|
const { expect } = chai;
|
||||||
|
|
||||||
|
async function executeAndFormat(inPath) {
|
||||||
|
const { errors, cleanup } = await execute(inPath);
|
||||||
|
return formatErrors(cleanup(errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('formatErrors', () => {
|
||||||
|
before(() => {
|
||||||
|
// ignore colors in tests as most CIs won't support it
|
||||||
|
chalk.level = 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prints a nice summery', async () => {
|
||||||
|
const result = await executeAndFormat('fixtures/test-case');
|
||||||
|
expect(result.trim().split('\n')).to.deep.equal([
|
||||||
|
'1. missing id="my-teams" in fixtures/test-case/price/index.html',
|
||||||
|
' from fixtures/test-case/history/index.html:1:9 via href="/price/#my-teams"',
|
||||||
|
'',
|
||||||
|
'2. missing file fixtures/test-case/about/images/team.png',
|
||||||
|
' from fixtures/test-case/about/index.html:3:10 via src="./images/team.png"',
|
||||||
|
'',
|
||||||
|
'3. missing reference target fixtures/test-case/aboot',
|
||||||
|
' from fixtures/test-case/about/index.html:6:11 via href="/aboot"',
|
||||||
|
' from fixtures/test-case/history/index.html:4:11 via href="/aboot"',
|
||||||
|
' from fixtures/test-case/index.html:4:11 via href="/aboot"',
|
||||||
|
' ... 2 more references to this target',
|
||||||
|
'',
|
||||||
|
'4. missing reference target fixtures/test-case/prce',
|
||||||
|
' from fixtures/test-case/index.html:1:9 via href="./prce"',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
28
packages/check-html-links/test-node/test-helpers.js
Normal file
28
packages/check-html-links/test-node/test-helpers.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
import { validateFolder } from 'check-html-links';
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
export async function execute(inPath) {
|
||||||
|
const testDir = path.join(__dirname, inPath.split('/').join(path.sep));
|
||||||
|
const errors = await validateFolder(testDir);
|
||||||
|
return {
|
||||||
|
cleanup: items => {
|
||||||
|
const newItems = [];
|
||||||
|
for (const item of items) {
|
||||||
|
newItems.push({
|
||||||
|
...item,
|
||||||
|
filePath: path.relative(__dirname, item.filePath),
|
||||||
|
usage: item.usage.map(usageObj => ({
|
||||||
|
...usageObj,
|
||||||
|
file: path.relative(__dirname, usageObj.file),
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return newItems;
|
||||||
|
},
|
||||||
|
errors,
|
||||||
|
};
|
||||||
|
}
|
||||||
289
packages/check-html-links/test-node/validateFolder.test.js
Normal file
289
packages/check-html-links/test-node/validateFolder.test.js
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
import chai from 'chai';
|
||||||
|
import { execute } from './test-helpers.js';
|
||||||
|
|
||||||
|
const { expect } = chai;
|
||||||
|
|
||||||
|
describe('validateFolder', () => {
|
||||||
|
it('validates internal links', async () => {
|
||||||
|
const { errors, cleanup } = await execute('fixtures/internal-link');
|
||||||
|
expect(cleanup(errors)).to.deep.equal([
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-link/absolute/index.html',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'href',
|
||||||
|
value: '/absolute/index.html',
|
||||||
|
file: 'fixtures/internal-link/index.html',
|
||||||
|
line: 0,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-link/relative/index.html',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'href',
|
||||||
|
value: './relative/index.html',
|
||||||
|
file: 'fixtures/internal-link/index.html',
|
||||||
|
line: 1,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-link/absolute-page/index.html',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'href',
|
||||||
|
value: '/absolute-page/index.html',
|
||||||
|
file: 'fixtures/internal-link/page.html',
|
||||||
|
line: 0,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-link/relative-page/index.html',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'href',
|
||||||
|
value: './relative-page/index.html',
|
||||||
|
file: 'fixtures/internal-link/page.html',
|
||||||
|
line: 1,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('groups multiple usage of the same missing file', async () => {
|
||||||
|
const { errors, cleanup } = await execute('fixtures/internal-links-to-same-file');
|
||||||
|
expect(cleanup(errors)).to.deep.equal([
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-links-to-same-file/foo',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
attribute: 'href',
|
||||||
|
value: '/foo',
|
||||||
|
anchor: '',
|
||||||
|
file: 'fixtures/internal-links-to-same-file/index.html',
|
||||||
|
line: 0,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute: 'href',
|
||||||
|
value: './foo',
|
||||||
|
anchor: '',
|
||||||
|
file: 'fixtures/internal-links-to-same-file/index.html',
|
||||||
|
line: 1,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
attribute: 'href',
|
||||||
|
value: './foo#my-anchor',
|
||||||
|
anchor: 'my-anchor',
|
||||||
|
file: 'fixtures/internal-links-to-same-file/index.html',
|
||||||
|
line: 2,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('validates that ids of anchors exist', async () => {
|
||||||
|
const { errors, cleanup } = await execute('fixtures/internal-link-anchor');
|
||||||
|
expect(cleanup(errors)).to.deep.equal([
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-link-anchor/page.html',
|
||||||
|
onlyAnchorMissing: true,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
attribute: 'href',
|
||||||
|
value: './page.html#missing-headline',
|
||||||
|
anchor: 'missing-headline',
|
||||||
|
file: 'fixtures/internal-link-anchor/index.html',
|
||||||
|
line: 2,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-link-anchor/missing-page.html',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
attribute: 'href',
|
||||||
|
value: './missing-page.html#missing-headline',
|
||||||
|
anchor: 'missing-headline',
|
||||||
|
file: 'fixtures/internal-link-anchor/index.html',
|
||||||
|
line: 3,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-link-anchor/index.html',
|
||||||
|
onlyAnchorMissing: true,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
attribute: 'href',
|
||||||
|
value: '#local-missing',
|
||||||
|
anchor: 'local-missing',
|
||||||
|
file: 'fixtures/internal-link-anchor/index.html',
|
||||||
|
line: 5,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can handle urls that end with a /', async () => {
|
||||||
|
const { errors, cleanup } = await execute('fixtures/internal-link-folder');
|
||||||
|
expect(cleanup(errors)).to.deep.equal([
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-link-folder/missing-folder/index.html',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'href',
|
||||||
|
value: './missing-folder/',
|
||||||
|
file: 'fixtures/internal-link-folder/index.html',
|
||||||
|
line: 2,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: 'my-anchor',
|
||||||
|
attribute: 'href',
|
||||||
|
value: './missing-folder/#my-anchor',
|
||||||
|
file: 'fixtures/internal-link-folder/index.html',
|
||||||
|
line: 3,
|
||||||
|
character: 9,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('ignores mailto links', async () => {
|
||||||
|
const { errors, cleanup } = await execute('fixtures/mailto');
|
||||||
|
expect(cleanup(errors)).to.deep.equal([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can handle img src', async () => {
|
||||||
|
const { errors, cleanup } = await execute('fixtures/internal-images');
|
||||||
|
expect(cleanup(errors)).to.deep.equal([
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-images/missing.png',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'src',
|
||||||
|
value: '/missing.png',
|
||||||
|
file: 'fixtures/internal-images/index.html',
|
||||||
|
line: 0,
|
||||||
|
character: 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'src',
|
||||||
|
character: 10,
|
||||||
|
file: 'fixtures/internal-images/index.html',
|
||||||
|
line: 1,
|
||||||
|
value: './missing.png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-images/absolute/missing.png',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'src',
|
||||||
|
character: 10,
|
||||||
|
file: 'fixtures/internal-images/index.html',
|
||||||
|
line: 2,
|
||||||
|
value: '/absolute/missing.png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-images/relative/missing.png',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'src',
|
||||||
|
character: 10,
|
||||||
|
file: 'fixtures/internal-images/index.html',
|
||||||
|
line: 3,
|
||||||
|
value: './relative/missing.png',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can handle picture source srcset', async () => {
|
||||||
|
const { errors, cleanup } = await execute('fixtures/internal-pictures');
|
||||||
|
expect(cleanup(errors)).to.deep.equal([
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-pictures/images/missing-300.png',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'srcset',
|
||||||
|
value: '/images/missing-300.png',
|
||||||
|
file: 'fixtures/internal-pictures/index.html',
|
||||||
|
line: 6,
|
||||||
|
character: 18,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-pictures/images/missing-600.png',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'srcset',
|
||||||
|
value: '/images/missing-600.png',
|
||||||
|
file: 'fixtures/internal-pictures/index.html',
|
||||||
|
line: 6,
|
||||||
|
character: 18,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
filePath: 'fixtures/internal-pictures/images/missing.png',
|
||||||
|
onlyAnchorMissing: false,
|
||||||
|
usage: [
|
||||||
|
{
|
||||||
|
anchor: '',
|
||||||
|
attribute: 'src',
|
||||||
|
value: '/images/missing.png',
|
||||||
|
file: 'fixtures/internal-pictures/index.html',
|
||||||
|
line: 7,
|
||||||
|
character: 12,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
24
packages/check-html-links/tsconfig.json
Normal file
24
packages/check-html-links/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// Don't edit this file directly. It is generated by /scripts/update-package-configs.ts
|
||||||
|
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.node-base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ESNext",
|
||||||
|
"outDir": "./dist-types",
|
||||||
|
"rootDir": ".",
|
||||||
|
"composite": true,
|
||||||
|
"allowJs": true,
|
||||||
|
"checkJs": true,
|
||||||
|
"emitDeclarationOnly": true
|
||||||
|
},
|
||||||
|
"references": [],
|
||||||
|
"include": [
|
||||||
|
"src",
|
||||||
|
"*.js",
|
||||||
|
"types"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"dist",
|
||||||
|
"dist-types"
|
||||||
|
]
|
||||||
|
}
|
||||||
27
packages/check-html-links/types/main.d.ts
vendored
Normal file
27
packages/check-html-links/types/main.d.ts
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
export interface Link {
|
||||||
|
value: string;
|
||||||
|
attribute: string;
|
||||||
|
htmlFilePath: string;
|
||||||
|
line: number;
|
||||||
|
character: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Usage {
|
||||||
|
attribute: string;
|
||||||
|
value: string;
|
||||||
|
anchor: string;
|
||||||
|
file: string;
|
||||||
|
line: number;
|
||||||
|
character: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LocalFile {
|
||||||
|
filePath: string;
|
||||||
|
usage: Usage[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Error {
|
||||||
|
filePath: string;
|
||||||
|
onlyAnchorMissing: boolean;
|
||||||
|
usage: Usage[];
|
||||||
|
}
|
||||||
@@ -1,5 +1,29 @@
|
|||||||
# @rocket/cli
|
# @rocket/cli
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- cd22231: Restructure and simplify Rocket Cli Plugin System
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- cd22231: Add a default Rocket Cli Plugin which checks all links on every save (during start) and after a production build
|
||||||
|
- Updated dependencies [cd22231]
|
||||||
|
- Updated dependencies [cd22231]
|
||||||
|
- @rocket/eleventy-plugin-mdjs-unified@0.3.0
|
||||||
|
- check-html-links@0.1.0
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 897892d: bump dependencies
|
||||||
|
- 295cfbd: Better support for windows paths
|
||||||
|
- Updated dependencies [897892d]
|
||||||
|
- @rocket/building-rollup@0.1.2
|
||||||
|
- @rocket/eleventy-rocket-nav@0.2.1
|
||||||
|
|
||||||
## 0.2.0
|
## 0.2.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rocket/cli",
|
"name": "@rocket/cli",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -51,17 +51,18 @@
|
|||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@11ty/eleventy": "^0.11.1",
|
"@11ty/eleventy": "^0.11.1",
|
||||||
"@11ty/eleventy-img": "^0.7.3",
|
"@11ty/eleventy-img": "^0.7.4",
|
||||||
"@rocket/building-rollup": "^0.1.1",
|
"@rocket/building-rollup": "^0.1.2",
|
||||||
"@rocket/core": "^0.1.1",
|
"@rocket/core": "^0.1.1",
|
||||||
"@rocket/eleventy-plugin-mdjs-unified": "^0.2.0",
|
"@rocket/eleventy-plugin-mdjs-unified": "^0.3.0",
|
||||||
"@rocket/eleventy-rocket-nav": "^0.2.0",
|
"@rocket/eleventy-rocket-nav": "^0.2.1",
|
||||||
"@rollup/plugin-babel": "^5.2.2",
|
"@rollup/plugin-babel": "^5.2.2",
|
||||||
"@rollup/plugin-node-resolve": "^11.0.1",
|
"@rollup/plugin-node-resolve": "^11.0.1",
|
||||||
"@web/config-loader": "^0.1.3",
|
"@web/config-loader": "^0.1.3",
|
||||||
"@web/dev-server": "^0.1.2",
|
"@web/dev-server": "^0.1.4",
|
||||||
"@web/dev-server-rollup": "^0.3.0",
|
"@web/dev-server-rollup": "^0.3.2",
|
||||||
"@web/rollup-plugin-copy": "^0.2.0",
|
"@web/rollup-plugin-copy": "^0.2.0",
|
||||||
|
"check-html-links": "^0.1.0",
|
||||||
"command-line-args": "^5.1.1",
|
"command-line-args": "^5.1.1",
|
||||||
"command-line-usage": "^6.1.1",
|
"command-line-usage": "^6.1.1",
|
||||||
"fs-extra": "^9.0.1",
|
"fs-extra": "^9.0.1",
|
||||||
|
|||||||
@@ -58,21 +58,28 @@ async function productionBuild(config) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class RocketBuild {
|
export class RocketBuild {
|
||||||
|
static pluginName = 'RocketBuild';
|
||||||
commands = ['build'];
|
commands = ['build'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {RocketCliOptions} config
|
||||||
|
*/
|
||||||
setupCommand(config) {
|
setupCommand(config) {
|
||||||
config.watch = false;
|
config.watch = false;
|
||||||
|
config.lintInputDir = config.outputDir;
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
async setup({ config }) {
|
async setup({ config, eleventy }) {
|
||||||
this.config = {
|
this.config = {
|
||||||
emptyOutputDir: true,
|
emptyOutputDir: true,
|
||||||
...config,
|
...config,
|
||||||
};
|
};
|
||||||
|
this.eleventy = eleventy;
|
||||||
}
|
}
|
||||||
|
|
||||||
async build() {
|
async buildCommand() {
|
||||||
|
await this.eleventy.write();
|
||||||
if (this.config.emptyOutputDir) {
|
if (this.config.emptyOutputDir) {
|
||||||
await fs.emptyDir(this.config.outputDir);
|
await fs.emptyDir(this.config.outputDir);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,23 +28,15 @@ export class RocketEleventy extends Eleventy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async write() {
|
async write() {
|
||||||
/** @type {function} */
|
|
||||||
let finishBuild;
|
|
||||||
this.__rocketCli.updateComplete = new Promise(resolve => {
|
|
||||||
finishBuild = resolve;
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.__rocketCli.mergePresets();
|
await this.__rocketCli.mergePresets();
|
||||||
|
|
||||||
await super.write();
|
await super.write();
|
||||||
await this.__rocketCli.update();
|
await this.__rocketCli.update();
|
||||||
// @ts-ignore
|
|
||||||
finishBuild();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RocketCli {
|
export class RocketCli {
|
||||||
updateComplete = new Promise(resolve => resolve(null));
|
/** @type {string[]} */
|
||||||
|
errors = [];
|
||||||
|
|
||||||
constructor({ argv } = { argv: undefined }) {
|
constructor({ argv } = { argv: undefined }) {
|
||||||
const mainDefinitions = [
|
const mainDefinitions = [
|
||||||
@@ -72,7 +64,10 @@ export class RocketCli {
|
|||||||
if (!this.eleventy) {
|
if (!this.eleventy) {
|
||||||
const { _inputDirCwdRelative, outputDevDir } = this.config;
|
const { _inputDirCwdRelative, outputDevDir } = this.config;
|
||||||
|
|
||||||
await fs.emptyDir(outputDevDir);
|
// We need to merge before we setup 11ty as the write phase is too late for _data
|
||||||
|
// TODO: find a way so we don't need to double merge
|
||||||
|
await this.mergePresets();
|
||||||
|
|
||||||
const elev = new RocketEleventy(_inputDirCwdRelative, outputDevDir, this);
|
const elev = new RocketEleventy(_inputDirCwdRelative, outputDevDir, this);
|
||||||
elev.isVerbose = false;
|
elev.isVerbose = false;
|
||||||
// 11ty always wants a relative path to cwd - why?
|
// 11ty always wants a relative path to cwd - why?
|
||||||
@@ -81,10 +76,6 @@ export class RocketCli {
|
|||||||
elev.setConfigPathOverride(relCwdPathToConfig);
|
elev.setConfigPathOverride(relCwdPathToConfig);
|
||||||
await elev.init();
|
await elev.init();
|
||||||
|
|
||||||
if (this.config.watch) {
|
|
||||||
elev.watch();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.eleventy = elev;
|
this.eleventy = elev;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -116,48 +107,43 @@ export class RocketCli {
|
|||||||
async run() {
|
async run() {
|
||||||
await this.setup();
|
await this.setup();
|
||||||
|
|
||||||
if (this.config) {
|
for (const plugin of this.config.plugins) {
|
||||||
for (const plugin of this.config.plugins) {
|
if (this.considerPlugin(plugin) && typeof plugin.setupCommand === 'function') {
|
||||||
if (this.considerPlugin(plugin)) {
|
this.config = plugin.setupCommand(this.config);
|
||||||
if (typeof plugin.setupCommand === 'function') {
|
|
||||||
this.config = plugin.setupCommand(this.config);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof plugin.setup === 'function') {
|
|
||||||
await plugin.setup({ config: this.config, argv: this.subArgv });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.mergePresets();
|
await fs.emptyDir(this.config.outputDevDir);
|
||||||
await this.setupEleventy();
|
await this.setupEleventy();
|
||||||
|
|
||||||
if (this.config) {
|
for (const plugin of this.config.plugins) {
|
||||||
await this.updateComplete;
|
if (typeof plugin.setup === 'function') {
|
||||||
|
await plugin.setup({ config: this.config, argv: this.subArgv, eleventy: this.eleventy });
|
||||||
for (const plugin of this.config.plugins) {
|
|
||||||
if (this.considerPlugin(plugin) && typeof plugin.execute === 'function') {
|
|
||||||
await plugin.execute();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.config.watch === false && this.eleventy) {
|
// execute the actual command
|
||||||
await this.eleventy.write();
|
let executedAtLeastOneCommand = false;
|
||||||
|
const commandFn = `${this.config.command}Command`;
|
||||||
|
for (const plugin of this.config.plugins) {
|
||||||
|
if (this.considerPlugin(plugin) && typeof plugin[commandFn] === 'function') {
|
||||||
|
console.log(`Rocket executes ${commandFn} of ${plugin.constructor.pluginName}`);
|
||||||
|
executedAtLeastOneCommand = true;
|
||||||
|
await plugin[commandFn]();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if (executedAtLeastOneCommand === false) {
|
||||||
|
throw new Error(`No Rocket Cli Plugin had a ${commandFn} function.`);
|
||||||
|
}
|
||||||
|
|
||||||
// Build Phase
|
for (const plugin of this.config.plugins) {
|
||||||
if (this.config.command === 'build') {
|
if (this.considerPlugin(plugin) && typeof plugin.postCommand === 'function') {
|
||||||
for (const plugin of this.config.plugins) {
|
await plugin.postCommand();
|
||||||
if (typeof plugin.build === 'function') {
|
|
||||||
await plugin.build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (this.config.command === 'help') {
|
if (this.config.command === 'help') {
|
||||||
console.log('Help is here: use build or start');
|
console.log('Help is here: use build or start');
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
91
packages/cli/src/RocketLint.js
Executable file
91
packages/cli/src/RocketLint.js
Executable file
@@ -0,0 +1,91 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
|
|
||||||
|
/** @typedef {import('../types/main').RocketCliOptions} RocketCliOptions */
|
||||||
|
|
||||||
|
import chalk from 'chalk';
|
||||||
|
import { validateFolder, formatErrors } from 'check-html-links';
|
||||||
|
|
||||||
|
export class RocketLint {
|
||||||
|
static pluginName = 'RocketLint';
|
||||||
|
commands = ['start', 'build', 'lint'];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {RocketCliOptions} config
|
||||||
|
*/
|
||||||
|
setupCommand(config) {
|
||||||
|
if (config.command === 'lint') {
|
||||||
|
config.watch = false;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {object} options
|
||||||
|
* @param {RocketCliOptions} options.config
|
||||||
|
* @param {any} options.argv
|
||||||
|
*/
|
||||||
|
async setup({ config, argv, eleventy }) {
|
||||||
|
this.__argv = argv;
|
||||||
|
this.config = {
|
||||||
|
lintInputDir: config.outputDevDir,
|
||||||
|
lintExecutesEleventyBefore: true,
|
||||||
|
...config,
|
||||||
|
};
|
||||||
|
this.eleventy = eleventy;
|
||||||
|
}
|
||||||
|
|
||||||
|
async lintCommand() {
|
||||||
|
if (this.config.lintExecutesEleventyBefore) {
|
||||||
|
await this.eleventy.write();
|
||||||
|
// updated will trigger linting
|
||||||
|
} else {
|
||||||
|
await this.__lint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async __lint() {
|
||||||
|
if (this.config?.pathPrefix) {
|
||||||
|
console.log('INFO: RocketLint currently does not support being used with a pathPrefix');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const performanceStart = process.hrtime();
|
||||||
|
console.log('👀 Checking if all internal links work...');
|
||||||
|
const errors = await validateFolder(this.config.lintInputDir);
|
||||||
|
const performance = process.hrtime(performanceStart);
|
||||||
|
if (errors.length > 0) {
|
||||||
|
let referenceCount = 0;
|
||||||
|
for (const error of errors) {
|
||||||
|
referenceCount += error.usage.length;
|
||||||
|
}
|
||||||
|
const output = [
|
||||||
|
`❌ Found ${chalk.red.bold(
|
||||||
|
errors.length.toString(),
|
||||||
|
)} missing reference targets (used by ${referenceCount} links):`,
|
||||||
|
...formatErrors(errors)
|
||||||
|
.split('\n')
|
||||||
|
.map(line => ` ${line}`),
|
||||||
|
`Checking links duration: ${performance[0]}s ${performance[1] / 1000000}ms`,
|
||||||
|
];
|
||||||
|
if (this.config.watch) {
|
||||||
|
console.log(output.join('\n'));
|
||||||
|
} else {
|
||||||
|
throw new Error(output.join('\n'));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('✅ All internal links are valid.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async postCommand() {
|
||||||
|
if (this.config.watch === false) {
|
||||||
|
await this.__lint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updated() {
|
||||||
|
if (this.config.watch === true) {
|
||||||
|
await this.__lint();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import { metaConfigToWebDevServerConfig } from 'plugins-manager';
|
|||||||
/** @typedef {import('@web/dev-server').DevServerConfig} DevServerConfig */
|
/** @typedef {import('@web/dev-server').DevServerConfig} DevServerConfig */
|
||||||
|
|
||||||
export class RocketStart {
|
export class RocketStart {
|
||||||
|
static pluginName = 'RocketStart';
|
||||||
commands = ['start'];
|
commands = ['start'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -22,7 +23,7 @@ export class RocketStart {
|
|||||||
* @param {RocketCliOptions} options.config
|
* @param {RocketCliOptions} options.config
|
||||||
* @param {any} options.argv
|
* @param {any} options.argv
|
||||||
*/
|
*/
|
||||||
async setup({ config, argv }) {
|
async setup({ config, argv, eleventy }) {
|
||||||
this.__argv = argv;
|
this.__argv = argv;
|
||||||
this.config = {
|
this.config = {
|
||||||
...config,
|
...config,
|
||||||
@@ -30,13 +31,20 @@ export class RocketStart {
|
|||||||
...config.devServer,
|
...config.devServer,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
this.eleventy = eleventy;
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute() {
|
async startCommand() {
|
||||||
if (!this.config) {
|
if (!this.config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.config.watch) {
|
||||||
|
await this.eleventy.watch();
|
||||||
|
} else {
|
||||||
|
await this.eleventy.write();
|
||||||
|
}
|
||||||
|
|
||||||
/** @type {DevServerConfig} */
|
/** @type {DevServerConfig} */
|
||||||
const devServerConfig = metaConfigToWebDevServerConfig(
|
const devServerConfig = metaConfigToWebDevServerConfig(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { readConfig } from '@web/config-loader';
|
|||||||
|
|
||||||
import { RocketStart } from './RocketStart.js';
|
import { RocketStart } from './RocketStart.js';
|
||||||
import { RocketBuild } from './RocketBuild.js';
|
import { RocketBuild } from './RocketBuild.js';
|
||||||
|
import { RocketLint } from './RocketLint.js';
|
||||||
|
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
@@ -36,7 +37,7 @@ export async function normalizeConfig(inConfig) {
|
|||||||
watch: true,
|
watch: true,
|
||||||
inputDir: 'docs',
|
inputDir: 'docs',
|
||||||
outputDir: '_site',
|
outputDir: '_site',
|
||||||
outputDevDir: path.resolve('_site-dev'),
|
outputDevDir: '_site-dev',
|
||||||
build: {},
|
build: {},
|
||||||
devServer: {},
|
devServer: {},
|
||||||
|
|
||||||
@@ -114,8 +115,9 @@ export async function normalizeConfig(inConfig) {
|
|||||||
|
|
||||||
/** @type {MetaPlugin[]} */
|
/** @type {MetaPlugin[]} */
|
||||||
let pluginsMeta = [
|
let pluginsMeta = [
|
||||||
{ name: 'rocket-start', plugin: RocketStart },
|
{ name: 'RocketStart', plugin: RocketStart },
|
||||||
{ name: 'rocket-build', plugin: RocketBuild },
|
{ name: 'RocketBuild', plugin: RocketBuild },
|
||||||
|
{ name: 'RocketLint', plugin: RocketLint },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (Array.isArray(config.setupCliPlugins)) {
|
if (Array.isArray(config.setupCliPlugins)) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
|
||||||
const { processContentWithTitle } = require('@rocket/core/title');
|
const { processContentWithTitle } = require('@rocket/core/title');
|
||||||
const { createPageSocialImage } = require('./createPageSocialImage.cjs');
|
const { createPageSocialImage } = require('./createPageSocialImage.cjs');
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@ module.exports = {
|
|||||||
if (data.page.filePathStem) {
|
if (data.page.filePathStem) {
|
||||||
// filePathStem: '/sub/subsub/index'
|
// filePathStem: '/sub/subsub/index'
|
||||||
// filePathStem: '/index',
|
// filePathStem: '/index',
|
||||||
const parts = data.page.filePathStem.split(path.sep);
|
const parts = data.page.filePathStem.split('/');
|
||||||
if (parts.length > 2) {
|
if (parts.length > 2) {
|
||||||
return parts[1];
|
return parts[1];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { RocketCli } from '../src/RocketCli.js';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import fs from 'fs-extra';
|
import fs from 'fs-extra';
|
||||||
|
import chalk from 'chalk';
|
||||||
|
|
||||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
@@ -13,7 +14,7 @@ const { expect } = chai;
|
|||||||
* @param {function} method
|
* @param {function} method
|
||||||
* @param {string} errorMessage
|
* @param {string} errorMessage
|
||||||
*/
|
*/
|
||||||
async function expectThrowsAsync(method, errorMessage) {
|
async function expectThrowsAsync(method, { errorMatch, errorMessage } = {}) {
|
||||||
let error = null;
|
let error = null;
|
||||||
try {
|
try {
|
||||||
await method();
|
await method();
|
||||||
@@ -21,8 +22,11 @@ async function expectThrowsAsync(method, errorMessage) {
|
|||||||
error = err;
|
error = err;
|
||||||
}
|
}
|
||||||
expect(error).to.be.an('Error', 'No error was thrown');
|
expect(error).to.be.an('Error', 'No error was thrown');
|
||||||
|
if (errorMatch) {
|
||||||
|
expect(error.message).to.match(errorMatch);
|
||||||
|
}
|
||||||
if (errorMessage) {
|
if (errorMessage) {
|
||||||
expect(error.message).to.match(errorMessage);
|
expect(error.message).to.equal(errorMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,6 +87,18 @@ describe('RocketCli e2e', () => {
|
|||||||
await execute();
|
await execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function executeLint(pathToConfig) {
|
||||||
|
cli = new RocketCli({
|
||||||
|
argv: ['lint', '--config-file', path.join(__dirname, pathToConfig.split('/').join(path.sep))],
|
||||||
|
});
|
||||||
|
await execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
before(() => {
|
||||||
|
// ignore colors in tests as most CIs won't support it
|
||||||
|
chalk.level = 0;
|
||||||
|
});
|
||||||
|
|
||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
if (cli?.cleanup) {
|
if (cli?.cleanup) {
|
||||||
await cli.cleanup();
|
await cli.cleanup();
|
||||||
@@ -133,7 +149,9 @@ describe('RocketCli e2e', () => {
|
|||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
await expectThrowsAsync(() => execute(), /Error in your Eleventy config file.*/);
|
await expectThrowsAsync(() => execute(), {
|
||||||
|
errorMatch: /Error in your Eleventy config file.*/,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -215,16 +233,16 @@ describe('RocketCli e2e', () => {
|
|||||||
type: 'start',
|
type: 'start',
|
||||||
});
|
});
|
||||||
expect(indexHtml).to.equal(
|
expect(indexHtml).to.equal(
|
||||||
'<p>You can show rocket config data like rocketConfig.absoluteBaseUrl = http://test-domain.com/</p>',
|
'<p>You can show rocket config data like rocketConfig.absoluteBaseUrl = <a href="http://test-domain.com/">http://test-domain.com/</a></p>',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can add a pathprefix that will not influence the start command', async () => {
|
it('can add a pathPrefix that will not influence the start command', async () => {
|
||||||
cli = new RocketCli({
|
cli = new RocketCli({
|
||||||
argv: [
|
argv: [
|
||||||
'start',
|
'start',
|
||||||
'--config-file',
|
'--config-file',
|
||||||
path.join(__dirname, 'e2e-fixtures', 'content', 'pathprefix.rocket.config.js'),
|
path.join(__dirname, 'e2e-fixtures', 'content', 'pathPrefix.rocket.config.js'),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
await execute();
|
await execute();
|
||||||
@@ -233,7 +251,7 @@ describe('RocketCli e2e', () => {
|
|||||||
type: 'start',
|
type: 'start',
|
||||||
});
|
});
|
||||||
expect(linkHtml).to.equal(
|
expect(linkHtml).to.equal(
|
||||||
['<p><a href="../../">home</a></p>', '<p><a href="/">absolute home</a></p>'].join('\n'),
|
['<p><a href="../">home</a></p>', '<p><a href="/">absolute home</a></p>'].join('\n'),
|
||||||
);
|
);
|
||||||
const assetHtml = await readOutput('use-assets/index.html', {
|
const assetHtml = await readOutput('use-assets/index.html', {
|
||||||
type: 'start',
|
type: 'start',
|
||||||
@@ -256,10 +274,9 @@ describe('RocketCli e2e', () => {
|
|||||||
stripToBody: true,
|
stripToBody: true,
|
||||||
});
|
});
|
||||||
expect(linkHtml).to.equal(
|
expect(linkHtml).to.equal(
|
||||||
[
|
['<p><a href="../">home</a></p>', '<p><a href="/my-sub-folder/">absolute home</a></p>'].join(
|
||||||
'<p><a href="../../">home</a></p>',
|
'\n',
|
||||||
'<p><a href="/my-sub-folder/">absolute home</a></p>',
|
),
|
||||||
].join('\n'),
|
|
||||||
);
|
);
|
||||||
const assetHtml = await readOutput('use-assets/index.html', {
|
const assetHtml = await readOutput('use-assets/index.html', {
|
||||||
stripServiceWorker: true,
|
stripServiceWorker: true,
|
||||||
@@ -336,36 +353,29 @@ describe('RocketCli e2e', () => {
|
|||||||
expect(guidesHtml).to.equal('/_merged_assets/11ty-img/58b7e437-1200.png');
|
expect(guidesHtml).to.equal('/_merged_assets/11ty-img/58b7e437-1200.png');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('will add "../" for links and image urls only within named template files', async () => {
|
it.only('will add "../" for links and image urls only within named template files', async () => {
|
||||||
await executeStart('e2e-fixtures/image-link/rocket.config.js');
|
await executeStart('e2e-fixtures/image-link/rocket.config.js');
|
||||||
|
|
||||||
const namedMdContent = [
|
const namedMdContent = [
|
||||||
'<p><a href="../">Root</a>',
|
'<p><a href="../">Root</a>',
|
||||||
'<a href="../guides/#with-anchor">Guides</a>',
|
|
||||||
'<a href="../one-level/raw/">Raw</a>',
|
'<a href="../one-level/raw/">Raw</a>',
|
||||||
'<a href="../../up-one-level/template/">Template</a>',
|
'<img src="../images/my-img.svg" alt="my-img">',
|
||||||
'<img src="../images-one-level/my-img.svg" alt="my-img">',
|
'<img src="/images/my-img.svg" alt="absolute-img"></p>',
|
||||||
'<img src="/absolute-img.svg" alt="absolute-img"></p>',
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const namedHtmlContent = [
|
const namedHtmlContent = [
|
||||||
'<div>',
|
'<div id="with-anchor">',
|
||||||
' <a href="../">Root</a>',
|
' <a href="../">Root</a>',
|
||||||
' <a href="../guides/#with-anchor">Guides</a>',
|
|
||||||
' <a href="../one-level/raw/">Raw</a>',
|
' <a href="../one-level/raw/">Raw</a>',
|
||||||
' <a href="../../up-one-level/template/">Template</a>',
|
' <img src="../images/my-img.svg" alt="my-img">',
|
||||||
' <img src="../images-one-level/my-img.svg" alt="my-img">',
|
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||||
' <img src="/absolute-img.svg" alt="absolute-img">',
|
|
||||||
' <picture>',
|
' <picture>',
|
||||||
' <source media="(min-width:465px)" srcset="../picture-min-465.jpg">',
|
' <source media="(min-width:465px)" srcset="../images/picture-min-465.jpg">',
|
||||||
' <img src="../../images-up-one-level/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
' <img src="../images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||||
' </picture>',
|
' </picture>',
|
||||||
'</div>',
|
'</div>',
|
||||||
];
|
];
|
||||||
|
|
||||||
const rawHtml = await readStartOutput('raw/index.html');
|
|
||||||
expect(rawHtml, 'raw/index.html does not match').to.equal(namedHtmlContent.join('\n'));
|
|
||||||
|
|
||||||
const templateHtml = await readStartOutput('template/index.html');
|
const templateHtml = await readStartOutput('template/index.html');
|
||||||
expect(templateHtml, 'template/index.html does not match').to.equal(
|
expect(templateHtml, 'template/index.html does not match').to.equal(
|
||||||
namedHtmlContent.join('\n'),
|
namedHtmlContent.join('\n'),
|
||||||
@@ -381,6 +391,22 @@ describe('RocketCli e2e', () => {
|
|||||||
'<p>Nothing to adjust in here</p>',
|
'<p>Nothing to adjust in here</p>',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const rawHtml = await readStartOutput('one-level/raw/index.html');
|
||||||
|
expect(rawHtml, 'raw/index.html does not match').to.equal(
|
||||||
|
[
|
||||||
|
'<div>',
|
||||||
|
' <a href="../../">Root</a>',
|
||||||
|
' <a href="../../guides/#with-anchor">Guides</a>',
|
||||||
|
' <img src="../../images/my-img.svg" alt="my-img">',
|
||||||
|
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||||
|
' <picture>',
|
||||||
|
' <source media="(min-width:465px)" srcset="/images/picture-min-465.jpg">',
|
||||||
|
' <img src="../../images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||||
|
' </picture>',
|
||||||
|
'</div>',
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
|
||||||
// for index files no '../' will be added
|
// for index files no '../' will be added
|
||||||
const indexHtml = await readStartOutput('index.html');
|
const indexHtml = await readStartOutput('index.html');
|
||||||
expect(indexHtml, 'index.html does not match').to.equal(
|
expect(indexHtml, 'index.html does not match').to.equal(
|
||||||
@@ -388,22 +414,28 @@ describe('RocketCli e2e', () => {
|
|||||||
'<p><a href="./">Root</a>',
|
'<p><a href="./">Root</a>',
|
||||||
'<a href="guides/#with-anchor">Guides</a>',
|
'<a href="guides/#with-anchor">Guides</a>',
|
||||||
'<a href="./one-level/raw/">Raw</a>',
|
'<a href="./one-level/raw/">Raw</a>',
|
||||||
'<a href="../up-one-level/template/">Template</a>',
|
'<a href="template/">Template</a>',
|
||||||
'<img src="./images-one-level/my-img.svg" alt="my-img">',
|
'<img src="./images/my-img.svg" alt="my-img">',
|
||||||
'<img src="/absolute-img.svg" alt="absolute-img"></p>',
|
'<img src="/images/my-img.svg" alt="absolute-img"></p>',
|
||||||
'<div>',
|
'<div>',
|
||||||
' <a href="./">Root</a>',
|
' <a href="./">Root</a>',
|
||||||
' <a href="guides/#with-anchor">Guides</a>',
|
' <a href="guides/#with-anchor">Guides</a>',
|
||||||
' <a href="./one-level/raw/">Raw</a>',
|
' <a href="./one-level/raw/">Raw</a>',
|
||||||
' <a href="../up-one-level/template/">Template</a>',
|
' <a href="template/">Template</a>',
|
||||||
' <img src="./images-one-level/my-img.svg" alt="my-img">',
|
' <img src="./images/my-img.svg" alt="my-img">',
|
||||||
' <img src="/absolute-img.svg" alt="absolute-img">',
|
' <img src="/images/my-img.svg" alt="absolute-img">',
|
||||||
' <picture>',
|
' <picture>',
|
||||||
' <source media="(min-width:465px)" srcset="./picture-min-465.jpg">',
|
' <source media="(min-width:465px)" srcset="./images/picture-min-465.jpg">',
|
||||||
' <img src="../images-up-one-level/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
' <img src="./images/picture-fallback.jpg" alt="Fallback" style="width:auto;">',
|
||||||
' </picture>',
|
' </picture>',
|
||||||
'</div>',
|
'</div>',
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('smoke test for link checking', async () => {
|
||||||
|
await expectThrowsAsync(() => executeLint('e2e-fixtures/lint-links/rocket.config.js'), {
|
||||||
|
errorMatch: /Found 1 missing reference targets/,
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,6 @@
|
|||||||
layout: layout.njk
|
layout: layout.njk
|
||||||
---
|
---
|
||||||
|
|
||||||
[home](../index.md)
|
[home](./index.md)
|
||||||
|
|
||||||
<a href="{{ '/' | url }}">absolute home</a>
|
<a href="{{ '/' | url }}">absolute home</a>
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
[Root](./index.md)
|
[Root](./index.md)
|
||||||
[Guides](./guides.md#with-anchor)
|
|
||||||
[Raw](./one-level/raw.html)
|
[Raw](./one-level/raw.html)
|
||||||
[Template](../up-one-level/template.njk)
|

|
||||||

|

|
||||||

|
|
||||||
|
|
||||||
<div>
|
<div id="with-anchor">
|
||||||
<a href="./index.md">Root</a>
|
<a href="./index.md">Root</a>
|
||||||
<a href="./guides.md#with-anchor">Guides</a>
|
|
||||||
<a href="./one-level/raw.html">Raw</a>
|
<a href="./one-level/raw.html">Raw</a>
|
||||||
<a href="../up-one-level/template.njk">Template</a>
|
<img src="./images/my-img.svg" alt="my-img">
|
||||||
<img src="./images-one-level/my-img.svg" alt="my-img">
|
<img src="/images/my-img.svg" alt="absolute-img">
|
||||||
<img src="/absolute-img.svg" alt="absolute-img">
|
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(min-width:465px)" srcset="./picture-min-465.jpg">
|
<source media="(min-width:465px)" srcset="./images/picture-min-465.jpg">
|
||||||
<img src="../images-up-one-level/picture-fallback.jpg" alt="Fallback" style="width:auto;">
|
<img src="./images/picture-fallback.jpg" alt="Fallback" style="width:auto;">
|
||||||
</picture>
|
</picture>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
[Root](./)
|
[Root](./)
|
||||||
[Guides](./guides.md#with-anchor)
|
[Guides](./guides.md#with-anchor)
|
||||||
[Raw](./one-level/raw.html)
|
[Raw](./one-level/raw.html)
|
||||||
[Template](../up-one-level/template.njk)
|
[Template](./template.njk)
|
||||||

|

|
||||||

|

|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<a href="./">Root</a>
|
<a href="./">Root</a>
|
||||||
<a href="./guides.md#with-anchor">Guides</a>
|
<a href="./guides.md#with-anchor">Guides</a>
|
||||||
<a href="./one-level/raw.html">Raw</a>
|
<a href="./one-level/raw.html">Raw</a>
|
||||||
<a href="../up-one-level/template.njk">Template</a>
|
<a href="./template.njk">Template</a>
|
||||||
<img src="./images-one-level/my-img.svg" alt="my-img">
|
<img src="./images/my-img.svg" alt="my-img">
|
||||||
<img src="/absolute-img.svg" alt="absolute-img">
|
<img src="/images/my-img.svg" alt="absolute-img">
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(min-width:465px)" srcset="./picture-min-465.jpg">
|
<source media="(min-width:465px)" srcset="./images/picture-min-465.jpg">
|
||||||
<img src="../images-up-one-level/picture-fallback.jpg" alt="Fallback" style="width:auto;">
|
<img src="./images/picture-fallback.jpg" alt="Fallback" style="width:auto;">
|
||||||
</picture>
|
</picture>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<div>
|
||||||
|
<a href="../index.md">Root</a>
|
||||||
|
<a href="../guides.md#with-anchor">Guides</a>
|
||||||
|
<img src="../images/my-img.svg" alt="my-img">
|
||||||
|
<img src="/images/my-img.svg" alt="absolute-img">
|
||||||
|
<picture>
|
||||||
|
<source media="(min-width:465px)" srcset="/images/picture-min-465.jpg">
|
||||||
|
<img src="../images/picture-fallback.jpg" alt="Fallback" style="width:auto;">
|
||||||
|
</picture>
|
||||||
|
</div>
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
<div>
|
|
||||||
<a href="./index.md">Root</a>
|
|
||||||
<a href="./guides.md#with-anchor">Guides</a>
|
|
||||||
<a href="./one-level/raw.html">Raw</a>
|
|
||||||
<a href="../up-one-level/template.njk">Template</a>
|
|
||||||
<img src="./images-one-level/my-img.svg" alt="my-img">
|
|
||||||
<img src="/absolute-img.svg" alt="absolute-img">
|
|
||||||
<picture>
|
|
||||||
<source media="(min-width:465px)" srcset="./picture-min-465.jpg">
|
|
||||||
<img src="../images-up-one-level/picture-fallback.jpg" alt="Fallback" style="width:auto;">
|
|
||||||
</picture>
|
|
||||||
</div>
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
<div>
|
<div id="with-anchor">
|
||||||
<a href="./index.md">Root</a>
|
<a href="./index.md">Root</a>
|
||||||
<a href="./guides.md#with-anchor">Guides</a>
|
|
||||||
<a href="./one-level/raw.html">Raw</a>
|
<a href="./one-level/raw.html">Raw</a>
|
||||||
<a href="../up-one-level/template.njk">Template</a>
|
<img src="./images/my-img.svg" alt="my-img">
|
||||||
<img src="./images-one-level/my-img.svg" alt="my-img">
|
<img src="/images/my-img.svg" alt="absolute-img">
|
||||||
<img src="/absolute-img.svg" alt="absolute-img">
|
|
||||||
<picture>
|
<picture>
|
||||||
<source media="(min-width:465px)" srcset="./picture-min-465.jpg">
|
<source media="(min-width:465px)" srcset="./images/picture-min-465.jpg">
|
||||||
<img src="../images-up-one-level/picture-fallback.jpg" alt="Fallback" style="width:auto;">
|
<img src="./images/picture-fallback.jpg" alt="Fallback" style="width:auto;">
|
||||||
</picture>
|
</picture>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<a href="./foo"></a>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
/** @type {Partial<import("../../../types/main").RocketCliOptions>} */
|
||||||
|
const config = {};
|
||||||
|
export default config;
|
||||||
@@ -39,7 +39,11 @@ describe('normalizeConfig', () => {
|
|||||||
setupEleventyPlugins: [],
|
setupEleventyPlugins: [],
|
||||||
setupCliPlugins: [],
|
setupCliPlugins: [],
|
||||||
presets: [],
|
presets: [],
|
||||||
plugins: [{ commands: ['start'] }, { commands: ['build'] }],
|
plugins: [
|
||||||
|
{ commands: ['start'] },
|
||||||
|
{ commands: ['build'] },
|
||||||
|
{ commands: ['start', 'build', 'lint'] },
|
||||||
|
],
|
||||||
inputDir: 'docs',
|
inputDir: 'docs',
|
||||||
outputDir: '_site',
|
outputDir: '_site',
|
||||||
});
|
});
|
||||||
@@ -68,7 +72,11 @@ describe('normalizeConfig', () => {
|
|||||||
setupEleventyPlugins: [],
|
setupEleventyPlugins: [],
|
||||||
setupCliPlugins: [],
|
setupCliPlugins: [],
|
||||||
presets: [],
|
presets: [],
|
||||||
plugins: [{ commands: ['start'] }, { commands: ['build'] }],
|
plugins: [
|
||||||
|
{ commands: ['start'] },
|
||||||
|
{ commands: ['build'] },
|
||||||
|
{ commands: ['start', 'build', 'lint'] },
|
||||||
|
],
|
||||||
inputDir: 'docs',
|
inputDir: 'docs',
|
||||||
outputDir: '_site',
|
outputDir: '_site',
|
||||||
});
|
});
|
||||||
@@ -94,7 +102,11 @@ describe('normalizeConfig', () => {
|
|||||||
setupEleventyPlugins: [],
|
setupEleventyPlugins: [],
|
||||||
setupCliPlugins: [],
|
setupCliPlugins: [],
|
||||||
presets: [],
|
presets: [],
|
||||||
plugins: [{ commands: ['start'] }, { commands: ['build'] }],
|
plugins: [
|
||||||
|
{ commands: ['start'] },
|
||||||
|
{ commands: ['build'] },
|
||||||
|
{ commands: ['start', 'build', 'lint'] },
|
||||||
|
],
|
||||||
inputDir: 'docs',
|
inputDir: 'docs',
|
||||||
outputDir: '_site',
|
outputDir: '_site',
|
||||||
});
|
});
|
||||||
@@ -123,7 +135,11 @@ describe('normalizeConfig', () => {
|
|||||||
setupEleventyPlugins: [],
|
setupEleventyPlugins: [],
|
||||||
setupCliPlugins: [],
|
setupCliPlugins: [],
|
||||||
presets: [],
|
presets: [],
|
||||||
plugins: [{ commands: ['start'] }, { commands: ['build'] }],
|
plugins: [
|
||||||
|
{ commands: ['start'] },
|
||||||
|
{ commands: ['build'] },
|
||||||
|
{ commands: ['start', 'build', 'lint'] },
|
||||||
|
],
|
||||||
inputDir: 'docs',
|
inputDir: 'docs',
|
||||||
outputDir: '_site',
|
outputDir: '_site',
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @rocket/drawer
|
# @rocket/drawer
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 897892d: bump dependencies
|
||||||
|
|
||||||
## 0.1.1
|
## 0.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rocket/drawer",
|
"name": "@rocket/drawer",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"testing"
|
"testing"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lion/overlays": "^0.22.8",
|
"@lion/overlays": "^0.23.2",
|
||||||
"lit-element": "^2.4.0"
|
"lit-element": "^2.4.0"
|
||||||
},
|
},
|
||||||
"types": "dist-types/index.d.ts"
|
"types": "dist-types/index.d.ts"
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @rocket/eleventy-plugin-mdjs-unified
|
# @rocket/eleventy-plugin-mdjs-unified
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- cd22231: Adjustments to work with the restructured CLI Plugin System
|
||||||
|
|
||||||
## 0.2.0
|
## 0.2.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rocket/eleventy-plugin-mdjs-unified",
|
"name": "@rocket/eleventy-plugin-mdjs-unified",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"mdjs"
|
"mdjs"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mdjs/core": "^0.6.1",
|
"@mdjs/core": "^0.6.2",
|
||||||
"es-module-lexer": "^0.3.26",
|
"es-module-lexer": "^0.3.26",
|
||||||
"unist-util-visit": "^2.0.3"
|
"unist-util-visit": "^2.0.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
# @rocket/eleventy-rocket-nav
|
# @rocket/eleventy-rocket-nav
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 897892d: bump dependencies
|
||||||
|
|
||||||
## 0.2.0
|
## 0.2.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rocket/eleventy-rocket-nav",
|
"name": "@rocket/eleventy-rocket-nav",
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -27,7 +27,7 @@
|
|||||||
"eleventy-plugin"
|
"eleventy-plugin"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dependency-graph": "^0.9.0",
|
"dependency-graph": "^0.10.0",
|
||||||
"sax-wasm": "^2.0.0"
|
"sax-wasm": "^2.0.0"
|
||||||
},
|
},
|
||||||
"types": "dist-types/index.d.ts"
|
"types": "dist-types/index.d.ts"
|
||||||
|
|||||||
@@ -1,5 +1,24 @@
|
|||||||
# @rocket/launch
|
# @rocket/launch
|
||||||
|
|
||||||
|
## 0.3.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- cd22231: Adjustments to work with the restructured CLI Plugin System
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- cd22231: Move `noscript.css` into `_assets/_static` as it does not get detected/moved automatically by `@web/rollup-plugin-html`.
|
||||||
|
|
||||||
|
## 0.2.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 86c3a4b: adds themable CSS custom properties to <inline-notification>
|
||||||
|
- 7dd6f4c: Make default logo work with auto generating social media images
|
||||||
|
- Updated dependencies [897892d]
|
||||||
|
- @rocket/drawer@0.1.2
|
||||||
|
|
||||||
## 0.2.0
|
## 0.2.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -31,26 +31,29 @@ export class InlineNotification extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
:host([type='tip']) {
|
:host([type='tip']) {
|
||||||
background-color: rgba(221, 221, 221, 0.3);
|
background-color: var(--inline-notification-tip-background-color, rgba(221, 221, 221, 0.3));
|
||||||
border-color: #42b983;
|
border-color: var(--inline-notification-tip-border-color, #42b983);
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([type='warning']) {
|
:host([type='warning']) {
|
||||||
background-color: rgba(255, 229, 100, 0.2);
|
background-color: var(
|
||||||
border-color: #e7c000;
|
--inline-notification-warning-background-color,
|
||||||
}
|
rgba(255, 229, 100, 0.2)
|
||||||
|
);
|
||||||
:host([type='warning']) h3 {
|
border-color: var(--inline-notification-warning-border-color, #e7c000);
|
||||||
color: #b29400;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([type='danger']) {
|
:host([type='danger']) {
|
||||||
background-color: rgba(192, 0, 0, 0.1);
|
background-color: var(--inline-notification-danger-background-color, rgba(192, 0, 0, 0.1));
|
||||||
border-color: #c00;
|
border-color: var(--inline-notification-danger-border-color, #c00);
|
||||||
|
}
|
||||||
|
|
||||||
|
:host([type='warning']) h3 {
|
||||||
|
color: var(--inline-notification-warning-heading-color, #b29400);
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([type='danger']) h3 {
|
:host([type='danger']) h3 {
|
||||||
color: #900;
|
color: var(--inline-notification-danger-heading-color, #900);
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(p) {
|
::slotted(p) {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rocket/launch",
|
"name": "@rocket/launch",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"preset"
|
"preset"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@rocket/drawer": "^0.1.1",
|
"@rocket/drawer": "^0.1.2",
|
||||||
"@rocket/navigation": "^0.1.0"
|
"@rocket/navigation": "^0.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
|
||||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
|
||||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
viewBox="0 0 511.998 511.998" style="enable-background:new 0 0 511.998 511.998;" xml:space="preserve">
|
viewBox="0 0 511.998 511.998" style="enable-background:new 0 0 511.998 511.998;" xml:space="preserve">
|
||||||
<g>
|
<g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -42,6 +42,6 @@
|
|||||||
<link rel="stylesheet" href="{{ '/_assets/layout.css' | asset | url }}">
|
<link rel="stylesheet" href="{{ '/_assets/layout.css' | asset | url }}">
|
||||||
<link rel="stylesheet" href="{{ '/_assets/markdown.css' | asset | url }}">
|
<link rel="stylesheet" href="{{ '/_assets/markdown.css' | asset | url }}">
|
||||||
<link rel="stylesheet" href="{{ '/_assets/style.css' | asset | url }}">
|
<link rel="stylesheet" href="{{ '/_assets/style.css' | asset | url }}">
|
||||||
<noscript><link rel="stylesheet" href="{{ '/_assets/noscript.css' | asset | url }}"/></noscript>
|
<noscript><link rel="stylesheet" href="{{ '/_assets/_static/noscript.css' | asset | url }}"/></noscript>
|
||||||
|
|
||||||
{% include 'partials/head.njk' %}
|
{% include 'partials/head.njk' %}
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 0.6.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 897892d: bump dependencies
|
||||||
|
- b68923b: Add gfm to support markdown tables
|
||||||
|
|
||||||
## 0.6.1
|
## 0.6.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@mdjs/core",
|
"name": "@mdjs/core",
|
||||||
"version": "0.6.1",
|
"version": "0.6.2",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -54,10 +54,11 @@
|
|||||||
"rehype-raw": "^5.0.0",
|
"rehype-raw": "^5.0.0",
|
||||||
"rehype-slug": "^4.0.1",
|
"rehype-slug": "^4.0.1",
|
||||||
"rehype-stringify": "^8.0.0",
|
"rehype-stringify": "^8.0.0",
|
||||||
"remark": "^11.0.2",
|
"remark": "^13.0.0",
|
||||||
|
"remark-gfm": "^1.0.0",
|
||||||
"remark-parse": "^9.0.0",
|
"remark-parse": "^9.0.0",
|
||||||
"remark-rehype": "^8.0.0",
|
"remark-rehype": "^8.0.0",
|
||||||
"unified": "^8.4.2",
|
"unified": "^9.2.0",
|
||||||
"unist-util-remove": "^2.0.1",
|
"unist-util-remove": "^2.0.1",
|
||||||
"unist-util-visit": "^2.0.3"
|
"unist-util-visit": "^2.0.3"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
|
|
||||||
const unified = require('unified');
|
const unified = require('unified');
|
||||||
const markdown = require('remark-parse');
|
const markdown = require('remark-parse');
|
||||||
|
const gfm = require('remark-gfm');
|
||||||
const remark2rehype = require('remark-rehype');
|
const remark2rehype = require('remark-rehype');
|
||||||
const raw = require('rehype-raw');
|
const raw = require('rehype-raw');
|
||||||
const htmlStringify = require('rehype-stringify');
|
const htmlStringify = require('rehype-stringify');
|
||||||
@@ -21,6 +22,7 @@ const { mdjsStoryParse } = require('./mdjsStoryParse.js');
|
|||||||
/** @type {MdjsProcessPlugin[]} */
|
/** @type {MdjsProcessPlugin[]} */
|
||||||
const defaultMetaPlugins = [
|
const defaultMetaPlugins = [
|
||||||
{ name: 'markdown', plugin: markdown },
|
{ name: 'markdown', plugin: markdown },
|
||||||
|
{ name: 'gfm', plugin: gfm },
|
||||||
{ name: 'mdjsParse', plugin: mdjsParse },
|
{ name: 'mdjsParse', plugin: mdjsParse },
|
||||||
{ name: 'mdjsStoryParse', plugin: mdjsStoryParse },
|
{ name: 'mdjsStoryParse', plugin: mdjsStoryParse },
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@@ -136,4 +136,18 @@ describe('mdjsProcess', () => {
|
|||||||
});
|
});
|
||||||
expect(resultOfArray.html).to.equal(expectedForArray);
|
expect(resultOfArray.html).to.equal(expectedForArray);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('handles tables', async () => {
|
||||||
|
const input = [
|
||||||
|
//
|
||||||
|
'| Page | Type |',
|
||||||
|
'| ----- | ---- |',
|
||||||
|
'| About | Info |',
|
||||||
|
].join('\n');
|
||||||
|
|
||||||
|
const expected =
|
||||||
|
'<table><thead><tr><th>Page</th><th>Type</th></tr></thead><tbody><tr><td>About</td><td>Info</td></tr></tbody></table>';
|
||||||
|
const result = await mdjsProcess(input);
|
||||||
|
expect(result.html.trim()).to.equal(expected);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,5 +1,23 @@
|
|||||||
# @rocket/search
|
# @rocket/search
|
||||||
|
|
||||||
|
## 0.2.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- cd22231: Adjustments to work with the restructured CLI Plugin System
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- 897892d: bump dependencies
|
||||||
|
- 7b2dc64: fix(search): improve a11y
|
||||||
|
|
||||||
|
- a11y: add labels to button
|
||||||
|
- ux: replace 'arrow-left' icon with 'x' icon for 'close search' button
|
||||||
|
- perf: makes all templates static
|
||||||
|
- fix: address typescript errors
|
||||||
|
|
||||||
## 0.1.1
|
## 0.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@rocket/search",
|
"name": "@rocket/search",
|
||||||
"version": "0.1.1",
|
"version": "0.2.0",
|
||||||
"publishConfig": {
|
"publishConfig": {
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
@@ -40,8 +40,9 @@
|
|||||||
"search"
|
"search"
|
||||||
],
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@lion/combobox": "^0.1.16",
|
"@lion/combobox": "^0.1.20",
|
||||||
"@open-wc/scoped-elements": "^1.3.2",
|
"@open-wc/scoped-elements": "^1.3.2",
|
||||||
|
"chalk": "^4.0.0",
|
||||||
"minisearch": "^3.0.2",
|
"minisearch": "^3.0.2",
|
||||||
"plugins-manager": "^0.2.0",
|
"plugins-manager": "^0.2.0",
|
||||||
"sax-wasm": "^2.0.0"
|
"sax-wasm": "^2.0.0"
|
||||||
|
|||||||
@@ -2,11 +2,21 @@ import { css } from '@lion/core';
|
|||||||
import { LionCombobox } from '@lion/combobox';
|
import { LionCombobox } from '@lion/combobox';
|
||||||
import { withDropdownConfig } from '@lion/overlays';
|
import { withDropdownConfig } from '@lion/overlays';
|
||||||
|
|
||||||
const tmpl = document.createElement('template');
|
const closeBtnTmpl = document.createElement('template');
|
||||||
tmpl.innerHTML = `
|
closeBtnTmpl.innerHTML = `
|
||||||
<button arrow-left>
|
<button close-btn aria-label="Back">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 40">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M34 20a.872.872 0 00-.428-.751L20.31 6.25a.875.875 0 00-1.226 1.25l11.861 11.625H6.875a.876.876 0 000 1.75H31.02L19.158 32.5a.877.877 0 00.613 1.5.87.87 0 00.612-.25l13.353-13.089a.876.876 0 00.264-.626l-.001-.017L34 20z" />
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
||||||
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
const searchBtnTmpl = document.createElement('template');
|
||||||
|
searchBtnTmpl.innerHTML = `
|
||||||
|
<button search-btn aria-label="Search">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129">
|
||||||
|
<path d="M51.6 96.7c11 0 21-3.9 28.8-10.5l35 35c.8.8 1.8 1.2 2.9 1.2s2.1-.4 2.9-1.2c1.6-1.6 1.6-4.2 0-5.8l-35-35c6.5-7.8 10.5-17.9 10.5-28.8 0-24.9-20.2-45.1-45.1-45.1-24.8 0-45.1 20.3-45.1 45.1 0 24.9 20.3 45.1 45.1 45.1zm0-82c20.4 0 36.9 16.6 36.9 36.9C88.5 72 72 88.5 51.6 88.5S14.7 71.9 14.7 51.6c0-20.3 16.6-36.9 36.9-36.9z"/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
`;
|
`;
|
||||||
@@ -35,8 +45,8 @@ export class RocketSearchCombobox extends LionCombobox {
|
|||||||
fill: var(--rocket-search-fill-color, #000);
|
fill: var(--rocket-search-fill-color, #000);
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted([slot='prefix'][arrow-left]) {
|
::slotted([slot='prefix'][close-btn]) {
|
||||||
transform: rotate(180deg);
|
height: 25px;
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,11 +89,11 @@ export class RocketSearchCombobox extends LionCombobox {
|
|||||||
background: var(--rocket-search-background-color, #fff);
|
background: var(--rocket-search-background-color, #fff);
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([show-input]) ::slotted([slot='prefix'][arrow-left]) {
|
:host([show-input]) ::slotted([slot='prefix'][close-btn]) {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
:host([show-input]) ::slotted([slot='prefix'][magnifying-glass]) {
|
:host([show-input]) ::slotted([slot='prefix'][search-btn]) {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,6 +125,7 @@ export class RocketSearchCombobox extends LionCombobox {
|
|||||||
:host {
|
:host {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-group__input {
|
.input-group__input {
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
@@ -164,9 +175,9 @@ export class RocketSearchCombobox extends LionCombobox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return /** @type {typeof LionCombobox['properties'] & { showInput: import("lit-element").PropertyDeclaration } } */ ({
|
||||||
showInput: { type: Boolean, reflect: true, attribute: 'show-input' },
|
showInput: { type: Boolean, reflect: true, attribute: 'show-input' },
|
||||||
};
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_connectSlotMixin() {
|
_connectSlotMixin() {
|
||||||
@@ -174,7 +185,9 @@ export class RocketSearchCombobox extends LionCombobox {
|
|||||||
Object.keys(this.slots).forEach(slotName => {
|
Object.keys(this.slots).forEach(slotName => {
|
||||||
if (!this.querySelector(`[slot=${slotName}]`)) {
|
if (!this.querySelector(`[slot=${slotName}]`)) {
|
||||||
const slotFactory = this.slots[slotName];
|
const slotFactory = this.slots[slotName];
|
||||||
|
/** @type {HTMLElement | undefined | HTMLElement[]} */
|
||||||
let slotEls = slotFactory();
|
let slotEls = slotFactory();
|
||||||
|
if (!slotEls) return;
|
||||||
if (!Array.isArray(slotEls)) {
|
if (!Array.isArray(slotEls)) {
|
||||||
slotEls = [slotEls];
|
slotEls = [slotEls];
|
||||||
}
|
}
|
||||||
@@ -183,6 +196,7 @@ export class RocketSearchCombobox extends LionCombobox {
|
|||||||
if (slotEl instanceof Element) {
|
if (slotEl instanceof Element) {
|
||||||
slotEl.setAttribute('slot', slotName);
|
slotEl.setAttribute('slot', slotName);
|
||||||
this.appendChild(slotEl);
|
this.appendChild(slotEl);
|
||||||
|
// @ts-expect-error: explicitly using private field on LionCombobox
|
||||||
this.__privateSlots.add(slotName);
|
this.__privateSlots.add(slotName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,48 +207,33 @@ export class RocketSearchCombobox extends LionCombobox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_defineOverlayConfig() {
|
_defineOverlayConfig() {
|
||||||
|
/** @type {'bottom'} */
|
||||||
|
const placement = 'bottom';
|
||||||
return {
|
return {
|
||||||
...withDropdownConfig(),
|
...withDropdownConfig(),
|
||||||
popperConfig: {
|
popperConfig: {
|
||||||
placement: 'bottom-center',
|
placement,
|
||||||
// modifiers: {
|
|
||||||
// ...parentConfig?.popperConfig?.modifiers,
|
|
||||||
// offset: {
|
|
||||||
// offset: 100,
|
|
||||||
// enabled: false,
|
|
||||||
// },
|
|
||||||
// keepTogether: {
|
|
||||||
// enabled: true,
|
|
||||||
// },
|
|
||||||
// arrow: {
|
|
||||||
// enabled: true,
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @type {LionCombobox['slots']} */
|
||||||
|
// @ts-expect-error: explicitly overriding with accessor
|
||||||
get slots() {
|
get slots() {
|
||||||
return {
|
return {
|
||||||
...super.slots,
|
...super.slots,
|
||||||
|
// @ts-expect-error: error in LionCombobox types.
|
||||||
prefix: () => {
|
prefix: () => {
|
||||||
const arrowLeft = document.importNode(tmpl.content, true).children[0];
|
const [closeButton] = closeBtnTmpl.content.cloneNode(true).children;
|
||||||
arrowLeft.addEventListener('click', () => {
|
closeButton.addEventListener('click', () => {
|
||||||
if (window.innerWidth < 1024) {
|
if (window.innerWidth < 1024) {
|
||||||
this.showInput = false;
|
this.showInput = false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
tmpl.innerHTML = `
|
const [searchButton] = searchBtnTmpl.content.cloneNode(true).children;
|
||||||
<button magnifying-glass>
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129">
|
|
||||||
<path d="M51.6 96.7c11 0 21-3.9 28.8-10.5l35 35c.8.8 1.8 1.2 2.9 1.2s2.1-.4 2.9-1.2c1.6-1.6 1.6-4.2 0-5.8l-35-35c6.5-7.8 10.5-17.9 10.5-28.8 0-24.9-20.2-45.1-45.1-45.1-24.8 0-45.1 20.3-45.1 45.1 0 24.9 20.3 45.1 45.1 45.1zm0-82c20.4 0 36.9 16.6 36.9 36.9C88.5 72 72 88.5 51.6 88.5S14.7 71.9 14.7 51.6c0-20.3 16.6-36.9 36.9-36.9z"/>
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
`;
|
|
||||||
const magnifyingClass = document.importNode(tmpl.content, true).children[0];
|
|
||||||
|
|
||||||
magnifyingClass.addEventListener('click', () => {
|
searchButton.addEventListener('click', () => {
|
||||||
if (window.innerWidth < 1024) {
|
if (window.innerWidth < 1024) {
|
||||||
this.showInput = true;
|
this.showInput = true;
|
||||||
this.updateComplete.then(() => {
|
this.updateComplete.then(() => {
|
||||||
@@ -245,7 +244,7 @@ export class RocketSearchCombobox extends LionCombobox {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return [arrowLeft, magnifyingClass];
|
return [closeButton, searchButton];
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -256,10 +255,12 @@ export class RocketSearchCombobox extends LionCombobox {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
/** @type {'none'} */
|
||||||
this.autocomplete = 'none';
|
this.autocomplete = 'none';
|
||||||
this.selectionFollowsFocus = false;
|
this.selectionFollowsFocus = false;
|
||||||
this.rotateKeyboardNavigation = false;
|
this.rotateKeyboardNavigation = false;
|
||||||
this.showInput = false;
|
this.showInput = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
customElements.define('rocket-search-combobox', RocketSearchCombobox);
|
customElements.define('rocket-search-combobox', RocketSearchCombobox);
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ export class RocketSearchPlugin {
|
|||||||
excludeLayouts = ['with-index.njk'];
|
excludeLayouts = ['with-index.njk'];
|
||||||
documents = [];
|
documents = [];
|
||||||
|
|
||||||
async setup({ config, argv }) {
|
async setup({ config, argv, eleventy }) {
|
||||||
const searcDefinitions = [
|
const searchDefinitions = [
|
||||||
{
|
{
|
||||||
name: 'mode',
|
name: 'mode',
|
||||||
alias: 'm',
|
alias: 'm',
|
||||||
@@ -24,25 +24,13 @@ export class RocketSearchPlugin {
|
|||||||
{ name: 'term', type: String, defaultOption: true, defaultValue: '' },
|
{ name: 'term', type: String, defaultOption: true, defaultValue: '' },
|
||||||
{ name: 'help', type: Boolean, description: 'See all options' },
|
{ name: 'help', type: Boolean, description: 'See all options' },
|
||||||
];
|
];
|
||||||
const searchOptions = commandLineArgs(searcDefinitions, { argv });
|
const searchOptions = commandLineArgs(searchDefinitions, { argv });
|
||||||
|
|
||||||
this.config = {
|
this.config = {
|
||||||
...config,
|
...config,
|
||||||
search: searchOptions,
|
search: searchOptions,
|
||||||
};
|
};
|
||||||
}
|
this.eleventy = eleventy;
|
||||||
|
|
||||||
async execute() {
|
|
||||||
if (this.config.command !== 'search') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const { mode } = this.config.search;
|
|
||||||
switch (mode) {
|
|
||||||
case 'search':
|
|
||||||
await this.search();
|
|
||||||
break;
|
|
||||||
/* no default */
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async inspectRenderedHtml({ html, url, layout, title, data, eleventy }) {
|
async inspectRenderedHtml({ html, url, layout, title, data, eleventy }) {
|
||||||
@@ -67,7 +55,8 @@ export class RocketSearchPlugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async search() {
|
async searchCommand() {
|
||||||
|
await this.eleventy.write();
|
||||||
await this.setupIndex();
|
await this.setupIndex();
|
||||||
|
|
||||||
process.stderr.write('\u001B[?25l'); // hide default cursor
|
process.stderr.write('\u001B[?25l'); // hide default cursor
|
||||||
|
|||||||
@@ -19,6 +19,9 @@
|
|||||||
{
|
{
|
||||||
"path": "./packages/core/tsconfig.json"
|
"path": "./packages/core/tsconfig.json"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"path": "./packages/check-html-links/tsconfig.json"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"path": "./packages/eleventy-plugin-mdjs-unified/tsconfig.json"
|
"path": "./packages/eleventy-plugin-mdjs-unified/tsconfig.json"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ const packages = [
|
|||||||
// { name: 'cli', type: 'js', environment: 'node-esm' },
|
// { name: 'cli', type: 'js', environment: 'node-esm' },
|
||||||
{ name: 'plugins-manager', type: 'js', environment: 'node-esm' },
|
{ name: 'plugins-manager', type: 'js', environment: 'node-esm' },
|
||||||
{ name: 'core', type: 'js', environment: 'node' },
|
{ name: 'core', type: 'js', environment: 'node' },
|
||||||
|
{ name: 'check-html-links', type: 'js', environment: 'node-esm' },
|
||||||
{ name: 'eleventy-plugin-mdjs-unified', type: 'js', environment: 'node' },
|
{ name: 'eleventy-plugin-mdjs-unified', type: 'js', environment: 'node' },
|
||||||
{ name: 'eleventy-rocket-nav', type: 'js', environment: 'node' },
|
{ name: 'eleventy-rocket-nav', type: 'js', environment: 'node' },
|
||||||
{ name: 'drawer', type: 'js', environment: 'browser' },
|
{ name: 'drawer', type: 'js', environment: 'browser' },
|
||||||
|
|||||||
Reference in New Issue
Block a user