1 引言

我们常常使用 obsidian 写文章,发布到公众号和其他公共平台,其中有两个比较麻烦的问题:一个是将 markdown 转换成合适的网页格式,另一个是上传图片。这往往会花费很多时间,尤其是当文章较长,包含各层标题、引用、代码和公式时,需要花费更多的时间。此外,由于公众号文章需要有封面图,文章中也需要一些插图,因此就需要将图从 ob 中复制出来,再上传到公众号平台。

所以一直在找一键发布的方案。找到几个工具可以简化上述操作,但也都有各自问题。比如:

  • 使用壹伴工具进行公众号排版,其中预设了很多风格排版,还可以以插件方式在公众号网页中使用;但是它是收费的,且将 markdown 转为 HTML,以及处理图片的问题仍不可避免。
  • 使用开源工具 doocs-md 直接将 markdown 转成公众号格式的 HTML 也不错。它支持直接输入 markdown 格式,但也无法解决图片上传的问题,且风格相对有限。
  • 安装 Obsidian 插件 obsidian-wechat-public-platform,它可以自动上传图片,将 markdown 转换为 HTML,并保存到公众号草稿箱;但是排版比较单一,不太美观,需要自己修改 CSS 文件,还有一些小 bug。
  • 在没有配图的情况下,微信官方提供了 AI 生成配图功能。

2 核心功能

公众号排版工具确实提供了很多风格和功能,但仔细想想,一般情况下大家都是以输出内容为主,不会经常更换风格。排版公众号基本就是优化以下几点:

  • 各层标题(一般两层)
    • 字体颜色、背景色、字体、字号、居中、边框大小
  • 正文
    • 字体、字号、边框大小、行间距
  • 列表
    • 缩进、字体、字号、边框大小、行间距

其实并不多,一般维护两到三个适合不同场景的 CSS 文件就可以了。所以最后的方案是自行修改 CSS。配合 Obsidian-WeChat-Public-Platform,就可以实现一键发布了。

3 原理

obsidian-wechat-public-platform 使用 juice 库将样式内联到 HTML 中(juice.inlineContent)。在此过程中,设置了几部分 CSS,其中自定义样式位于最后,优先级最高。理论上只需修改 custom.css 即可。在该插件的设置界面中,可以下载最新的 CSS 并对其进行修改。

如需调试,可以修改代码,以输出修改后的 HTML 并进行查看(在 api.ts 的第 377 行附近可找到调试信息)。需要注意的是,由于该项目使用 TypeScript,需要安装 Node.js 环境。

在 CSS 语法方面,只需按照模板文件填入内容即可,请删除不需要设置的空内容。

4 方法

4.1 基础用法

  • 先在安装 obsidian-wechat-public-platform 插件
  • 在设置界面输入 appid 和 secret,点击 connect 获取 token,取自公众号开发平台:

  • 在待发布文章中设置文件头:
1
2
3
author: 作者名
open_comment: "0"
banner_path: 封面地址
  • Ctrl+P,选择 add draft to wechat platform

4.2 修改风格

  • 在设置中下载 custom.css 文件。
  • 下载后打开并编辑其内容,务必参考源码中的 wechatFormat.ts,里面有非常详细的中文设置说明,否则有些细节自己寻找可能会非常困难。
  • 编辑之后,为了使其立即生效,我关闭了 obsidian-wechat-public-platform 插件再打开,以便让其重新加载。
  • 通过 Ctrl+P 将当前文档发送到草稿箱即可。

5 进阶

有时候我们看到了比较舒服的排版,但不知道它的 css 具体是怎么写的,推荐以下的 js 代码,它可以从复制的代码段中拆出 style。

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
try {
const clipboardData = await navigator.clipboard.read();
for (const item of clipboardData) {
if (item.types.includes('text/html')) {
const blob = await item.getType('text/html');
const html = await blob.text();

// 创建一个临时的 DOM 元素来解析 HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;

console.log(html)

// 提取所有内联样式
const elementsWithStyle = tempDiv.querySelectorAll('[style]');
let styles = [];

elementsWithStyle.forEach(element => {
styles.push(element.getAttribute('style'));
});

// 去重并格式化样式
const uniqueStyles = […new Set(styles)];
const formattedStyles = uniqueStyles
.map(style => `样式: ${style}`)
.join('\n');

// 插入到编辑器
if (mdEditor.value && formattedStyles) {
mdEditor.value.insert(() => {
return {
targetValue: '\n提取的样式:\n```css\n' + formattedStyles + '\n```\n',
select: true,
deviationStart: 0,
deviationEnd: 0
};
});
isContentModified.value = true;
ElMessage.success('样式提取成功');
} else {
ElMessage.info('未发现样式');
}
return;
}
}
ElMessage.warning('剪贴板中没有富文本内容');
} catch (error) {
console.error('读取剪贴板失败:', error);
ElMessage.error('读取剪贴板失败: ' + error.message);
}

其原理是:当从网页上复制内容时,一般可以得到富文本格式,即带有格式的内容,粘贴时就可以得到格式信息。

6 相关知识

6.1 内联样式

直接写在 HTML 标签的 style 属性中的 CSS 样式,优先级最高。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<!-- 内联样式 -->
<div style="color: red; font-size: 16px;">
这是内联样式
</div>

<!-- 内部样式表 -->
<style>
.my-class {
color: red;
font-size: 16px;
}
</style>
<div class="my-class">这是内部样式表</div>

<!-- 外部样式表 -->
<!-- 在custom.css文件中 -->
.my-class {
color: red;
font-size: 16px;
}

6.2 css 选择器

``` /* ID选择器 - 选择id="nice"的元素 */ #nice { color: red; }

/* 类选择器 - 选择class="nice"的元素 */ .nice { color: blue; }

/* 标签选择器 - 选择所有

标签 */ div { color: green; }