├── .gitignore ├── .editorconfig ├── package.json ├── README.md └── src └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .vscode/ 3 | debug.log -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-prism-plugin", 3 | "version": "2.3.0", 4 | "description": "Hexo code highlight by Prism.js", 5 | "main": "src/index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Eric Huang", 10 | "license": "MIT", 11 | "dependencies": { 12 | "dir-resolve": "^1.0.2", 13 | "hexo-fs": "^0.2.1", 14 | "node-prismjs": "^0.1.0", 15 | "prism-themes": "^1.0.0", 16 | "prismjs": "^1.6.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hexo-Prism-Plugin [](https://www.npmjs.com/package/hexo-prism-plugin) 2 | Since `highlight.js` didn't support JSX syntax properly, I wrote this plugin to replace 3 | Hexo's default code highlight plugin. 4 | 5 | ## Install 6 | ``` 7 | npm i -S hexo-prism-plugin 8 | ``` 9 | ## Usage 10 | Firstly, you should edit your `_config.yml` by adding following configuration. 11 | ```yaml 12 | prism_plugin: 13 | mode: 'preprocess' # realtime/preprocess 14 | theme: 'default' 15 | line_number: false # default false 16 | custom_css: 'path/to/your/custom.css' # optional 17 | ``` 18 | After that, check `highlight` option in `_config.yml`. Make sure that default code highlight plugin is disabled. 19 | ```yaml 20 | highlight: 21 | enable: false 22 | ``` 23 | Finally, clean and re-generate your project by running following commands: 24 | 25 | ``` 26 | hexo clean 27 | ``` 28 | 29 | ``` 30 | hexo generate 31 | ``` 32 | 33 | ## Options 34 | - mode: 35 | - realtime (Parse code on browser in real time) 36 | - preprocess (Preprocess code in node) 37 | 38 | - theme: 39 | - default 40 | - coy 41 | - dark 42 | - funky 43 | - okaidia 44 | - solarizedlight 45 | - tomorrow 46 | - twilight 47 | - atom-dark 48 | - base16-ateliersulphurpool.light 49 | - cb 50 | - duotone-dark 51 | - duotone-earth 52 | - duotone-forest 53 | - duotone-light 54 | - duotone-sea 55 | - duotone-space 56 | - ghcolors 57 | - hopscotch 58 | - pojoaque 59 | - vs 60 | - xonokai 61 | 62 | - line_number: 63 | - true (Show line numbers) 64 | - false (Default, Hide line numbers) 65 | 66 | - no_assets 67 | - true (Stop loading asset files) 68 | - false (Default, load script and stylesheets files) 69 | 70 | ## Themes 71 | You can check out prism-themes project for additional theme preview: 72 | 73 | https://github.com/PrismJS/prism-themes#available-themes 74 | 75 | ## Supported languages 76 | You could find the supported languages here: 77 | 78 | http://prismjs.com/#languages-list 79 | 80 | ## License 81 | MIT 82 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('hexo-fs'); 4 | const path = require('path'); 5 | const Prism = require('node-prismjs'); 6 | const dirResolve = require('dir-resolve'); 7 | 8 | const map = { 9 | ''': '\'', 10 | '&': '&', 11 | '>': '>', 12 | '<': '<', 13 | '"': '"' 14 | }; 15 | 16 | const themeRegex = /^prism-(.*).css$/; 17 | const regex = /
([\s\S]*?)<\/code><\/pre>/igm;
18 | const captionRegex = /(?![\s\S]*<\/p>/igm;
19 |
20 | /**
21 | * Unescape from Marked escape
22 | * @param {String} str
23 | * @return {String}
24 | */
25 | function unescape(str) {
26 | if (!str || str === null) return '';
27 | const re = new RegExp('(' + Object.keys(map).join('|') + ')', 'g');
28 | return String(str).replace(re, (match) => map[match]);
29 | }
30 |
31 | /**
32 | * Wrap theme file to unified format
33 | * @param {String} basePath
34 | * @param {String} filename
35 | * @return {Object}
36 | */
37 | function toThemeMap(basePath, filename) {
38 | const matches = filename.match(themeRegex);
39 | if (!matches)
40 | return;
41 |
42 | return {
43 | name: matches[1],
44 | filename,
45 | path: path.join(basePath, filename)
46 | };
47 | }
48 |
49 | const rootPath = hexo.config.root || '/';
50 | const prismLineNumbersPluginDir = dirResolve('prismjs/plugins/line-numbers');
51 | const prismThemeDir = dirResolve('prismjs/themes');
52 | const extraThemeDir = dirResolve('prism-themes/themes');
53 | const prismMainFile = require.resolve('prismjs');
54 | const standardThemes = fs.listDirSync(prismThemeDir)
55 | .map(themeFileName => toThemeMap(prismThemeDir, themeFileName));
56 | const extraThemes = fs.listDirSync(extraThemeDir)
57 | .map(themeFileName => toThemeMap(extraThemeDir, themeFileName));
58 |
59 | // Since the regex will not match for the default "prism.css" theme,
60 | // we filter the null theme out and manually add the default theme to the array
61 | const themes = standardThemes.concat(extraThemes).filter(Boolean);
62 | themes.push({
63 | name: 'default',
64 | filename: 'prism.css',
65 | path: path.join(prismThemeDir, 'prism.css')
66 | });
67 |
68 | // If prism plugin has not been configured, it cannot be initialized properly.
69 | if (!hexo.config.prism_plugin) {
70 | throw new Error('`prism_plugin` options should be added to _config.yml file');
71 | }
72 |
73 | // Plugin settings from config
74 | const prismThemeName = hexo.config.prism_plugin.theme || 'default';
75 | const mode = hexo.config.prism_plugin.mode || 'preprocess';
76 | const line_number = hexo.config.prism_plugin.line_number || false;
77 | const custom_css = hexo.config.prism_plugin.custom_css || null;
78 | const no_assets = hexo.config.prism_plugin.no_assets || false;
79 |
80 | const prismTheme = themes.find(theme => theme.name === prismThemeName);
81 | if (!prismTheme) {
82 | throw new Error("Invalid theme " + prismThemeName + ". Valid Themes: \n" + themes.map(t => t.name).concat('\n'));
83 | }
84 | const prismThemeFileName = prismTheme.filename;
85 | const prismThemeFilePath = custom_css === null ? prismTheme.path : path.join(hexo.base_dir, custom_css);
86 | /**
87 | * Code transform for prism plugin.
88 | * @param {Object} data
89 | * @return {Object}
90 | */
91 | function PrismPlugin(data) {
92 | // Patch for caption support
93 | if (captionRegex.test(data.content)) {
94 | // Attempt to parse the code
95 | data.content = data.content.replace(captionRegex, (origin, lang, caption, code) => {
96 | if (!lang || !caption || !code) return origin;
97 | return `${caption} ${code}
`;
98 | })
99 | }
100 |
101 | data.content = data.content.replace(regex, (origin, lang, code) => {
102 | const lineNumbers = line_number ? 'line-numbers' : '';
103 | const startTag = ``;
104 | const endTag = `
`;
105 | code = unescape(code);
106 | let parsedCode = '';
107 | if (Prism.languages[lang]) {
108 | parsedCode = Prism.highlight(code, Prism.languages[lang]);
109 | } else {
110 | parsedCode = code;
111 | }
112 | if (line_number) {
113 | const match = parsedCode.match(/\n(?!$)/g);
114 | const linesNum = match ? match.length + 1 : 1;
115 | let lines = new Array(linesNum + 1);
116 | lines = lines.join('');
117 | const startLine = '';
119 | parsedCode += startLine + lines + endLine;
120 | }
121 | return startTag + parsedCode + endTag;
122 | });
123 |
124 | return data;
125 | }
126 |
127 | /**
128 | * Copy asset to hexo public folder.
129 | */
130 | function copyAssets() {
131 | const assets = [{
132 | path: `css/${prismThemeFileName}`,
133 | data: () => fs.createReadStream(prismThemeFilePath)
134 | }];
135 |
136 | // If line_number is enabled in plugin config add the corresponding stylesheet
137 | if (line_number) {
138 | assets.push({
139 | path: 'css/prism-line-numbers.css',
140 | data: () => fs.createReadStream(path.join(prismLineNumbersPluginDir, 'prism-line-numbers.css'))
141 | });
142 | }
143 |
144 | // If prism plugin config mode is realtime include prism.js and line-numbers.js
145 | if (mode === 'realtime') {
146 | assets.push({
147 | path: 'js/prism.js',
148 | data: () => fs.createReadStream(prismMainFile)
149 | });
150 | if (line_number) {
151 | assets.push({
152 | path: 'js/prism-line-numbers.min.js',
153 | data: () => fs.createReadStream(path.join(prismLineNumbersPluginDir, 'prism-line-numbers.min.js'))
154 | });
155 | }
156 | }
157 |
158 | return assets;
159 | }
160 |
161 | /**
162 | * Injects code to html for importing assets.
163 | * @param {String} code
164 | * @param {Object} data
165 | */
166 | function importAssets(code, data) {
167 | const js = [];
168 | const css = [
169 | ``
170 | ];
171 |
172 | if (line_number && custom_css === null) {
173 | css.push(``);
174 | }
175 | if (mode === 'realtime') {
176 | js.push(``);
177 | if (line_number) {
178 | js.push(``);
179 | }
180 | }
181 | const imports = css.join('\n') + js.join('\n');
182 |
183 | // Avoid duplicates
184 | if (code.indexOf(imports) > -1) {
185 | return code;
186 | }
187 | return code.replace(/<\s*\/\s*head\s*>/, imports + '');;
188 | }
189 |
190 | // Register prism plugin
191 | // Set priority to make sure PrismPlugin executed first
192 | // Lower priority means that it will be executed first. The default priority is 10.
193 | hexo.extend.filter.register('after_post_render', PrismPlugin, 9);
194 |
195 | if (custom_css === null && !no_assets) {
196 | // Register to append static assets
197 | hexo.extend.generator.register('prism_assets', copyAssets);
198 |
199 | // Register for importing static assets
200 | hexo.extend.filter.register('after_render:html', importAssets);
201 | }
202 |
--------------------------------------------------------------------------------