metalsmith-prism 5.0.0 → 5.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +84 -38
- package/lib/index.cjs +190 -0
- package/lib/index.cjs.map +1 -0
- package/lib/index.modern.js +181 -0
- package/lib/index.modern.js.map +1 -0
- package/package.json +34 -14
- package/src/index.js +194 -0
- package/lib/index.js +0 -130
package/README.md
CHANGED
|
@@ -1,45 +1,76 @@
|
|
|
1
1
|
# metalsmith-prism
|
|
2
2
|
|
|
3
|
-
A Metalsmith plugin that **adds Prism specific HTML markup** to code sections for syntax coloring.
|
|
3
|
+
A Metalsmith plugin that **adds Prism specific HTML markup** to code sections for syntax coloring.
|
|
4
4
|
|
|
5
5
|
[](http://opensource.org/licenses/MIT)
|
|
6
6
|
[](https://npmjs.org/package/metalsmith-prism)
|
|
7
7
|
[](https://travis-ci.org/Availity/metalsmith-prism)
|
|
8
8
|
[](https://ci.appveyor.com/project/robmcguinness/metalsmith-prism)
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## Dual Module Support (ESM and CommonJS)
|
|
11
|
+
|
|
12
|
+
This plugin supports both ESM and CommonJS environments:
|
|
13
|
+
|
|
14
|
+
- ESM: `import prism from 'metalsmith-prism'`
|
|
15
|
+
- CommonJS: `const prism = require('metalsmith-prism')`
|
|
16
|
+
|
|
17
|
+
While this plugin adds all the required Prism HTML markup, **prism.css** must be included on the page to provide the syntax coloring. The plugin:
|
|
18
|
+
|
|
19
|
+
- Automatically handles language dependencies
|
|
20
|
+
- Supports HTML entity decoding
|
|
21
|
+
- Can add line numbers
|
|
22
|
+
- Works seamlessly with Markdown code blocks
|
|
23
|
+
- Supports all Prism.js languages
|
|
11
24
|
|
|
12
25
|
## Requirements
|
|
13
26
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
27
|
+
- Node `>= 18.x.x`
|
|
28
|
+
- NPM `>= 9.x.x`
|
|
29
|
+
- Metalsmith `>= v2.6.x`
|
|
17
30
|
|
|
31
|
+
## Quick Start
|
|
18
32
|
|
|
33
|
+
1. Install the plugin
|
|
34
|
+
2. Add Prism CSS to your page
|
|
35
|
+
3. Add language classes to your code blocks
|
|
36
|
+
4. Configure the plugin in your Metalsmith build
|
|
37
|
+
|
|
38
|
+
Example using all features:
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
metalsmith(__dirname).use(
|
|
42
|
+
prism({
|
|
43
|
+
decode: true, // Decode HTML entities
|
|
44
|
+
lineNumbers: true, // Show line numbers
|
|
45
|
+
preLoad: ['java'], // Pre-load language dependencies
|
|
46
|
+
})
|
|
47
|
+
);
|
|
48
|
+
```
|
|
19
49
|
|
|
20
50
|
## Installation
|
|
21
51
|
|
|
22
52
|
NPM:
|
|
53
|
+
|
|
23
54
|
```bash
|
|
24
55
|
npm install metalsmith-prism --save-dev
|
|
25
56
|
```
|
|
26
57
|
|
|
27
58
|
Yarn:
|
|
59
|
+
|
|
28
60
|
```bash
|
|
29
61
|
yarn add metalsmith-prism
|
|
30
62
|
```
|
|
31
63
|
|
|
32
64
|
## Usage
|
|
33
65
|
|
|
34
|
-
### Add Prism styles to page header.
|
|
66
|
+
### Add Prism styles to page header.
|
|
35
67
|
|
|
36
|
-
If the `linenumbers` option is set to `true`, `prism-line-numbers.css` must be added to the page.
|
|
68
|
+
If the `linenumbers` option is set to `true`, `prism-line-numbers.css` must be added to the page.
|
|
37
69
|
|
|
38
70
|
The css files can be downloaded from the [Prism website](https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript) or [use a CDN](https://prismjs.com/#basic-usage-cdn). Please refer to the [Prism documentation](https://prismjs.com/index.html) for details.
|
|
39
71
|
|
|
40
72
|
```html
|
|
41
|
-
<link href="/assets/prism.css" rel="stylesheet" />
|
|
42
|
-
<link href="/assets/prism-line-numbers.css" rel="stylesheet" />
|
|
73
|
+
<link href="/assets/prism.css" rel="stylesheet" /> <link href="/assets/prism-line-numbers.css" rel="stylesheet" />
|
|
43
74
|
```
|
|
44
75
|
|
|
45
76
|
### Add language definition to code block
|
|
@@ -50,30 +81,46 @@ The css files can be downloaded from the [Prism website](https://prismjs.com/dow
|
|
|
50
81
|
|
|
51
82
|
### Add `metalsmith-prism` plugin to metalsmith
|
|
52
83
|
|
|
84
|
+
**ESM:**
|
|
85
|
+
```js
|
|
86
|
+
import Metalsmith from 'metalsmith';
|
|
87
|
+
import prism from 'metalsmith-prism';
|
|
88
|
+
|
|
89
|
+
Metalsmith(__dirname).use(prism()).build();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**CommonJS:**
|
|
53
93
|
```js
|
|
54
|
-
const
|
|
94
|
+
const Metalsmith = require('metalsmith');
|
|
55
95
|
const prism = require('metalsmith-prism');
|
|
56
96
|
|
|
57
|
-
|
|
58
|
-
.use(prism())
|
|
59
|
-
.build();
|
|
97
|
+
Metalsmith(__dirname).use(prism()).build();
|
|
60
98
|
```
|
|
61
99
|
|
|
62
100
|
### To use with Markdown code blocks rendered by [@metalsmith/markdown](https://github.com/metalsmith/markdown)
|
|
63
101
|
|
|
102
|
+
**ESM:**
|
|
64
103
|
```js
|
|
65
|
-
|
|
104
|
+
import Metalsmith from 'metalsmith';
|
|
105
|
+
import markdown from '@metalsmith/markdown';
|
|
106
|
+
import prism from 'metalsmith-prism';
|
|
107
|
+
|
|
108
|
+
Metalsmith(__dirname).use(markdown()).use(prism()).build();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**CommonJS:**
|
|
112
|
+
```js
|
|
113
|
+
const Metalsmith = require('metalsmith');
|
|
66
114
|
const markdown = require('@metalsmith/markdown');
|
|
67
115
|
const prism = require('metalsmith-prism');
|
|
68
116
|
|
|
69
|
-
|
|
70
|
-
.use(markdown())
|
|
71
|
-
.use(prism())
|
|
72
|
-
.build();
|
|
117
|
+
Metalsmith(__dirname).use(markdown()).use(prism()).build();
|
|
73
118
|
```
|
|
74
119
|
|
|
75
120
|
## Language support
|
|
76
121
|
|
|
122
|
+
The plugin default language support includes: markup, css, clike, javascript and php.
|
|
123
|
+
|
|
77
124
|
Supports all programming languages that have a corresponding Prism.js component file. Component files are found in the [Prism.js `components` directory](https://github.com/PrismJS/prism/tree/master/components).
|
|
78
125
|
|
|
79
126
|
## Options
|
|
@@ -83,21 +130,23 @@ Supports all programming languages that have a corresponding Prism.js component
|
|
|
83
130
|
Always decode the html entities when processing language of type `markup`
|
|
84
131
|
|
|
85
132
|
```js
|
|
86
|
-
Metalsmith(__dirname)
|
|
87
|
-
|
|
88
|
-
decode: true
|
|
89
|
-
})
|
|
133
|
+
Metalsmith(__dirname).use(
|
|
134
|
+
prism({
|
|
135
|
+
decode: true,
|
|
136
|
+
})
|
|
137
|
+
);
|
|
90
138
|
```
|
|
91
139
|
|
|
92
140
|
**lineNumbers (optional)**
|
|
93
141
|
|
|
94
|
-
Adds the additional HTML markup so line numbers can be added via the line-numbers CSS.
|
|
142
|
+
Adds the additional HTML markup so line numbers can be added via the line-numbers CSS.
|
|
95
143
|
|
|
96
144
|
```javascript
|
|
97
|
-
Metalsmith(__dirname)
|
|
98
|
-
|
|
99
|
-
lineNumbers: true
|
|
100
|
-
})
|
|
145
|
+
Metalsmith(__dirname).use(
|
|
146
|
+
metalsmithPrism({
|
|
147
|
+
lineNumbers: true,
|
|
148
|
+
})
|
|
149
|
+
);
|
|
101
150
|
```
|
|
102
151
|
|
|
103
152
|
**preLoad (optional)**
|
|
@@ -107,11 +156,13 @@ Pre-loads language component(s), such that each language component registers its
|
|
|
107
156
|
Useful for loading syntax that extends other language components that are not automatically registered by Prism
|
|
108
157
|
|
|
109
158
|
```javascript
|
|
110
|
-
Metalsmith(__dirname)
|
|
111
|
-
|
|
112
|
-
preLoad: [
|
|
113
|
-
})
|
|
159
|
+
Metalsmith(__dirname).use(
|
|
160
|
+
prism({
|
|
161
|
+
preLoad: ['java', 'scala'],
|
|
162
|
+
})
|
|
163
|
+
);
|
|
114
164
|
```
|
|
165
|
+
|
|
115
166
|
## Debug
|
|
116
167
|
|
|
117
168
|
To enable debug logs, set the `DEBUG` environment variable to `metalsmith-prism`:
|
|
@@ -142,16 +193,11 @@ Add `metalsmith-prism` key to your `metalsmith.json` plugins key
|
|
|
142
193
|
}
|
|
143
194
|
}
|
|
144
195
|
```
|
|
145
|
-
## Credits
|
|
146
|
-
|
|
147
|
-
[Robert McGuinness]( https://github.com/robmcguinness) - for the initial implementation of the plugin.
|
|
148
|
-
|
|
149
196
|
|
|
197
|
+
## Credits
|
|
150
198
|
|
|
199
|
+
[Robert McGuinness](https://github.com/robmcguinness) - for the initial implementation of the plugin.
|
|
151
200
|
|
|
152
201
|
## License
|
|
153
202
|
|
|
154
203
|
Code released under [the MIT license](https://github.com/wernerglinka/metalsmith-prism/blob/main/LICENSE).
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
package/lib/index.cjs
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var cheerio = require('cheerio');
|
|
4
|
+
var path = require('path');
|
|
5
|
+
var Prism = require('prismjs');
|
|
6
|
+
var loadLanguages = require('prismjs/components/index.js');
|
|
7
|
+
var he = require('he');
|
|
8
|
+
var debugLib = require('debug');
|
|
9
|
+
|
|
10
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
11
|
+
|
|
12
|
+
var Prism__default = /*#__PURE__*/_interopDefaultLegacy(Prism);
|
|
13
|
+
var loadLanguages__default = /*#__PURE__*/_interopDefaultLegacy(loadLanguages);
|
|
14
|
+
var he__default = /*#__PURE__*/_interopDefaultLegacy(he);
|
|
15
|
+
var debugLib__default = /*#__PURE__*/_interopDefaultLegacy(debugLib);
|
|
16
|
+
|
|
17
|
+
const debug = debugLib__default["default"]('metalsmith-prism');
|
|
18
|
+
|
|
19
|
+
// Import languages from Prism's default export
|
|
20
|
+
const {
|
|
21
|
+
languages
|
|
22
|
+
} = Prism__default["default"];
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Check if a file is HTML based on its extension
|
|
26
|
+
* @param {string} filePath - Path to the file
|
|
27
|
+
* @returns {boolean} - True if the file has an HTML extension
|
|
28
|
+
*/
|
|
29
|
+
const isHTMLFile = filePath => {
|
|
30
|
+
const extension = path.extname(filePath).toLowerCase();
|
|
31
|
+
return extension === '.html' || extension === '.htm';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @typedef Options
|
|
36
|
+
* @property {boolean} [decode=false] - Whether to decode HTML entities
|
|
37
|
+
* @property {boolean} [lineNumbers=false] - Whether to add line numbers
|
|
38
|
+
* @property {string[]} [preLoad=[]] - Languages to preload
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Metalsmith plugin to highlight code syntax with PrismJS
|
|
43
|
+
*
|
|
44
|
+
* This plugin finds all code blocks in HTML files that have language-* classes
|
|
45
|
+
* and applies Prism.js syntax highlighting to them. It can also add line numbers
|
|
46
|
+
* and handle HTML entity decoding.
|
|
47
|
+
*
|
|
48
|
+
* @param {Options} [options] - Configuration options
|
|
49
|
+
* @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks
|
|
50
|
+
* @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks
|
|
51
|
+
* @param {string[]} [options.preLoad=[]] - Languages to preload before processing
|
|
52
|
+
* @returns {import('metalsmith').Plugin} - A metalsmith plugin function
|
|
53
|
+
* @example
|
|
54
|
+
* // Basic usage
|
|
55
|
+
* metalsmith.use(prism());
|
|
56
|
+
*
|
|
57
|
+
* // With options
|
|
58
|
+
* metalsmith.use(prism({
|
|
59
|
+
* decode: true,
|
|
60
|
+
* lineNumbers: true,
|
|
61
|
+
* preLoad: ['java', 'scala']
|
|
62
|
+
* }));
|
|
63
|
+
*/
|
|
64
|
+
function metalsmithPrism(options = {}) {
|
|
65
|
+
// Track loaded languages to avoid duplicate loading
|
|
66
|
+
const loadedLanguages = new Set();
|
|
67
|
+
|
|
68
|
+
// Always load PHP by default
|
|
69
|
+
debug('Loading PHP by default');
|
|
70
|
+
try {
|
|
71
|
+
loadLanguages__default["default"](['php']);
|
|
72
|
+
loadedLanguages.add('php');
|
|
73
|
+
} catch (e) {
|
|
74
|
+
debug('Failed to load PHP:', e);
|
|
75
|
+
}
|
|
76
|
+
if (options.preLoad) {
|
|
77
|
+
debug('Preloading languages:', options.preLoad);
|
|
78
|
+
options.preLoad.forEach(language => {
|
|
79
|
+
if (!loadedLanguages.has(language)) {
|
|
80
|
+
try {
|
|
81
|
+
loadLanguages__default["default"]([language]);
|
|
82
|
+
loadedLanguages.add(language);
|
|
83
|
+
debug(`Successfully preloaded language: ${language}`);
|
|
84
|
+
} catch (e) {
|
|
85
|
+
console.warn(`Failed to preload prism syntax: ${language}!`, e);
|
|
86
|
+
debug(`Error preloading language ${language}:`, e);
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
debug(`Language ${language} already loaded, skipping`);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Require optional language package
|
|
96
|
+
* @param {string} language
|
|
97
|
+
*/
|
|
98
|
+
function requireLanguage(language) {
|
|
99
|
+
if (!loadedLanguages.has(language) && !languages[language]) {
|
|
100
|
+
debug(`Loading language on-demand: ${language}`);
|
|
101
|
+
try {
|
|
102
|
+
loadLanguages__default["default"]([language]);
|
|
103
|
+
loadedLanguages.add(language);
|
|
104
|
+
debug(`Successfully loaded language: ${language}`);
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.warn(`Failed to load prism syntax: ${language}!`, e);
|
|
107
|
+
debug(`Error loading language ${language}:`, e);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Set up line numbers functionality
|
|
113
|
+
const NEW_LINE_EXP = /\n(?!$)/g;
|
|
114
|
+
let lineNumbersWrapper;
|
|
115
|
+
|
|
116
|
+
// Only set up the hook if line numbers are requested
|
|
117
|
+
if (options.lineNumbers) {
|
|
118
|
+
debug('Setting up line numbers hook');
|
|
119
|
+
Prism__default["default"].hooks.add('after-tokenize', env => {
|
|
120
|
+
const match = env.code.match(NEW_LINE_EXP);
|
|
121
|
+
const linesNum = match ? match.length + 1 : 1;
|
|
122
|
+
debug(`Counted ${linesNum} lines for line numbers`);
|
|
123
|
+
const lines = new Array(linesNum + 1).join('<span></span>');
|
|
124
|
+
lineNumbersWrapper = `<span aria-hidden="true" class="line-numbers-rows">${lines}</span>`;
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return function (files, metalsmith, done) {
|
|
128
|
+
debug('Starting metalsmith-prism plugin');
|
|
129
|
+
debug('Options:', options);
|
|
130
|
+
setImmediate(done);
|
|
131
|
+
Object.keys(files).forEach(file => {
|
|
132
|
+
if (!isHTMLFile(file)) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
debug(`Processing HTML file: ${file}`);
|
|
136
|
+
const contents = files[file].contents.toString();
|
|
137
|
+
const $ = cheerio.load(contents, {
|
|
138
|
+
decodeEntities: false
|
|
139
|
+
});
|
|
140
|
+
let highlighted = false;
|
|
141
|
+
const code = $('code');
|
|
142
|
+
if (!code.length) {
|
|
143
|
+
debug(`No code blocks found in ${file}`);
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
debug(`Found ${code.length} code blocks in ${file}`);
|
|
147
|
+
code.each(function () {
|
|
148
|
+
const $this = $(this);
|
|
149
|
+
const className = $this.attr('class') || '';
|
|
150
|
+
const targets = className.split('language-');
|
|
151
|
+
let addLineNumbers = false;
|
|
152
|
+
if (targets.length > 1) {
|
|
153
|
+
const $pre = $this.parent('pre');
|
|
154
|
+
if ($pre) {
|
|
155
|
+
// Copy className to <pre> container
|
|
156
|
+
$pre.addClass(className);
|
|
157
|
+
if (options.lineNumbers) {
|
|
158
|
+
$pre.addClass('line-numbers');
|
|
159
|
+
addLineNumbers = true;
|
|
160
|
+
debug('Adding line numbers');
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
highlighted = true;
|
|
164
|
+
let language = targets[1];
|
|
165
|
+
debug(`Detected language: ${language}`);
|
|
166
|
+
requireLanguage(language);
|
|
167
|
+
if (!languages[language]) {
|
|
168
|
+
debug(`Language ${language} not available, falling back to markup`);
|
|
169
|
+
language = 'markup';
|
|
170
|
+
}
|
|
171
|
+
const html = language === 'markup' && !options.decode ? $this.html() : he__default["default"].decode($this.html());
|
|
172
|
+
debug(`HTML decoding ${options.decode ? 'applied' : 'not applied'} for language ${language}`);
|
|
173
|
+
debug(`Highlighting code with language: ${language}`);
|
|
174
|
+
const highlightedCode = Prism__default["default"].highlight(html, languages[language]);
|
|
175
|
+
$this.html(addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
if (highlighted) {
|
|
179
|
+
debug(`Updating contents of ${file} with highlighted code`);
|
|
180
|
+
files[file].contents = Buffer.from($.html());
|
|
181
|
+
} else {
|
|
182
|
+
debug(`No code was highlighted in ${file}`);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
debug('Completed metalsmith-prism plugin');
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = metalsmithPrism;
|
|
190
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/index.js"],"sourcesContent":["import { load } from 'cheerio';\nimport { extname } from 'path';\nimport Prism from 'prismjs';\nimport loadLanguages from 'prismjs/components/index.js';\nimport he from 'he';\nimport debugLib from 'debug';\n\nconst debug = debugLib('metalsmith-prism');\n\n// Import languages from Prism's default export\nconst { languages } = Prism;\n\n/**\n * Check if a file is HTML based on its extension\n * @param {string} filePath - Path to the file\n * @returns {boolean} - True if the file has an HTML extension\n */\nconst isHTMLFile = (filePath) => {\n const extension = extname(filePath).toLowerCase();\n return extension === '.html' || extension === '.htm';\n};\n\n/**\n * @typedef Options\n * @property {boolean} [decode=false] - Whether to decode HTML entities\n * @property {boolean} [lineNumbers=false] - Whether to add line numbers\n * @property {string[]} [preLoad=[]] - Languages to preload\n */\n\n/**\n * Metalsmith plugin to highlight code syntax with PrismJS\n * \n * This plugin finds all code blocks in HTML files that have language-* classes\n * and applies Prism.js syntax highlighting to them. It can also add line numbers\n * and handle HTML entity decoding.\n *\n * @param {Options} [options] - Configuration options\n * @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks\n * @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks\n * @param {string[]} [options.preLoad=[]] - Languages to preload before processing\n * @returns {import('metalsmith').Plugin} - A metalsmith plugin function\n * @example\n * // Basic usage\n * metalsmith.use(prism());\n * \n * // With options\n * metalsmith.use(prism({\n * decode: true,\n * lineNumbers: true,\n * preLoad: ['java', 'scala']\n * }));\n */\nfunction metalsmithPrism(options = {}) {\n // Track loaded languages to avoid duplicate loading\n const loadedLanguages = new Set();\n \n // Always load PHP by default\n debug('Loading PHP by default');\n try {\n loadLanguages(['php']);\n loadedLanguages.add('php');\n } catch (e) {\n debug('Failed to load PHP:', e);\n }\n \n if (options.preLoad) {\n debug('Preloading languages:', options.preLoad);\n options.preLoad.forEach((language) => {\n if (!loadedLanguages.has(language)) {\n try {\n loadLanguages([language]);\n loadedLanguages.add(language);\n debug(`Successfully preloaded language: ${language}`);\n } catch (e) {\n console.warn(`Failed to preload prism syntax: ${language}!`, e);\n debug(`Error preloading language ${language}:`, e);\n }\n } else {\n debug(`Language ${language} already loaded, skipping`);\n }\n });\n }\n\n /**\n * Require optional language package\n * @param {string} language\n */\n function requireLanguage(language) {\n if (!loadedLanguages.has(language) && !languages[language]) {\n debug(`Loading language on-demand: ${language}`);\n try {\n loadLanguages([language]);\n loadedLanguages.add(language);\n debug(`Successfully loaded language: ${language}`);\n } catch (e) {\n console.warn(`Failed to load prism syntax: ${language}!`, e);\n debug(`Error loading language ${language}:`, e);\n }\n }\n }\n\n // Set up line numbers functionality\n const NEW_LINE_EXP = /\\n(?!$)/g;\n let lineNumbersWrapper;\n\n // Only set up the hook if line numbers are requested\n if (options.lineNumbers) {\n debug('Setting up line numbers hook');\n Prism.hooks.add('after-tokenize', (env) => {\n const match = env.code.match(NEW_LINE_EXP);\n const linesNum = match ? match.length + 1 : 1;\n debug(`Counted ${linesNum} lines for line numbers`);\n const lines = new Array(linesNum + 1).join('<span></span>');\n lineNumbersWrapper = `<span aria-hidden=\"true\" class=\"line-numbers-rows\">${lines}</span>`;\n });\n }\n\n return function (files, metalsmith, done) {\n debug('Starting metalsmith-prism plugin');\n debug('Options:', options);\n \n setImmediate(done);\n\n Object.keys(files).forEach((file) => {\n if (!isHTMLFile(file)) {\n return;\n }\n\n debug(`Processing HTML file: ${file}`);\n const contents = files[file].contents.toString();\n const $ = load(contents, { decodeEntities: false });\n let highlighted = false;\n const code = $('code');\n\n if (!code.length) {\n debug(`No code blocks found in ${file}`);\n return;\n }\n \n debug(`Found ${code.length} code blocks in ${file}`);\n\n code.each(function () {\n const $this = $(this);\n\n const className = $this.attr('class') || '';\n const targets = className.split('language-');\n let addLineNumbers = false;\n\n if (targets.length > 1) {\n const $pre = $this.parent('pre');\n\n if ($pre) {\n // Copy className to <pre> container\n $pre.addClass(className);\n\n if (options.lineNumbers) {\n $pre.addClass('line-numbers');\n addLineNumbers = true;\n debug('Adding line numbers');\n }\n }\n\n highlighted = true;\n let language = targets[1];\n debug(`Detected language: ${language}`);\n requireLanguage(language);\n\n if (!languages[language]) {\n debug(`Language ${language} not available, falling back to markup`);\n language = 'markup';\n }\n\n const html = language === 'markup' && !options.decode ? $this.html() : he.decode($this.html());\n debug(`HTML decoding ${options.decode ? 'applied' : 'not applied'} for language ${language}`);\n\n debug(`Highlighting code with language: ${language}`);\n const highlightedCode = Prism.highlight(html, languages[language]);\n $this.html(addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode);\n }\n });\n\n if (highlighted) {\n debug(`Updating contents of ${file} with highlighted code`);\n files[file].contents = Buffer.from($.html());\n } else {\n debug(`No code was highlighted in ${file}`);\n }\n });\n \n debug('Completed metalsmith-prism plugin');\n };\n}\n\nexport { metalsmithPrism as default };"],"names":["debug","debugLib","languages","Prism","isHTMLFile","filePath","extension","extname","toLowerCase","metalsmithPrism","options","loadedLanguages","Set","loadLanguages","add","e","preLoad","forEach","language","has","console","warn","requireLanguage","NEW_LINE_EXP","lineNumbersWrapper","lineNumbers","hooks","env","match","code","linesNum","length","lines","Array","join","files","metalsmith","done","setImmediate","Object","keys","file","contents","toString","$","load","decodeEntities","highlighted","each","$this","className","attr","targets","split","addLineNumbers","$pre","parent","addClass","html","decode","he","highlightedCode","highlight","Buffer","from"],"mappings":"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"}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { load } from 'cheerio';
|
|
2
|
+
import { extname } from 'path';
|
|
3
|
+
import Prism from 'prismjs';
|
|
4
|
+
import loadLanguages from 'prismjs/components/index.js';
|
|
5
|
+
import he from 'he';
|
|
6
|
+
import debugLib from 'debug';
|
|
7
|
+
|
|
8
|
+
const debug = debugLib('metalsmith-prism');
|
|
9
|
+
|
|
10
|
+
// Import languages from Prism's default export
|
|
11
|
+
const {
|
|
12
|
+
languages
|
|
13
|
+
} = Prism;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a file is HTML based on its extension
|
|
17
|
+
* @param {string} filePath - Path to the file
|
|
18
|
+
* @returns {boolean} - True if the file has an HTML extension
|
|
19
|
+
*/
|
|
20
|
+
const isHTMLFile = filePath => {
|
|
21
|
+
const extension = extname(filePath).toLowerCase();
|
|
22
|
+
return extension === '.html' || extension === '.htm';
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @typedef Options
|
|
27
|
+
* @property {boolean} [decode=false] - Whether to decode HTML entities
|
|
28
|
+
* @property {boolean} [lineNumbers=false] - Whether to add line numbers
|
|
29
|
+
* @property {string[]} [preLoad=[]] - Languages to preload
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Metalsmith plugin to highlight code syntax with PrismJS
|
|
34
|
+
*
|
|
35
|
+
* This plugin finds all code blocks in HTML files that have language-* classes
|
|
36
|
+
* and applies Prism.js syntax highlighting to them. It can also add line numbers
|
|
37
|
+
* and handle HTML entity decoding.
|
|
38
|
+
*
|
|
39
|
+
* @param {Options} [options] - Configuration options
|
|
40
|
+
* @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks
|
|
41
|
+
* @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks
|
|
42
|
+
* @param {string[]} [options.preLoad=[]] - Languages to preload before processing
|
|
43
|
+
* @returns {import('metalsmith').Plugin} - A metalsmith plugin function
|
|
44
|
+
* @example
|
|
45
|
+
* // Basic usage
|
|
46
|
+
* metalsmith.use(prism());
|
|
47
|
+
*
|
|
48
|
+
* // With options
|
|
49
|
+
* metalsmith.use(prism({
|
|
50
|
+
* decode: true,
|
|
51
|
+
* lineNumbers: true,
|
|
52
|
+
* preLoad: ['java', 'scala']
|
|
53
|
+
* }));
|
|
54
|
+
*/
|
|
55
|
+
function metalsmithPrism(options = {}) {
|
|
56
|
+
// Track loaded languages to avoid duplicate loading
|
|
57
|
+
const loadedLanguages = new Set();
|
|
58
|
+
|
|
59
|
+
// Always load PHP by default
|
|
60
|
+
debug('Loading PHP by default');
|
|
61
|
+
try {
|
|
62
|
+
loadLanguages(['php']);
|
|
63
|
+
loadedLanguages.add('php');
|
|
64
|
+
} catch (e) {
|
|
65
|
+
debug('Failed to load PHP:', e);
|
|
66
|
+
}
|
|
67
|
+
if (options.preLoad) {
|
|
68
|
+
debug('Preloading languages:', options.preLoad);
|
|
69
|
+
options.preLoad.forEach(language => {
|
|
70
|
+
if (!loadedLanguages.has(language)) {
|
|
71
|
+
try {
|
|
72
|
+
loadLanguages([language]);
|
|
73
|
+
loadedLanguages.add(language);
|
|
74
|
+
debug(`Successfully preloaded language: ${language}`);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
console.warn(`Failed to preload prism syntax: ${language}!`, e);
|
|
77
|
+
debug(`Error preloading language ${language}:`, e);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
debug(`Language ${language} already loaded, skipping`);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Require optional language package
|
|
87
|
+
* @param {string} language
|
|
88
|
+
*/
|
|
89
|
+
function requireLanguage(language) {
|
|
90
|
+
if (!loadedLanguages.has(language) && !languages[language]) {
|
|
91
|
+
debug(`Loading language on-demand: ${language}`);
|
|
92
|
+
try {
|
|
93
|
+
loadLanguages([language]);
|
|
94
|
+
loadedLanguages.add(language);
|
|
95
|
+
debug(`Successfully loaded language: ${language}`);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
console.warn(`Failed to load prism syntax: ${language}!`, e);
|
|
98
|
+
debug(`Error loading language ${language}:`, e);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Set up line numbers functionality
|
|
104
|
+
const NEW_LINE_EXP = /\n(?!$)/g;
|
|
105
|
+
let lineNumbersWrapper;
|
|
106
|
+
|
|
107
|
+
// Only set up the hook if line numbers are requested
|
|
108
|
+
if (options.lineNumbers) {
|
|
109
|
+
debug('Setting up line numbers hook');
|
|
110
|
+
Prism.hooks.add('after-tokenize', env => {
|
|
111
|
+
const match = env.code.match(NEW_LINE_EXP);
|
|
112
|
+
const linesNum = match ? match.length + 1 : 1;
|
|
113
|
+
debug(`Counted ${linesNum} lines for line numbers`);
|
|
114
|
+
const lines = new Array(linesNum + 1).join('<span></span>');
|
|
115
|
+
lineNumbersWrapper = `<span aria-hidden="true" class="line-numbers-rows">${lines}</span>`;
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return function (files, metalsmith, done) {
|
|
119
|
+
debug('Starting metalsmith-prism plugin');
|
|
120
|
+
debug('Options:', options);
|
|
121
|
+
setImmediate(done);
|
|
122
|
+
Object.keys(files).forEach(file => {
|
|
123
|
+
if (!isHTMLFile(file)) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
debug(`Processing HTML file: ${file}`);
|
|
127
|
+
const contents = files[file].contents.toString();
|
|
128
|
+
const $ = load(contents, {
|
|
129
|
+
decodeEntities: false
|
|
130
|
+
});
|
|
131
|
+
let highlighted = false;
|
|
132
|
+
const code = $('code');
|
|
133
|
+
if (!code.length) {
|
|
134
|
+
debug(`No code blocks found in ${file}`);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
debug(`Found ${code.length} code blocks in ${file}`);
|
|
138
|
+
code.each(function () {
|
|
139
|
+
const $this = $(this);
|
|
140
|
+
const className = $this.attr('class') || '';
|
|
141
|
+
const targets = className.split('language-');
|
|
142
|
+
let addLineNumbers = false;
|
|
143
|
+
if (targets.length > 1) {
|
|
144
|
+
const $pre = $this.parent('pre');
|
|
145
|
+
if ($pre) {
|
|
146
|
+
// Copy className to <pre> container
|
|
147
|
+
$pre.addClass(className);
|
|
148
|
+
if (options.lineNumbers) {
|
|
149
|
+
$pre.addClass('line-numbers');
|
|
150
|
+
addLineNumbers = true;
|
|
151
|
+
debug('Adding line numbers');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
highlighted = true;
|
|
155
|
+
let language = targets[1];
|
|
156
|
+
debug(`Detected language: ${language}`);
|
|
157
|
+
requireLanguage(language);
|
|
158
|
+
if (!languages[language]) {
|
|
159
|
+
debug(`Language ${language} not available, falling back to markup`);
|
|
160
|
+
language = 'markup';
|
|
161
|
+
}
|
|
162
|
+
const html = language === 'markup' && !options.decode ? $this.html() : he.decode($this.html());
|
|
163
|
+
debug(`HTML decoding ${options.decode ? 'applied' : 'not applied'} for language ${language}`);
|
|
164
|
+
debug(`Highlighting code with language: ${language}`);
|
|
165
|
+
const highlightedCode = Prism.highlight(html, languages[language]);
|
|
166
|
+
$this.html(addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
if (highlighted) {
|
|
170
|
+
debug(`Updating contents of ${file} with highlighted code`);
|
|
171
|
+
files[file].contents = Buffer.from($.html());
|
|
172
|
+
} else {
|
|
173
|
+
debug(`No code was highlighted in ${file}`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
debug('Completed metalsmith-prism plugin');
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export { metalsmithPrism as default };
|
|
181
|
+
//# sourceMappingURL=index.modern.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.modern.js","sources":["../src/index.js"],"sourcesContent":["import { load } from 'cheerio';\nimport { extname } from 'path';\nimport Prism from 'prismjs';\nimport loadLanguages from 'prismjs/components/index.js';\nimport he from 'he';\nimport debugLib from 'debug';\n\nconst debug = debugLib('metalsmith-prism');\n\n// Import languages from Prism's default export\nconst { languages } = Prism;\n\n/**\n * Check if a file is HTML based on its extension\n * @param {string} filePath - Path to the file\n * @returns {boolean} - True if the file has an HTML extension\n */\nconst isHTMLFile = (filePath) => {\n const extension = extname(filePath).toLowerCase();\n return extension === '.html' || extension === '.htm';\n};\n\n/**\n * @typedef Options\n * @property {boolean} [decode=false] - Whether to decode HTML entities\n * @property {boolean} [lineNumbers=false] - Whether to add line numbers\n * @property {string[]} [preLoad=[]] - Languages to preload\n */\n\n/**\n * Metalsmith plugin to highlight code syntax with PrismJS\n * \n * This plugin finds all code blocks in HTML files that have language-* classes\n * and applies Prism.js syntax highlighting to them. It can also add line numbers\n * and handle HTML entity decoding.\n *\n * @param {Options} [options] - Configuration options\n * @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks\n * @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks\n * @param {string[]} [options.preLoad=[]] - Languages to preload before processing\n * @returns {import('metalsmith').Plugin} - A metalsmith plugin function\n * @example\n * // Basic usage\n * metalsmith.use(prism());\n * \n * // With options\n * metalsmith.use(prism({\n * decode: true,\n * lineNumbers: true,\n * preLoad: ['java', 'scala']\n * }));\n */\nfunction metalsmithPrism(options = {}) {\n // Track loaded languages to avoid duplicate loading\n const loadedLanguages = new Set();\n \n // Always load PHP by default\n debug('Loading PHP by default');\n try {\n loadLanguages(['php']);\n loadedLanguages.add('php');\n } catch (e) {\n debug('Failed to load PHP:', e);\n }\n \n if (options.preLoad) {\n debug('Preloading languages:', options.preLoad);\n options.preLoad.forEach((language) => {\n if (!loadedLanguages.has(language)) {\n try {\n loadLanguages([language]);\n loadedLanguages.add(language);\n debug(`Successfully preloaded language: ${language}`);\n } catch (e) {\n console.warn(`Failed to preload prism syntax: ${language}!`, e);\n debug(`Error preloading language ${language}:`, e);\n }\n } else {\n debug(`Language ${language} already loaded, skipping`);\n }\n });\n }\n\n /**\n * Require optional language package\n * @param {string} language\n */\n function requireLanguage(language) {\n if (!loadedLanguages.has(language) && !languages[language]) {\n debug(`Loading language on-demand: ${language}`);\n try {\n loadLanguages([language]);\n loadedLanguages.add(language);\n debug(`Successfully loaded language: ${language}`);\n } catch (e) {\n console.warn(`Failed to load prism syntax: ${language}!`, e);\n debug(`Error loading language ${language}:`, e);\n }\n }\n }\n\n // Set up line numbers functionality\n const NEW_LINE_EXP = /\\n(?!$)/g;\n let lineNumbersWrapper;\n\n // Only set up the hook if line numbers are requested\n if (options.lineNumbers) {\n debug('Setting up line numbers hook');\n Prism.hooks.add('after-tokenize', (env) => {\n const match = env.code.match(NEW_LINE_EXP);\n const linesNum = match ? match.length + 1 : 1;\n debug(`Counted ${linesNum} lines for line numbers`);\n const lines = new Array(linesNum + 1).join('<span></span>');\n lineNumbersWrapper = `<span aria-hidden=\"true\" class=\"line-numbers-rows\">${lines}</span>`;\n });\n }\n\n return function (files, metalsmith, done) {\n debug('Starting metalsmith-prism plugin');\n debug('Options:', options);\n \n setImmediate(done);\n\n Object.keys(files).forEach((file) => {\n if (!isHTMLFile(file)) {\n return;\n }\n\n debug(`Processing HTML file: ${file}`);\n const contents = files[file].contents.toString();\n const $ = load(contents, { decodeEntities: false });\n let highlighted = false;\n const code = $('code');\n\n if (!code.length) {\n debug(`No code blocks found in ${file}`);\n return;\n }\n \n debug(`Found ${code.length} code blocks in ${file}`);\n\n code.each(function () {\n const $this = $(this);\n\n const className = $this.attr('class') || '';\n const targets = className.split('language-');\n let addLineNumbers = false;\n\n if (targets.length > 1) {\n const $pre = $this.parent('pre');\n\n if ($pre) {\n // Copy className to <pre> container\n $pre.addClass(className);\n\n if (options.lineNumbers) {\n $pre.addClass('line-numbers');\n addLineNumbers = true;\n debug('Adding line numbers');\n }\n }\n\n highlighted = true;\n let language = targets[1];\n debug(`Detected language: ${language}`);\n requireLanguage(language);\n\n if (!languages[language]) {\n debug(`Language ${language} not available, falling back to markup`);\n language = 'markup';\n }\n\n const html = language === 'markup' && !options.decode ? $this.html() : he.decode($this.html());\n debug(`HTML decoding ${options.decode ? 'applied' : 'not applied'} for language ${language}`);\n\n debug(`Highlighting code with language: ${language}`);\n const highlightedCode = Prism.highlight(html, languages[language]);\n $this.html(addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode);\n }\n });\n\n if (highlighted) {\n debug(`Updating contents of ${file} with highlighted code`);\n files[file].contents = Buffer.from($.html());\n } else {\n debug(`No code was highlighted in ${file}`);\n }\n });\n \n debug('Completed metalsmith-prism plugin');\n };\n}\n\nexport { metalsmithPrism as default };"],"names":["debug","debugLib","languages","Prism","isHTMLFile","filePath","extension","extname","toLowerCase","metalsmithPrism","options","loadedLanguages","Set","loadLanguages","add","e","preLoad","forEach","language","has","console","warn","requireLanguage","NEW_LINE_EXP","lineNumbersWrapper","lineNumbers","hooks","env","match","code","linesNum","length","lines","Array","join","files","metalsmith","done","setImmediate","Object","keys","file","contents","toString","$","load","decodeEntities","highlighted","each","$this","className","attr","targets","split","addLineNumbers","$pre","parent","addClass","html","decode","he","highlightedCode","highlight","Buffer","from"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "metalsmith-prism",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.2",
|
|
4
4
|
"description": "Syntax highlighting for Metalsmith HTML templates using Prism.js",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"main": "lib/index.
|
|
7
|
-
"
|
|
6
|
+
"main": "./lib/index.cjs",
|
|
7
|
+
"module": "./lib/index.modern.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
"import": "./lib/index.modern.js",
|
|
10
|
+
"require": "./lib/index.cjs"
|
|
11
|
+
},
|
|
8
12
|
"engines": {
|
|
9
13
|
"node": ">= 18.0.0"
|
|
10
14
|
},
|
|
11
15
|
"scripts": {
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
16
|
+
"coverage": "npm test && c8 report --reporter=text-lcov > ./coverage.info",
|
|
17
|
+
"build": "microbundle --entry src/index.js --output lib/index.js --target node -f esm,cjs --strict --generateTypes=false",
|
|
18
|
+
"prepublishOnly": "npm run build",
|
|
19
|
+
"format": "prettier --write \"**/*.{js,json}\"",
|
|
20
|
+
"format:check": "prettier --list-different \"**/*.{yml,md,js,json}\"",
|
|
21
|
+
"lint": "eslint --fix .",
|
|
22
|
+
"lint:check": "eslint --fix-dry-run .",
|
|
23
|
+
"release": "npm run build && node ./release.js",
|
|
24
|
+
"release:check": "npm run build && node ./release.js --dry-run",
|
|
25
|
+
"test": "c8 mocha test/index.mjs test/additional.mjs test/comprehensive.mjs -t 15000",
|
|
26
|
+
"test:debug": "DEBUG=metalsmith-optimize-html:* npm test",
|
|
27
|
+
"test:e2e": "serve -l 3000 test/fixtures"
|
|
15
28
|
},
|
|
16
29
|
"repository": {
|
|
17
30
|
"type": "git",
|
|
@@ -24,6 +37,7 @@
|
|
|
24
37
|
"highlighting"
|
|
25
38
|
],
|
|
26
39
|
"files": [
|
|
40
|
+
"src",
|
|
27
41
|
"lib",
|
|
28
42
|
"README.md"
|
|
29
43
|
],
|
|
@@ -35,17 +49,23 @@
|
|
|
35
49
|
"homepage": "https://github.com/wernerglinka/metalsmith-prism",
|
|
36
50
|
"dependencies": {
|
|
37
51
|
"cheerio": "^1.0.0",
|
|
38
|
-
"debug": "^4.
|
|
52
|
+
"debug": "^4.4.0",
|
|
53
|
+
"dotenv": "^16.4.7",
|
|
39
54
|
"he": "^1.2.0",
|
|
40
55
|
"metalsmith": "^2.6.3",
|
|
41
|
-
"prismjs": "^1.
|
|
56
|
+
"prismjs": "^1.30.0"
|
|
42
57
|
},
|
|
43
58
|
"devDependencies": {
|
|
44
|
-
"
|
|
45
|
-
"
|
|
46
|
-
"
|
|
47
|
-
"eslint
|
|
48
|
-
"
|
|
49
|
-
"prettier": "^
|
|
59
|
+
"auto-changelog": "^2.5.0",
|
|
60
|
+
"c8": "^10.1.3",
|
|
61
|
+
"chai": "^5.2.0",
|
|
62
|
+
"eslint": "^9.22.0",
|
|
63
|
+
"eslint-config-prettier": "^10.1.1",
|
|
64
|
+
"eslint-plugin-prettier": "^5.2.3",
|
|
65
|
+
"globals": "^16.0.0",
|
|
66
|
+
"microbundle": "^0.15.1",
|
|
67
|
+
"mocha": "^11.1.0",
|
|
68
|
+
"prettier": "^3.5.3",
|
|
69
|
+
"release-it": "^18.1.2"
|
|
50
70
|
}
|
|
51
71
|
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { load } from 'cheerio';
|
|
2
|
+
import { extname } from 'path';
|
|
3
|
+
import Prism from 'prismjs';
|
|
4
|
+
import loadLanguages from 'prismjs/components/index.js';
|
|
5
|
+
import he from 'he';
|
|
6
|
+
import debugLib from 'debug';
|
|
7
|
+
|
|
8
|
+
const debug = debugLib('metalsmith-prism');
|
|
9
|
+
|
|
10
|
+
// Import languages from Prism's default export
|
|
11
|
+
const { languages } = Prism;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Check if a file is HTML based on its extension
|
|
15
|
+
* @param {string} filePath - Path to the file
|
|
16
|
+
* @returns {boolean} - True if the file has an HTML extension
|
|
17
|
+
*/
|
|
18
|
+
const isHTMLFile = (filePath) => {
|
|
19
|
+
const extension = extname(filePath).toLowerCase();
|
|
20
|
+
return extension === '.html' || extension === '.htm';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @typedef Options
|
|
25
|
+
* @property {boolean} [decode=false] - Whether to decode HTML entities
|
|
26
|
+
* @property {boolean} [lineNumbers=false] - Whether to add line numbers
|
|
27
|
+
* @property {string[]} [preLoad=[]] - Languages to preload
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Metalsmith plugin to highlight code syntax with PrismJS
|
|
32
|
+
*
|
|
33
|
+
* This plugin finds all code blocks in HTML files that have language-* classes
|
|
34
|
+
* and applies Prism.js syntax highlighting to them. It can also add line numbers
|
|
35
|
+
* and handle HTML entity decoding.
|
|
36
|
+
*
|
|
37
|
+
* @param {Options} [options] - Configuration options
|
|
38
|
+
* @param {boolean} [options.decode=false] - Whether to decode HTML entities in code blocks
|
|
39
|
+
* @param {boolean} [options.lineNumbers=false] - Whether to add line numbers to code blocks
|
|
40
|
+
* @param {string[]} [options.preLoad=[]] - Languages to preload before processing
|
|
41
|
+
* @returns {import('metalsmith').Plugin} - A metalsmith plugin function
|
|
42
|
+
* @example
|
|
43
|
+
* // Basic usage
|
|
44
|
+
* metalsmith.use(prism());
|
|
45
|
+
*
|
|
46
|
+
* // With options
|
|
47
|
+
* metalsmith.use(prism({
|
|
48
|
+
* decode: true,
|
|
49
|
+
* lineNumbers: true,
|
|
50
|
+
* preLoad: ['java', 'scala']
|
|
51
|
+
* }));
|
|
52
|
+
*/
|
|
53
|
+
function metalsmithPrism(options = {}) {
|
|
54
|
+
// Track loaded languages to avoid duplicate loading
|
|
55
|
+
const loadedLanguages = new Set();
|
|
56
|
+
|
|
57
|
+
// Always load PHP by default
|
|
58
|
+
debug('Loading PHP by default');
|
|
59
|
+
try {
|
|
60
|
+
loadLanguages(['php']);
|
|
61
|
+
loadedLanguages.add('php');
|
|
62
|
+
} catch (e) {
|
|
63
|
+
debug('Failed to load PHP:', e);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (options.preLoad) {
|
|
67
|
+
debug('Preloading languages:', options.preLoad);
|
|
68
|
+
options.preLoad.forEach((language) => {
|
|
69
|
+
if (!loadedLanguages.has(language)) {
|
|
70
|
+
try {
|
|
71
|
+
loadLanguages([language]);
|
|
72
|
+
loadedLanguages.add(language);
|
|
73
|
+
debug(`Successfully preloaded language: ${language}`);
|
|
74
|
+
} catch (e) {
|
|
75
|
+
console.warn(`Failed to preload prism syntax: ${language}!`, e);
|
|
76
|
+
debug(`Error preloading language ${language}:`, e);
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
debug(`Language ${language} already loaded, skipping`);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Require optional language package
|
|
86
|
+
* @param {string} language
|
|
87
|
+
*/
|
|
88
|
+
function requireLanguage(language) {
|
|
89
|
+
if (!loadedLanguages.has(language) && !languages[language]) {
|
|
90
|
+
debug(`Loading language on-demand: ${language}`);
|
|
91
|
+
try {
|
|
92
|
+
loadLanguages([language]);
|
|
93
|
+
loadedLanguages.add(language);
|
|
94
|
+
debug(`Successfully loaded language: ${language}`);
|
|
95
|
+
} catch (e) {
|
|
96
|
+
console.warn(`Failed to load prism syntax: ${language}!`, e);
|
|
97
|
+
debug(`Error loading language ${language}:`, e);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Set up line numbers functionality
|
|
103
|
+
const NEW_LINE_EXP = /\n(?!$)/g;
|
|
104
|
+
let lineNumbersWrapper;
|
|
105
|
+
|
|
106
|
+
// Only set up the hook if line numbers are requested
|
|
107
|
+
if (options.lineNumbers) {
|
|
108
|
+
debug('Setting up line numbers hook');
|
|
109
|
+
Prism.hooks.add('after-tokenize', (env) => {
|
|
110
|
+
const match = env.code.match(NEW_LINE_EXP);
|
|
111
|
+
const linesNum = match ? match.length + 1 : 1;
|
|
112
|
+
debug(`Counted ${linesNum} lines for line numbers`);
|
|
113
|
+
const lines = new Array(linesNum + 1).join('<span></span>');
|
|
114
|
+
lineNumbersWrapper = `<span aria-hidden="true" class="line-numbers-rows">${lines}</span>`;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return function (files, metalsmith, done) {
|
|
119
|
+
debug('Starting metalsmith-prism plugin');
|
|
120
|
+
debug('Options:', options);
|
|
121
|
+
|
|
122
|
+
setImmediate(done);
|
|
123
|
+
|
|
124
|
+
Object.keys(files).forEach((file) => {
|
|
125
|
+
if (!isHTMLFile(file)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
debug(`Processing HTML file: ${file}`);
|
|
130
|
+
const contents = files[file].contents.toString();
|
|
131
|
+
const $ = load(contents, { decodeEntities: false });
|
|
132
|
+
let highlighted = false;
|
|
133
|
+
const code = $('code');
|
|
134
|
+
|
|
135
|
+
if (!code.length) {
|
|
136
|
+
debug(`No code blocks found in ${file}`);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
debug(`Found ${code.length} code blocks in ${file}`);
|
|
141
|
+
|
|
142
|
+
code.each(function () {
|
|
143
|
+
const $this = $(this);
|
|
144
|
+
|
|
145
|
+
const className = $this.attr('class') || '';
|
|
146
|
+
const targets = className.split('language-');
|
|
147
|
+
let addLineNumbers = false;
|
|
148
|
+
|
|
149
|
+
if (targets.length > 1) {
|
|
150
|
+
const $pre = $this.parent('pre');
|
|
151
|
+
|
|
152
|
+
if ($pre) {
|
|
153
|
+
// Copy className to <pre> container
|
|
154
|
+
$pre.addClass(className);
|
|
155
|
+
|
|
156
|
+
if (options.lineNumbers) {
|
|
157
|
+
$pre.addClass('line-numbers');
|
|
158
|
+
addLineNumbers = true;
|
|
159
|
+
debug('Adding line numbers');
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
highlighted = true;
|
|
164
|
+
let language = targets[1];
|
|
165
|
+
debug(`Detected language: ${language}`);
|
|
166
|
+
requireLanguage(language);
|
|
167
|
+
|
|
168
|
+
if (!languages[language]) {
|
|
169
|
+
debug(`Language ${language} not available, falling back to markup`);
|
|
170
|
+
language = 'markup';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const html = language === 'markup' && !options.decode ? $this.html() : he.decode($this.html());
|
|
174
|
+
debug(`HTML decoding ${options.decode ? 'applied' : 'not applied'} for language ${language}`);
|
|
175
|
+
|
|
176
|
+
debug(`Highlighting code with language: ${language}`);
|
|
177
|
+
const highlightedCode = Prism.highlight(html, languages[language]);
|
|
178
|
+
$this.html(addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (highlighted) {
|
|
183
|
+
debug(`Updating contents of ${file} with highlighted code`);
|
|
184
|
+
files[file].contents = Buffer.from($.html());
|
|
185
|
+
} else {
|
|
186
|
+
debug(`No code was highlighted in ${file}`);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
debug('Completed metalsmith-prism plugin');
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export { metalsmithPrism as default };
|
package/lib/index.js
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { load } from 'cheerio';
|
|
2
|
-
import { extname } from 'path';
|
|
3
|
-
import Prism from 'prismjs';
|
|
4
|
-
import loadLanguages from 'prismjs/components/index.js';
|
|
5
|
-
import he from 'he';
|
|
6
|
-
|
|
7
|
-
// Import languages from Prism's default export
|
|
8
|
-
const { languages } = Prism;
|
|
9
|
-
|
|
10
|
-
// Preload PHP
|
|
11
|
-
loadLanguages( [ 'php' ] );
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Check if a file is HTML
|
|
15
|
-
* @param {string} filePath
|
|
16
|
-
* @returns {boolean}
|
|
17
|
-
*/
|
|
18
|
-
const isHTMLFile = ( filePath ) => {
|
|
19
|
-
return /\.html|\.htm/.test( extname( filePath ) );
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @typedef Options
|
|
24
|
-
* @property {boolean} [decode=false] - Whether to decode HTML entities
|
|
25
|
-
* @property {boolean} [lineNumbers=false] - Whether to add line numbers
|
|
26
|
-
* @property {string[]} [preLoad=[]] - Languages to preload
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Metalsmith plugin to highlight code syntax with PrismJS
|
|
31
|
-
*
|
|
32
|
-
* @param {Options} [options]
|
|
33
|
-
* @returns {import('metalsmith').Plugin}
|
|
34
|
-
*/
|
|
35
|
-
function metalsmithPrism( options = {} ) {
|
|
36
|
-
if ( options.preLoad ) {
|
|
37
|
-
options.preLoad.forEach( ( language ) => {
|
|
38
|
-
try {
|
|
39
|
-
loadLanguages( [ language ] );
|
|
40
|
-
} catch ( e ) {
|
|
41
|
-
console.warn( `Failed to preload prism syntax: ${ language }!` );
|
|
42
|
-
}
|
|
43
|
-
} );
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Require optional language package
|
|
48
|
-
* @param {string} language
|
|
49
|
-
*/
|
|
50
|
-
function requireLanguage( language ) {
|
|
51
|
-
if ( !languages[ language ] ) {
|
|
52
|
-
try {
|
|
53
|
-
loadLanguages( [ language ] );
|
|
54
|
-
} catch ( e ) {
|
|
55
|
-
console.warn( `Failed to load prism syntax: ${ language }!` );
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// Set up line numbers
|
|
61
|
-
const NEW_LINE_EXP = /\n(?!$)/g;
|
|
62
|
-
let lineNumbersWrapper;
|
|
63
|
-
|
|
64
|
-
Prism.hooks.add( 'after-tokenize', function( env ) {
|
|
65
|
-
const match = env.code.match( NEW_LINE_EXP );
|
|
66
|
-
const linesNum = match ? match.length + 1 : 1;
|
|
67
|
-
const lines = new Array( linesNum + 1 ).join( '<span></span>' );
|
|
68
|
-
lineNumbersWrapper = `<span aria-hidden="true" class="line-numbers-rows">${ lines }</span>`;
|
|
69
|
-
} );
|
|
70
|
-
|
|
71
|
-
return function( files, metalsmith, done ) {
|
|
72
|
-
setImmediate( done );
|
|
73
|
-
|
|
74
|
-
Object.keys( files ).forEach( file => {
|
|
75
|
-
if ( !isHTMLFile( file ) ) {
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const contents = files[ file ].contents.toString();
|
|
80
|
-
const $ = load( contents, { decodeEntities: false } );
|
|
81
|
-
let highlighted = false;
|
|
82
|
-
const code = $( 'code' );
|
|
83
|
-
|
|
84
|
-
if ( !code.length ) return;
|
|
85
|
-
|
|
86
|
-
code.each( function() {
|
|
87
|
-
const $this = $( this );
|
|
88
|
-
|
|
89
|
-
const className = $this.attr( 'class' ) || '';
|
|
90
|
-
const targets = className.split( 'language-' );
|
|
91
|
-
let addLineNumbers = false;
|
|
92
|
-
|
|
93
|
-
if ( targets.length > 1 ) {
|
|
94
|
-
const $pre = $this.parent( 'pre' );
|
|
95
|
-
|
|
96
|
-
if ( $pre ) {
|
|
97
|
-
// Copy className to <pre> container
|
|
98
|
-
$pre.addClass( className );
|
|
99
|
-
|
|
100
|
-
if ( options.lineNumbers ) {
|
|
101
|
-
$pre.addClass( 'line-numbers' );
|
|
102
|
-
addLineNumbers = true;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
highlighted = true;
|
|
107
|
-
let language = targets[ 1 ];
|
|
108
|
-
requireLanguage( language );
|
|
109
|
-
|
|
110
|
-
if ( !languages[ language ] ) {
|
|
111
|
-
language = 'markup';
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
const html = ( language === 'markup' && !options.decode )
|
|
115
|
-
? $this.html()
|
|
116
|
-
: he.decode( $this.html() );
|
|
117
|
-
|
|
118
|
-
const highlightedCode = Prism.highlight( html, languages[ language ] );
|
|
119
|
-
$this.html( addLineNumbers ? highlightedCode + lineNumbersWrapper : highlightedCode );
|
|
120
|
-
}
|
|
121
|
-
} );
|
|
122
|
-
|
|
123
|
-
if ( highlighted ) {
|
|
124
|
-
files[ file ].contents = Buffer.from( $.html() );
|
|
125
|
-
}
|
|
126
|
-
} );
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
export default metalsmithPrism;
|