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 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
  [![License](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square&label=license)](http://opensource.org/licenses/MIT)
6
6
  [![NPM](http://img.shields.io/npm/v/metalsmith-prism.svg?style=flat-square&label=npm)](https://npmjs.org/package/metalsmith-prism)
7
7
  [![Linux Passing](https://img.shields.io/travis/Availity/metalsmith-prism.svg?style=flat-square&label=linux)](https://travis-ci.org/Availity/metalsmith-prism)
8
8
  [![Windows Passing](https://img.shields.io/appveyor/ci/robmcguinness/metalsmith-prism.svg?style=flat-square&label=windows)](https://ci.appveyor.com/project/robmcguinness/metalsmith-prism)
9
9
 
10
- While this plugin adds all the required Prism HTML markup, **prism.css** must be included on the page to provide the syntax coloring.
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
- + Node `>= 14.x.x`
15
- + NPM `>= 8.x.x`
16
- + Metalsmith `>= v2.4.x`
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 metalsmith = require('metalsmith');
94
+ const Metalsmith = require('metalsmith');
55
95
  const prism = require('metalsmith-prism');
56
96
 
57
- metalsmith(__dirname)
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
- const metalsmith = require('metalsmith');
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
- metalsmith(__dirname)
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
- .use(prism({
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
- .use(metalsmithPrism({
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
- .use(prism({
112
- preLoad: ["java", "scala"]
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.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.js",
7
- "exports": "./lib/index.js",
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
- "preversion": "npm run test",
13
- "test": "mocha ./tests/index.mjs",
14
- "lint": "eslint ./lib ./tests"
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.3.7",
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.29.0"
56
+ "prismjs": "^1.30.0"
42
57
  },
43
58
  "devDependencies": {
44
- "chai": "^5.1.2",
45
- "eslint": "^9.14.0",
46
- "eslint-config-prettier": "^9.1.0",
47
- "eslint-plugin-prettier": "^5.2.1",
48
- "mocha": "^10.8.2",
49
- "prettier": "^3.3.3"
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;