发布于 

分享两个Hexo小工具

分享两个我自己用的 Hexo 小工具,其实就是两个 Nodejs 小脚本。

1. 网站字数统计

现在用的这个主题没有全站字数统计功能,于是自己写了一个脚本来实现。

其实原理很简单,就是获取 source/_posts 目录下的所有 .md 文件,然后统计每个文件中的文字数,最后将总文字数替换到 source/js/wordcount.js 文件中。

就是每次提交前要记得执行。

sitewordcount.js 内容如下:

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
const fs = require('fs');
const path = require('path');
const TurndownService = require('turndown');
const turndownService = new TurndownService();

const postsDir = path.join(__dirname, 'source', '_posts');
let totalWordCount = 0;

function countWordsInFile(file) {
const content = fs.readFileSync(file, 'utf-8');
const separatorIndex = content.indexOf('---', 3); // 查找第二个 '---' 分隔符
const text = content.substring(separatorIndex + 3).trim(); // 获取分隔符之后的文本内容并去除空格
const characters = text.length;
return characters;
}

function traversePostsDir(dir) {
const files = fs.readdirSync(dir);

for (const file of files) {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);

if (stat.isDirectory()) {
traversePostsDir(filePath);
} else if (stat.isFile() && file.endsWith('.md')) {
const wordCount = countWordsInFile(filePath);
console.log(`${file}: ${wordCount} words`);
totalWordCount += wordCount;
}
}
}

traversePostsDir(postsDir);

// 将总文字数替换到文件中
const wordcountFilePath = path.join(__dirname, 'source', 'js', 'wordcount.js');
let wordcountContent = fs.readFileSync(wordcountFilePath, 'utf-8');
wordcountContent = wordcountContent.replace(/var\s+siteWordCount\s+=\s+\d+;/, `var siteWordCount = ${totalWordCount};`);
fs.writeFileSync(wordcountFilePath, wordcountContent);

console.log(`全站总字数: ${totalWordCount}`);

wordcount.js 是另一个脚本,运行在前端,负责把总字数显示到网站底部。

这里我不但用它显示总字数,同时每篇文章的字数和预计阅读时间也在此实现。

每次执行 node sitewordcount.js 之后,wordcount.js 的第一行内容就会变化。

wordcount.js 内容如下:

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
var siteWordCount = 221051;

document.getElementById("sitewordcount").innerHTML="<p style=\"text-align:center\">全站共 " + siteWordCount + " 个字.</p>"

// 获取 <article> 元素
var articleElement = document.querySelector('article.md-text.content.post');

// 检查 <article> 元素是否存在
if (articleElement) {
// 获取 <article> 元素下的所有 <p> 元素
var pElements = articleElement.querySelectorAll('p:not(.article-footer p)');

// 统计所有 <p> 元素的文本字数
var wordCount = 0;
for (var i = 0; i < pElements.length; i++) {
var pElement = pElements[i];
// console.log(pElement.textContent);
wordCount += countWords(pElement.textContent);
}

// 计算阅读时间(假设平均阅读速度为每分钟阅读700个字)
var readingTime = (wordCount / 700).toFixed(1);

// 创建 <blockquote> 元素
var blockquoteElement = document.createElement('blockquote');

// var brElement = document.createElement('br');

// 创建 <p> 元素
var pElement = document.createElement('p');
pElement.innerHTML = '本文共 ' + wordCount + ' 个字,阅读本文大概需要 ' + readingTime + ' 分钟。';

// 将 <p> 元素添加到 <blockquote> 元素中
blockquoteElement.appendChild(pElement);

// 查找第一个 <h1> 标签
var firstH1Element = articleElement.querySelector('h1');

// 检查第一个 <h1> 标签是否存在
if (firstH1Element) {
// 在第一个 <h1> 标签后插入 <blockquote> 元素
articleElement.insertBefore(blockquoteElement, firstH1Element.nextSibling);
// articleElement.insertBefore(brElement, blockquoteElement.nextSibling);
} else {
// 如果没有 <h1> 标签,则将 <blockquote> 元素插入到 <article> 元素的开头
articleElement.insertBefore(blockquoteElement, articleElement.firstChild);
// articleElement.insertBefore(brElement, blockquoteElement.nextSibling);
}
} else {
console.log('<article class="md-text content post"> 元素不存在');
}

// 统计文本字数
function countWords(text) {
// 移除文本中的空格和换行符
var cleanedText = text.replace(/\s+/g, '');

// 统计非空字符的数量
var wordCount = cleanedText.length;

return wordCount;
}

2. 创建文章时自动创建文件夹

每次创建文章后,都需要手动创建一个用于存放图片的文件夹。

我的习惯是文件夹名字用英文,所以每次都是要先翻译,再创建,有点麻烦,所以想一步到位。

create.js 脚本如下:

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
74
75
76
77
78
79
80
81
82
83
84
85
86
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const axios = require('axios');

// 配置项
const CONFIG = {
TRANSLATE_API: 'http://192.168.6.126:5000/translate', // 本地翻译服务地址
HEXO_PATH: path.join(__dirname), // Hexo根目录路径
ASSETS_DIR: path.join(__dirname, 'source', 'assets') // 资源目录路径
};

// 翻译函数
async function translate(text, source = 'zh', target = 'en') {
try {
const response = await axios.post(
CONFIG.TRANSLATE_API,
`q=${encodeURIComponent(text)}&source=${source}&target=${target}&format=text&alternatives=3`,
{
headers: {
'accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
}
}
);

if (!response.data?.translatedText) {
throw new Error('未收到有效翻译结果');
}

return response.data.translatedText;
} catch (error) {
throw new Error(`翻译失败: ${error.response?.data?.error_msg || error.message}`);
}
}

// 创建文件夹
function createAssetFolder(folderName) {
const folderPath = path.join(CONFIG.ASSETS_DIR, folderName);

if (!fs.existsSync(folderPath)) {
fs.mkdirSync(folderPath, { recursive: true });
console.log(`✅ 资源文件夹创建成功: ${folderPath}`);
} else {
console.log(`ℹ️ 资源文件夹已存在: ${folderPath}`);
}
}

// 主函数
async function main() {
const chineseTitle = process.argv[2];

if (!chineseTitle) {
console.log('❌ 请提供文章标题,示例: node script.js "文章标题"');
process.exit(1);
}

try {
// 翻译标题
const englishTitle = await translate(chineseTitle);
const folderName = englishTitle
.toLowerCase()
.replace(/[^\w\s]/gi, '') // 移除特殊字符
.replace(/\s+/g, '-'); // 空格转连字符

// 创建Hexo文章
console.log('⏳ 创建Hexo文章中...');
execSync(`hexo new post "${chineseTitle}"`, {
cwd: CONFIG.HEXO_PATH,
stdio: 'inherit'
});

// 创建资源文件夹
createAssetFolder(folderName);

console.log('\n🎉 操作完成!');
console.log(`📝 英文标题: ${englishTitle}`);
console.log(`📁 资源目录: ${path.join(CONFIG.ASSETS_DIR, folderName)}`);

} catch (error) {
console.error(`❌ 发生错误: ${error.message}`);
process.exit(1);
}
}

main();

执行 node create.js "文章标题" 即可。

原理也很简单,先调用 Hexo 命令创建文章,再调用翻译接口,根据返回结果创建文件夹即可。

这里翻译服务我调用的是我本地部署的 LibreTranslate,很简单,使用 docker 部署即可。

也可以改成其他翻译 api,让 AI 修改代码即可。