Setup Hexo and Typora local images with github actions

Just deployed hexo on github pages. Never used hexo before and one thing annoying me is the images in markdown.

On typora I have following settings:

typora-image

This works well as typora will copy images to the relative path...and of coz it's not working with hexo.

I'm new to hexo, from my (re)search, the official way is to set post_asset_folder: true, but the default image path is ./${filename} not my current ./imgs.md/${filename}, and, it's dumb to create an empty folder for blog post without images...

Solution

Some quick search shows hexo-asset-image plugin...well, it's kinda abandoned and has bug with github.io -- or to be exact, the permanent link url pattern.

Here is the original idea for the .io domain fix. Mine is slightly different as a quick dirty fix:

  1. DON"T ENABLE post_asset_folder, i.e., keep it as false in _config.yml

  2. install the plugin npm install hexo-asset-image --save

  3. Modify node_modules/hexo-asset-image/index.js:
    let it run regardless post_asset_folder settings and set var link = 'pics/imgs.md/'

    Another minor bug. Find:

    || /^\s*\/uploads|images\//.test(src))) {

    change to

    || /^\s*\/(uploads|images)\//.test(src))) {

    This regex is used to keep /images/ /uploads/ unchanged, however without parentheses it will also match things like /path/to/some-images/images.jpg

behind the scenes...

Now, with hexo new test, hexo will only create source/_post/test.md but no additional image folder, and after I insert 1.jpg, typora puts it in source/_posts/imgs.md/test/1.jpg, and referred as relative path ./imgs.md/test/1.jpg.

After hexo generate with patched hexo-asset-image, the compiled html will look for/pics/imgs.md/test/1.jpg , which, in our local path, is public/pics/imgs.md/test/1.jpg.

With post_asset_folder disabled, the whole imgs.md folder is ignored during hexo g, so we have to copy it to the public folder afterwards and before final deploy.

Put everything together with github action

old workflow

I use github actions to deploy the blog to github pages. Before the change, it goes like:

1
2
3
4
5
6
7
cd "${GITHUB_WORKSPACE}"
npm install hexo-cli -g
npm install hexo-deployer-git --save
npm install hexo-asset-image --save
hexo clean
hexo generate
hexo deploy

new workflow:

In blog folder, mkdir script, put modified index.js as hexo-asset-image-index.js in it and create a fix-hexo-image.sh as below.

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/bin/bash

if [[ "$1" == "js" ]]; then
echo 'use modified hexo-assert-image plugin'
cp script/hexo-asset-image-index.js node_modules/hexo-asset-image/index.js
fi

if [[ "$1" == "pics" ]]; then
echo 'copy images from source/_posts/imgs.md to public/pics'
mkdir -p public/pics/
cp -a source/_posts/imgs.md public/pics/
fi

The updated workflow:

patch hexo-asset-image, followed by hexo clean, hexo generate. Copy imgs.md folder to public/ and finally hexo deploy.

1
2
3
4
5
6
7
8
9
10
cd "${GITHUB_WORKSPACE}"
npm install hexo-cli -g
npm install hexo-deployer-git --save
npm install hexo-asset-image --save
chmod a+x script/fix-hexo-image.sh
script/fix-hexo-image.sh js
hexo clean
hexo generate
script/fix-hexo-image.sh pics
hexo deploy

My full modified hexo-asset-image/index.js for reference, save it as hexo-asset-image-index.js and put in script folder.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
'use strict';
var cheerio = require('cheerio');

// http://stackoverflow.com/questions/14480345/how-to-get-the-nth-occurrence-in-a-string
function getPosition(str, m, i) {
return str.split(m, i).join(m).length;
}

hexo.extend.filter.register('after_post_render', function(data){
var config = hexo.config;
/*
if(config.post_asset_folder){
var link = data.permalink;
var beginPos = getPosition(link, '/', 3) + 1;
var appendLink = '';
// In hexo 3.1.1, the permalink of "about" page is like ".../about/index.html".
// if not with index.html endpos = link.lastIndexOf('.') + 1 support hexo-abbrlink
if(/.*\/index\.html$/.test(link)) {
// when permalink is end with index.html, for example 2019/02/20/xxtitle/index.html
// image in xxtitle/ will go to xxtitle/index/
appendLink = 'index/';
var endPos = link.lastIndexOf('/');
}
else {
var endPos = link.lastIndexOf('.');
}
link = link.substring(beginPos, endPos) + '/' + appendLink;
*/
if (true) {
var link = 'pics/imgs.md/';
var toprocess = ['excerpt', 'more', 'content'];
for(var i = 0; i < toprocess.length; i++){
var key = toprocess[i];

var $ = cheerio.load(data[key], {
ignoreWhitespace: false,
xmlMode: false,
lowerCaseTags: false,
decodeEntities: false
});

$('img').each(function(){
if ($(this).attr('src')){
// For windows style path, we replace '\' to '/'.
var src = $(this).attr('src').replace('\\', '/');
if(!(/http[s]*.*|\/\/.*/.test(src)
|| /^\s+\//.test(src)
|| /^\s*\/(uploads|images)\//.test(src))) {
// For "about" page, the first part of "src" can't be removed.
// In addition, to support multi-level local directory.
var linkArray = link.split('/').filter(function(elem){
return elem != '';
});
var srcArray = src.split('/').filter(function(elem){
return elem != '' && elem != '.';
});
if(srcArray.length > 1)
srcArray.shift();
src = srcArray.join('/');

$(this).attr('src', config.root + link + src);
console.info&&console.info("update link as:-->"+config.root + link + src);
}
}else{
console.info&&console.info("no src attr, skipped...");
console.info&&console.info($(this));
}
});
data[key] = $.html();
}
}
});