├── .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 [![NPM](https://img.shields.io/npm/dm/hexo-prism-plugin.svg)](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 | --------------------------------------------------------------------------------