将Typora警告框转化成fluid便签

将Typora警告框转化成fluid便签

我在使用fluid主题时遇到了一个hexo渲染与typora编辑器不兼容的问题:Typora的警告框不能够在hexo网页上实现渲染,需要先转换成fluid主题的便签格式。

Typora警告框

fluid便签

Typora中支持的警告框只能够在typora内部实现渲染,fluid不能将其自动转化为其便签的语法实现渲染,于是我让claude给我写了一个脚本将typora文章中的警告框语法转化成fluid主题的便签语法。

draft-to-post.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
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#!/usr/bin/env node

/**
* Hexo草稿转发布脚本
* 将_drafts目录中的Typora格式Markdown文件转换为Hexo兼容格式并发布到_posts目录
*
* 使用方法:
* node draft-to-post.js [文件名]
*
* 示例:
* node draft-to-post.js # 转换所有草稿
* node draft-to-post.js article.md # 只转换特定文件
*/

const fs = require('fs');
const path = require('path');

// 定义Hexo目录路径
const DRAFTS_DIR = path.join(process.cwd(), 'source', '_drafts');
const POSTS_DIR = path.join(process.cwd(), 'source', '_posts');

// 颜色映射关系
const alertTypeMap = {
'提醒内容': 'info', // 蓝色
'建议内容': 'success', // 绿色
'重要内容': 'primary', // 紫色
'警告内容': 'warning', // 黄色
'注意内容': 'danger', // 红色

'NOTE': 'info',
'TIP': 'success',
'IMPORTANT': 'primary',
'WARNING': 'warning',
'CAUTION': 'danger'
};

/**
* 将Typora警告框转换为Hexo Fluid便签
*/
function convertTyporaToHexoFluid(content) {
// 1. 转换英文格式的警告框
let convertedContent = convertTyporaAlerts(content);

// 2. 转换中文格式的提示框
convertedContent = convertTyporaTips(convertedContent);

return convertedContent;
}

