├── .gitignore ├── .npmignore ├── LICENSE ├── index.js ├── package-lock.json ├── package.json └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | node_modules 28 | 29 | # Optional npm cache directory 30 | .npm 31 | 32 | # Optional REPL history 33 | .node_repl_history 34 | 35 | 36 | # npmignore 37 | .git -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Daniel Skogly 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 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var fs = require('hexo-fs'); 2 | var fm = require('hexo-front-matter'); 3 | 4 | var absolute_path_reference_url = function(url) { 5 | url = new URL(url); 6 | return url.pathname + url.search + url.hash; 7 | }; 8 | 9 | var contentJsonPath = hexo.public_dir + 'content.json'; 10 | var post_asset_folder = hexo.config.post_asset_folder; 11 | var imagesPath = hexo.config.url + hexo.config.root; 12 | var config = hexo.config.hasOwnProperty('featured_image') 13 | ? hexo.config.featured_image 14 | : {}; 15 | // https://tools.ietf.org/html/rfc3986#section-4.2 16 | var useAbsolutePathReference = config.absolute_path_reference; 17 | if (useAbsolutePathReference) { 18 | imagesPath = absolute_path_reference_url(imagesPath); 19 | } 20 | if (!post_asset_folder) { 21 | if (hexo.config.image_dir) { 22 | imagesPath += hexo.config.image_dir; 23 | } else { 24 | imagesPath += 'images'; 25 | } 26 | imagesPath += '/'; 27 | } 28 | 29 | hexo.extend.filter.register('before_post_render', function(data) { 30 | var front = fm.parse(data.raw); 31 | var featured_image = front.featured_image; 32 | if (featured_image) { 33 | var thumbnail = front.thumbnail; 34 | var imagePrefix = imagesPath; 35 | // Use post asset folder 36 | if (post_asset_folder) { 37 | imagePrefix = data.permalink; 38 | if (useAbsolutePathReference) { 39 | imagePrefix = absolute_path_reference_url(imagePrefix); 40 | } 41 | } 42 | // Check if the featured image path is an absolute URI 43 | if ( 44 | featured_image.indexOf('http') === 0 || 45 | featured_image.indexOf('/') === 0 46 | ) { 47 | // Use no prefix since we have the full or relative URI 48 | imagePrefix = ''; 49 | } 50 | 51 | // Compile the featured image URI 52 | data.featured_image = imagePrefix + featured_image; 53 | 54 | if (thumbnail) { 55 | data.thumbnail = imagePrefix + thumbnail; 56 | } 57 | } 58 | return data; 59 | }); 60 | 61 | hexo.extend.filter.register('before_exit', function() { 62 | // to work smoothly with hexo_generator_json_content 63 | var jsonContentCfg = hexo.config.hasOwnProperty('jsonContent') 64 | ? hexo.config.jsonContent 65 | : { 66 | meta: true, 67 | }; 68 | var postsCfg = jsonContentCfg.hasOwnProperty('posts') 69 | ? jsonContentCfg.posts 70 | : {}; 71 | 72 | if (postsCfg.featured_image && fs.existsSync(contentJsonPath)) { 73 | var postsObject = {}; 74 | var posts = hexo.locals.get('posts'); 75 | posts.forEach(function(post) { 76 | postsObject[post.path] = post; 77 | }); 78 | var content = JSON.parse(fs.readFileSync(contentJsonPath)); 79 | var contentPosts = content.posts; 80 | if (!contentPosts) return; 81 | content.posts = contentPosts.map(function(post) { 82 | var fullPost = postsObject[post.path]; 83 | if (fullPost && fullPost.featured_image) { 84 | post.featured_image = fullPost.featured_image; 85 | if (postsCfg.thumbnail && fullPost.thumbnail) { 86 | post.thumbnail = fullPost.thumbnail; 87 | } 88 | } 89 | return post; 90 | }); 91 | fs.writeFileSync(contentJsonPath, JSON.stringify(content)); 92 | } 93 | }); 94 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-featured-image", 3 | "version": "0.4.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "anymatch": { 8 | "version": "3.1.1", 9 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", 10 | "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", 11 | "requires": { 12 | "normalize-path": "^3.0.0", 13 | "picomatch": "^2.0.4" 14 | } 15 | }, 16 | "argparse": { 17 | "version": "1.0.10", 18 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 19 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 20 | "requires": { 21 | "sprintf-js": "~1.0.2" 22 | } 23 | }, 24 | "binary-extensions": { 25 | "version": "2.0.0", 26 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", 27 | "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" 28 | }, 29 | "bluebird": { 30 | "version": "3.7.2", 31 | "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", 32 | "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" 33 | }, 34 | "braces": { 35 | "version": "3.0.2", 36 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 37 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 38 | "requires": { 39 | "fill-range": "^7.0.1" 40 | } 41 | }, 42 | "chokidar": { 43 | "version": "3.3.1", 44 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz", 45 | "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==", 46 | "requires": { 47 | "anymatch": "~3.1.1", 48 | "braces": "~3.0.2", 49 | "fsevents": "~2.1.2", 50 | "glob-parent": "~5.1.0", 51 | "is-binary-path": "~2.1.0", 52 | "is-glob": "~4.0.1", 53 | "normalize-path": "~3.0.0", 54 | "readdirp": "~3.3.0" 55 | } 56 | }, 57 | "escape-string-regexp": { 58 | "version": "2.0.0", 59 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", 60 | "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==" 61 | }, 62 | "esprima": { 63 | "version": "4.0.1", 64 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 65 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" 66 | }, 67 | "fill-range": { 68 | "version": "7.0.1", 69 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 70 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 71 | "requires": { 72 | "to-regex-range": "^5.0.1" 73 | } 74 | }, 75 | "fsevents": { 76 | "version": "2.1.2", 77 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", 78 | "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", 79 | "optional": true 80 | }, 81 | "glob-parent": { 82 | "version": "5.1.0", 83 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", 84 | "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", 85 | "requires": { 86 | "is-glob": "^4.0.1" 87 | } 88 | }, 89 | "graceful-fs": { 90 | "version": "4.2.3", 91 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", 92 | "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" 93 | }, 94 | "hexo-front-matter": { 95 | "version": "1.0.0", 96 | "resolved": "https://registry.npmjs.org/hexo-front-matter/-/hexo-front-matter-1.0.0.tgz", 97 | "integrity": "sha512-Hn8IIzgWWnxYTekrjnA0rxwWMoQHifyrxKMqVibmFaRKf4AQ2V6Xo13Jiso6CDwYfS+OdA41QS5DG1Y+QXA5gw==", 98 | "requires": { 99 | "js-yaml": "^3.13.1" 100 | } 101 | }, 102 | "hexo-fs": { 103 | "version": "2.0.0", 104 | "resolved": "https://registry.npmjs.org/hexo-fs/-/hexo-fs-2.0.0.tgz", 105 | "integrity": "sha512-mtwjfh5IZMXVCoITtoV+LfWbrD7xCWyv8OTIrOmwUW4JR+7EEvuwqu+QDztt4RS0azxUuc1sKVK68Mxfp2AoYQ==", 106 | "requires": { 107 | "bluebird": "^3.5.1", 108 | "chokidar": "^3.0.0", 109 | "escape-string-regexp": "^2.0.0", 110 | "graceful-fs": "^4.1.11" 111 | } 112 | }, 113 | "is-binary-path": { 114 | "version": "2.1.0", 115 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 116 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 117 | "requires": { 118 | "binary-extensions": "^2.0.0" 119 | } 120 | }, 121 | "is-extglob": { 122 | "version": "2.1.1", 123 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 124 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" 125 | }, 126 | "is-glob": { 127 | "version": "4.0.1", 128 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 129 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 130 | "requires": { 131 | "is-extglob": "^2.1.1" 132 | } 133 | }, 134 | "is-number": { 135 | "version": "7.0.0", 136 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 137 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 138 | }, 139 | "js-yaml": { 140 | "version": "3.13.1", 141 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 142 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 143 | "requires": { 144 | "argparse": "^1.0.7", 145 | "esprima": "^4.0.0" 146 | } 147 | }, 148 | "normalize-path": { 149 | "version": "3.0.0", 150 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 151 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" 152 | }, 153 | "picomatch": { 154 | "version": "2.2.1", 155 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz", 156 | "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA==" 157 | }, 158 | "readdirp": { 159 | "version": "3.3.0", 160 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz", 161 | "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==", 162 | "requires": { 163 | "picomatch": "^2.0.7" 164 | } 165 | }, 166 | "sprintf-js": { 167 | "version": "1.0.3", 168 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 169 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" 170 | }, 171 | "to-regex-range": { 172 | "version": "5.0.1", 173 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 174 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 175 | "requires": { 176 | "is-number": "^7.0.0" 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hexo-featured-image", 3 | "description": "A plugin for the Hexo static site generator to add support for featured images in posts and json-content.", 4 | "author": "Daniel Skogly (https://wishy.gift)", 5 | "homepage": "https://github.com/poacher2k/hexo-featured-image", 6 | "keywords": [ 7 | "hexo", 8 | "featured", 9 | "image", 10 | "plugin" 11 | ], 12 | "version": "0.4.3", 13 | "main": "index", 14 | "license": "MIT", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/poacher2k/hexo-featured-image.git" 18 | }, 19 | "dependencies": { 20 | "hexo-front-matter": "^1.0.0", 21 | "hexo-fs": "^2.0.0" 22 | }, 23 | "devDependencies": {} 24 | } 25 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Hexo Featured Image 2 | 3 | A Hexo plugin to allow adding featured images with `featured_image` in front-matter and using it in post and/or have it output in the content.json if used together with [hexo-generator-json-content](https://github.com/alexbruno/hexo-generator-json-content). 4 | 5 | `thumbnail` is also supported, and works the same as `featured_image`. 6 | 7 | For example: 8 | 9 | `CoolPost.md` 10 | 11 | --- 12 | title: Cool post 13 | featured_image: my_img.png 14 | thumbnail: my_img_thumbnail.png 15 | --- 16 | What a cool blog I have! 17 | 18 | By using the Hexo Front Featured Image plugin, you can specify a post's featured image in its front matter. 19 | 20 | The absolute path to `my_img.png` will be available through `page.featured_image` in your templates. 21 | 22 | For example: 23 | 24 | `article.ejs` 25 | 26 | ... 27 | <% if (page.featured_image){ %> 28 | 29 | <% } %> 30 | ... 31 | 32 | ## Installation 33 | 34 | npm install --save hexo-featured-image 35 | 36 | or 37 | yarn add hexo-featured-image 38 | 39 | ## Usage 40 | 41 | This plugin will make automatically make `page.featured_image` available in your templates when you run `hexo server` or `hexo generate`. 42 | 43 | If you are using [hexo-generator-json-content](https://github.com/alexbruno/hexo-generator-json-content), it will automatically add the `featured_image` property to `content.json` when you run `hexo generate` and when you **exit** `hexo server`. 44 | 45 | ## Configuration 46 | 47 | ### URL 48 | 49 | For this plugin to work correctly, you must set `url` to your URL in `_config.yml`. For example, if you are working locally using the default url (http://0.0.0.0:4000/), set it like this: 50 | 51 | `_config.yml` 52 | 53 | ... 54 | # URL 55 | url: http://0.0.0.0:4000/ 56 | ... 57 | 58 | or enable `absolute_path_reference` in the options. 59 | 60 | ### post_asset_folder 61 | 62 | This plugin works without configuration if you are using absolute or relative URI's, [post asset folders](https://hexo.io/docs/asset-folders.html), or you are storing your images in `source/images`. 63 | 64 | If you are not using post asset folders, and you prefer to store your images somewhere else than in `source/images`, you must specify `image_dir` in `_config.yml` to wherever you store your images. To set your image directory to `source/assets`, you would set `image_dir: assets` in `_config.yml`. Example: 65 | 66 | `_config.yml` 67 | 68 | ... 69 | # Directory 70 | source_dir: source 71 | public_dir: public 72 | ... 73 | image_dir: assets 74 | ... 75 | 76 | ### [hexo-generator-json-content](https://github.com/alexbruno/hexo-generator-json-content) 77 | 78 | This plugin plays nicely with [hexo-generator-json-content](https://github.com/alexbruno/hexo-generator-json-content), and will output the absolute path of `featured_image` to `content.json` if `featured_image` has been set to `true` in the `jsonContent` configuration part of `_config.yml` like so: 79 | 80 | ... 81 | jsonContent: { 82 | ... 83 | posts: { 84 | ... 85 | featured_image: true 86 | thumbnail: true # if you want thumbnail to be added as well 87 | } 88 | } 89 | 90 | ## Options 91 | Add or modify the following section to your root `_config.yml` file 92 | 93 | ``` yaml 94 | featured_image: 95 | absolute_path_reference: true 96 | ``` 97 | 98 | - **absolute_path_reference**: Make your featured_image URL relative to the root of the website (i.e. they will not contain `url`) 99 | - default: false 100 | --------------------------------------------------------------------------------