├── .npmignore ├── test.js ├── LICENSE ├── .gitignore ├── package.json ├── index.js ├── util.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | tmp/ 3 | coverage/ 4 | *.log 5 | .travis.yml 6 | gulpfile.js 7 | .idea/ 8 | appveyor.yml -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const util = require('./util'); 2 | 3 | // console.log(util.parseSource("Title")); 4 | // console.log(util.parseSource("20091010-Title")); 5 | // console.log(util.parseSource("2009-10-10-Title")); 6 | // console.log(util.parseSource("[20091010]-Title")); 7 | // console.log(util.parseSource("「20091010」Title")); 8 | // console.log(util.parseSource("『20091010』Title")); 9 | // console.log(util.parseSource("『20091010』Title")); 10 | 11 | console.log(util.parseSource("_posts/寅花晨拾/20111027-object file not found.md")); 12 | 13 | console.log(util.crc32("1234567890asdfghjkl")); 14 | 15 | let tags = []; 16 | util.parseTags("Linux, Mac, Shell, HTML", tags); 17 | console.log(tags); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 lin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | *.pid.lock 14 | 15 | # Directory for instrumented libs generated by jscoverage/JSCover 16 | lib-cov 17 | 18 | # Coverage directory used by tools like istanbul 19 | coverage 20 | 21 | # nyc test coverage 22 | .nyc_output 23 | 24 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 25 | .grunt 26 | 27 | # Bower dependency directory (https://bower.io/) 28 | bower_components 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (https://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directories 37 | node_modules/ 38 | jspm_packages/ 39 | 40 | # TypeScript v1 declaration files 41 | typings/ 42 | 43 | # Optional npm cache directory 44 | .npm 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # Yarn Integrity file 56 | .yarn-integrity 57 | yarn.lock 58 | 59 | # dotenv environment variables file 60 | .env 61 | 62 | # next.js build output 63 | .next 64 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-enhancer", 3 | "version": "1.0.10", 4 | "description": "a feature enhancer for hexo, support abbrlink, auto-title, auto-date, auto-categories, auto-tags...", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:sisyphsu/hexo-enhancer.git" 12 | }, 13 | "publishConfig": { 14 | "registry": "https://registry.npmjs.org" 15 | }, 16 | "keywords": [ 17 | "hexo", 18 | "link", 19 | "enhancer", 20 | "generate", 21 | "permalink", 22 | "categories", 23 | "tags", 24 | "title", 25 | "date" 26 | ], 27 | "author": "sisyphsu", 28 | "license": "MIT", 29 | "bugs": { 30 | "url": "https://github.com/sisyphsu/hexo-enhancer/issues" 31 | }, 32 | "homepage": "https://github.com/sisyphsu/hexo-enhancer#readme", 33 | "dependencies": { 34 | "base-x": "^3.0.5", 35 | "buffer-crc32": "^0.2.13", 36 | "hexo-front-matter": "^1.0.0", 37 | "hexo-fs": "^3.1.0", 38 | "md5": "^2.2.1", 39 | "moment-timezone": "^0.5.23" 40 | }, 41 | "devDependencies": { 42 | "@types/hexo": "^3.8.5", 43 | "@types/hexo-log": "^0.2.2", 44 | "@types/moment-timezone": "^0.5.10" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const util = require("./util"); 4 | 5 | // prepare tags by `hexo.config.enhancer.tags` and `hexo.config.keywords` 6 | const tags = []; 7 | util.parseTags(hexo.config.tags, tags); 8 | util.parseTags(hexo.config.keywords, tags); 9 | 10 | /** 11 | * fitler hexo's post, auto generate `title`, `date`, `abbrlink` 12 | * 13 | * @param log 14 | * @param data 15 | */ 16 | function filterPost(log, data) { 17 | let metadata = util.parseSource(data.source); 18 | 19 | if (!data.title) { 20 | data.title = metadata.title; 21 | log.i("Generate title [%s] for post [%s]", metadata.title, data.source); 22 | } 23 | 24 | if (metadata.date) { 25 | data.date = metadata.date; 26 | log.i("Generate date [%s] for post [%s]", metadata.date, data.source); 27 | } 28 | 29 | if (!data.abbrlink) { 30 | data.abbrlink = util.crc32(data.title); 31 | log.i("Generate abbrlink [%s] for post [%s]", data.abbrlink, data.source); 32 | } 33 | 34 | if (metadata.categories.length) { 35 | let categories = metadata.categories; 36 | data.categories.forEach(item => { 37 | if (typeof item === 'string') { 38 | categories.push(item); 39 | } else if (item.name) { 40 | categories.push(item.name); 41 | } 42 | }); 43 | categories.reverse(); 44 | data.setCategories(categories); 45 | log.i("Generate categories [%s] for post [%s]", categories, data.source); 46 | } 47 | 48 | if (tags.length) { 49 | let matchedTags = util.matchTags(data.raw, tags); 50 | if (matchedTags.length) { 51 | data.tags.forEach(tag => { 52 | if (matchedTags.indexOf(tag) < 0) { 53 | matchedTags.push(tag.name); 54 | } 55 | }); 56 | data.setTags(matchedTags); 57 | log.i("Generate tags [%s] for post [%s]", matchedTags, data.source); 58 | } 59 | } 60 | 61 | if (data.source && data.source.indexOf('$') >= 0) { 62 | data.mathjax = true; 63 | log.i("Generate mathjax [true] for post [%s]", data.source); 64 | } 65 | 66 | data.toc = true; 67 | } 68 | 69 | hexo.extend.filter.register('before_post_render', function (data) { 70 | if (data.layout === 'post') { 71 | filterPost(this.log, data); 72 | } 73 | return data; 74 | }); 75 | -------------------------------------------------------------------------------- /util.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const moment = require('moment-timezone'); 3 | const crc32 = require('buffer-crc32'); 4 | const md5 = require('md5'); 5 | const basex = require('base-x'); 6 | 7 | const base62 = basex("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"); 8 | const base32 = basex("0123456789ABCDEFGHJKMNPQRSTVWXYZ"); 9 | const reg = /^.?(\d{4})[-_]?(\d{2})[-_]?(\d{2}).?[-_.@# ]*(.*)$/; 10 | const postDir = '_posts'; 11 | const draftDir = '_drafts'; 12 | 13 | // copy from hexo 14 | function toMoment(value) { 15 | if (moment.isMoment(value)) return moment(value._d); 16 | return moment(value); 17 | } 18 | 19 | /** 20 | * custom hash, based on md5+base62 21 | * @param {string} str 22 | * @returns {string} 23 | */ 24 | module.exports.hash = function (str) { 25 | let md5bytes = md5(str, {asBytes: true}); 26 | let base62Str = base62.encode(Buffer.from(md5bytes)); 27 | 28 | return base62Str.substring(0, 12); 29 | }; 30 | 31 | /** 32 | * Calculate crc32 of `str`, with base32 format. 33 | * @param {string} str 34 | * @returns {string|*} 35 | */ 36 | module.exports.crc32 = function (str) { 37 | let buf = crc32(Buffer.from(str)); 38 | return base32.encode(buf); 39 | }; 40 | 41 | /** 42 | * Parse post's source, pick up `title` and `date` field 43 | * @param {string} src 44 | * @return Object 45 | */ 46 | module.exports.parseSource = function (src) { 47 | let title, date; 48 | let categories = []; 49 | let parts = src.split("/"); 50 | if (parts.length > 0) { 51 | let filename = parts[parts.length - 1]; 52 | if (filename.indexOf(".") >= 0) { 53 | filename = filename.substring(0, filename.indexOf(".")); 54 | } 55 | let match = filename.match(reg); 56 | if (match) { 57 | date = toMoment(`${match[1]}-${match[2]}-${match[3]}`); 58 | title = match[4]; 59 | } else { 60 | title = filename; 61 | } 62 | } 63 | for (let i = parts.length - 2; i > 0; i--) { 64 | let part = parts[i]; 65 | if (!part || part === '~' || part === '.' || part === postDir || part === draftDir) { 66 | break; 67 | } 68 | if (categories.indexOf(part) < 0) { 69 | categories.push(part); 70 | } 71 | } 72 | return {title, date, categories}; 73 | }; 74 | 75 | /** 76 | * Find `tags` that `src` contains. 77 | * @param {string} src 78 | * @param {Array} tags 79 | * @return {Array} 80 | */ 81 | module.exports.matchTags = function (src, tags) { 82 | let result = []; 83 | if (src && tags) { 84 | let lowerSrc = src.toLowerCase(); 85 | tags.forEach(tag => { 86 | if (lowerSrc.indexOf(tag.toLowerCase()) > 0) { 87 | result.push(tag); 88 | } 89 | }); 90 | } 91 | return result; 92 | }; 93 | 94 | /** 95 | * Parse tags from `src` string. 96 | * @param {string} src 97 | * @param {Array} tgt 98 | */ 99 | module.exports.parseTags = function (src, tgt) { 100 | if (src && tgt) { 101 | src.split(',').forEach(tag => { 102 | tag = tag.trim(); 103 | if (tag && tgt.indexOf(tag) < 0) { 104 | tgt.push(tag); 105 | } 106 | }); 107 | } 108 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hexo-enhancer 2 | 3 | This is an feature enhancement plugin for `Hexo`. 4 | 5 | 中文文档:https://sulin.me/2019/Z726F8.html 6 | 7 | ## Introduction 8 | 9 | This plugin support multiple helpful features as blow (**by now**): 10 | 11 | - Auto generate `title` by filename. 12 | - Auto generate `abbrlink` by filename, with `base32` and `crc32` arithmetic. 13 | - Auto generate `date` by filename, like `Jekyll`. 14 | - Auto generate `categories` by filepath. 15 | - Auto generate `tags` by global pre-configured `tags` and `keywords` in the `_config.yml`. 16 | 17 | For example, without this plugin, you need write MarkDown like this: 18 | 19 | ```markdown 20 | --- 21 | title: Title 22 | date: 2019-03-05 12:54:57 23 | categories: [A, B] 24 | tags: [tag1, tag2] 25 | --- 26 | 27 | # Title 28 | 29 | This is a markdown file, in categories [A, B], with tags [tag1, tag2]. 30 | 31 | ``` 32 | 33 | With this graceful plugin, you don't need write boring `Front-matter` anymore. 34 | 35 | ```markdown 36 | # Title 37 | 38 | This is a markdown file, in categories [A, B], with tags [tag1, tag2]. 39 | ``` 40 | 41 | ## Installation 42 | 43 | ``` 44 | npm install hexo-enhancer --save 45 | ``` 46 | 47 | OR 48 | 49 | ``` 50 | yarn add hexo-enhancer 51 | ``` 52 | 53 | ## Usage - `date` and `title` 54 | 55 | Like `Jekyll`, you need use formatted `filename`, which is `date + title`, `hexo-enhancer` will parse them from your filename. 56 | 57 | The format is really flexible: 58 | ```regexp 59 | /^.?(\d{4})[-_]?(\d{2})[-_]?(\d{2}).?[-_.@# ]*(.*)$/ 60 | ``` 61 | 62 | If you are familiar with `Regexp`, you will know how flexible it is: 63 | 64 | ``` 65 | 20091010-Title.md 66 | 2009-10-10_Title.md 67 | 2009-10-10-Title.md 68 | 2009/10/10#Title.md 69 | 2009/10/10@Title.md 70 | [20091010]-Title.md 71 | 【20091010】Title.md 72 | 「20091010」-Title.md 73 | ``` 74 | 75 | All filenames above is fine, `hexo-enhancer` will parse it into: 76 | 77 | ```markdown 78 | --- 79 | title: Title 80 | date: 2009-10-10 81 | --- 82 | ``` 83 | ## Usage - `categories` 84 | 85 | For `categories`, you should put your `.md` file in right directory with right name, `hexo-enhancer` will use the directory name as `categories`, 86 | which means `_posts/problom-record/Java/20091010-Title.md` will has: 87 | 88 | ```markdown 89 | --- 90 | categories: [problom-record, Java] 91 | --- 92 | ``` 93 | 94 | ## Usage - `tags` 95 | 96 | For `tags`, you can prepare all `candidate-tag` in `_config.yml`, like this: 97 | 98 | ```yaml 99 | keywords: HTML, JavaScript, Hexo 100 | tags: Java, Golang, React, Vue 101 | ``` 102 | 103 | `hexo-enhancer` will scan your post file, auto allocate all tags which appreared in the markdown source. 104 | 105 | Tags are case insensitive. 106 | 107 | ## Usage - `abbrlink` 108 | 109 | `hexo-enhancer` will use `base32(crc32(title))` generate a short link for your post, you can use it in the `permlink`: 110 | 111 | ```yaml 112 | permalink: :year/:abbrlink.html 113 | # permalink: :year/:abbrlink 114 | # permalink: posts/:abbrlink.html 115 | # permalink: :year/:month/:day/:abbrlink.html 116 | ``` 117 | 118 | With abbrlink, your post's url will really clean, like `https://sulin.me/2019/Z726F8.html` 119 | 120 | ## New Features 121 | 122 | ### 2020-04-22 123 | 124 | 1. Tag searching case insensitive 125 | 2. Automaticly add "mathjax: true" if src contains $ 126 | 127 | ## License 128 | 129 | MIT 130 | --------------------------------------------------------------------------------