/**
* 转换英文警告框
*/
function convertTyporaAlerts(content) {
const typoraAlertRegex = />\s*\[!(NOTE|TIP|WARNING|IMPORTANT|CAUTION)\]([\s\S]*?)(?=>\s*\[!|\n\s*\n|$)/g;

return content.replace(typoraAlertRegex, (match, alertType, alertContent) => {
const noteType = alertTypeMap[alertType] || 'info';

const cleanedContent = alertContent
.split('\n')
.map(line => line.replace(/^\s*>\s?/, ''))
.join('\n')
.trim();

return `{% note ${noteType} %}\n${cleanedContent}\n{% endnote %}`;
});
}

/**
* 转换中文提示框
*/
function convertTyporaTips(content) {
const typoCnTipRegex = />\s*(提醒内容|建议内容|重要内容|警告内容|注意内容)[::]([\s\S]*?)(?=>\s*(?:提醒内容|建议内容|重要内容|警告内容|注意内容)[::]|\n\s*\n|$)/g;

return content.replace(typoCnTipRegex, (match, tipType, tipContent) => {
const noteType = alertTypeMap[tipType] || 'info';

const cleanedContent = tipContent
.split('\n')
.map(line => line.replace(/^\s*>\s?/, ''))
.join('\n')
.trim();

return `{% note ${noteType} %}\n${cleanedContent}\n{% endnote %}`;
});
}

/**
* 确保文件包含有效的Front Matter
*/
function ensureFrontMatter(content, fileName) {
// 检查文件是否已有Front Matter
if (content.startsWith('---\n')) {
return content;
}

// 从文件名生成标题
const title = path.basename(fileName, path.extname(fileName))
.replace(/-/g, ' ')
.replace(/\b\w/g, l => l.toUpperCase());

// 获取当前日期
const now = new Date();
const dateStr = now.toISOString().split('T')[0];
const timeStr = now.toTimeString().split(' ')[0];

// 创建基本Front Matter
const frontMatter = `---
title: ${title}
date: ${dateStr} ${timeStr}
tags:
categories:
---

`;

return frontMatter + content;
}

/**
* 处理单个文件
*/
function processDraft(fileName) {
const draftPath = path.join(DRAFTS_DIR, fileName);
const postPath = path.join(POSTS_DIR, fileName);

try {
console.log(`处理草稿: ${fileName}`);

// 读取草稿内容
let content = fs.readFileSync(draftPath, 'utf8');

// 确保Front Matter存在
content = ensureFrontMatter(content, fileName);

// 转换便签
const convertedContent = convertTyporaToHexoFluid(content);

// 检查_posts目录是否存在
if (!fs.existsSync(POSTS_DIR)) {
fs.mkdirSync(POSTS_DIR, { recursive: true });
}

// 写入转换后的内容到_posts目录
fs.writeFileSync(postPath, convertedContent, 'utf8');

console.log(`✓ 已发布: ${fileName} (draft → post)`);
return true;
} catch (error) {
console.error(`❌ 处理文件时出错: ${fileName}`);
console.error(` - ${error.message}`);
return false;
}
}

/**
* 检查目录是否存在并创建
*/
function ensureDirectoryExists(dir) {
if (!fs.existsSync(dir)) {
console.log(`创建目录: ${dir}`);
fs.mkdirSync(dir, { recursive: true });
return true;
}
return false;
}

// 主函数
function main() {
// 确保目录存在
ensureDirectoryExists(DRAFTS_DIR);
ensureDirectoryExists(POSTS_DIR);

const args = process.argv.slice(2);
let targetFiles = [];

if (args.length > 0) {
// 处理指定的文件
targetFiles = args.map(file => {
// 如果用户没有提供.md后缀,添加上
return file.endsWith('.md') ? file : `${file}.md`;
});
} else {
// 处理所有草稿
try {
targetFiles = fs.readdirSync(DRAFTS_DIR)
.filter(file => file.endsWith('.md'));
} catch (error) {
console.error(`❌ 无法读取草稿目录: ${DRAFTS_DIR}`);
console.error(error);
process.exit(1);
}
}

if (targetFiles.length === 0) {
console.log(`⚠️ 没有找到要处理的Markdown文件(在 ${DRAFTS_DIR})`);
return;
}

console.log('');
console.log('🚀 开始将草稿转换为发布文章');
console.log(`📂 草稿目录: ${DRAFTS_DIR}`);
console.log(`📂 发布目录: ${POSTS_DIR}`);
console.log(`📄 要处理的文件: ${targetFiles.length}个`);
console.log('');

let successCount = 0;
let failCount = 0;

// 处理每个文件
targetFiles.forEach(fileName => {
if (processDraft(fileName)) {
successCount++;
} else {
failCount++;
}
});

console.log('');
console.log('✅ 转换完成!');
console.log(` - 成功: ${successCount}个文件`);
if (failCount > 0) {
console.log(` - 失败: ${failCount}个文件`);
}
console.log('');
console.log('💡 提示: 现在可以运行 "hexo g" 来生成站点');
console.log('');
}

// 运行主函数
main();

注意这个脚本会修改原始的typora文章中的警告框语法,破坏原先的文本内容,并且修改后的语法将无法在typora中渲染出效果。我的方案是在source/目录下与_posts/文件夹平行创建一个_drafts/文件夹,将原始的typora文章放在_drafts/目录下,上面的脚本运行后会在_posts/目录下生成新的包含fluid主题的便签语法,即hexo兼容格式的文章(发布文件),原始的typora文章还会在_drafts/目录下保持原样。

1
2
3
4
5
6
7
8
9
10
11
12
hexo_code/
├── source/
│ ├── _drafts/ <- 存放原始Typora文件
│ │ ├── article1.md
│ │ └── article2.md
│ │
│ └── _posts/ <- 存放转换后的发布文件
│ ├── article1.md
│ └── article2.md
├── themes/
├── _config.yml
└── ...

hexo g之前运行脚本:

1
2
node draft-to-post.js            # 转换所有草稿
node draft-to-post.js article.md # 只转换特定文件

将Typora警告框转化成fluid便签
http://oooscar8.github.io/2025/02/21/hexo-convert-note/
作者
Alex Sun
发布于
2025年2月21日
许可协议