hexo-text-pipeline 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -125,7 +125,7 @@ Each render cycle replaces the previous snapshot. Add the tap dir to `.gitignore
125
125
 
126
126
  ## Built-in preset: `obsidian`
127
127
 
128
- Compiles Obsidian Flavored Markdown for Hexo. Enable with `presets: [obsidian]`.
128
+ Compiles Obsidian Flavored Markdown for Hexo. Enable with `presets: [obsidian]` under the `text_pipeline:` key — full walkthrough in [docs/USING-PRESETS.md](docs/USING-PRESETS.md).
129
129
 
130
130
  | Node | Syntax | Default | Behavior |
131
131
  |------|--------|---------|----------|
@@ -139,13 +139,16 @@ Compiles Obsidian Flavored Markdown for Hexo. Enable with `presets: [obsidian]`.
139
139
  | `callout` | `> [!type] Title` | **off** | `<div class="callout callout-type">`; off because most renderers/themes already support callouts |
140
140
 
141
141
  ```yaml
142
- presets:
143
- - name: obsidian
144
- config:
145
- domain_prefix: '' # link prefix for wikilink/mdlink/embed
146
- callout: { enable: true } # opt in
147
- embed: { asset_prefix: /images } # prepended to embedded image paths
148
- mermaid: { theme: dark, priority: 15 } # any node: sub-config + priority override
142
+ text_pipeline:
143
+ presets:
144
+ - name: obsidian
145
+ config:
146
+ domain_prefix: '' # link prefix for wikilink/mdlink/embed
147
+ callout:
148
+ enable: true # opt in
149
+ css: ./source/css/callout.css # optional: replace the built-in styles with your own file(s)
150
+ embed: { asset_prefix: /images } # prepended to embedded image paths
151
+ mermaid: { theme: dark, priority: 15 } # any node: sub-config + priority override
149
152
  ```
150
153
 
151
154
  ## Installation
@@ -161,7 +164,7 @@ text_pipeline:
161
164
  enable: true # master switch
162
165
  debug: false # verbose logging
163
166
  strict: false # config errors / node failures fail the build (CI)
164
- inject_css: true # nodes' default styles (e.g. callout)
167
+ inject_css: true # nodes' default styles (e.g. callout); per-node `css: <file|list>` swaps in your own
165
168
  inject_js: true # nodes' frontend scripts (e.g. mermaid loader)
166
169
  presets: [] # built-in name | npm package | ./local/path | { name, config }
167
170
  hooks: [] # { script | command, stage, slot, priority, name, timeout, match, enable }
@@ -175,13 +178,14 @@ text_pipeline:
175
178
 
176
179
  ## Development
177
180
 
178
- Zero runtime dependencies, Node >= 16.
181
+ The only runtime dependency is `hexo-util` (ships with Hexo itself; npm dedupes to the copy your site already has, zero extra install cost). Node >= 16.
179
182
 
180
183
  ```bash
181
184
  npm test # node --test
182
185
  ```
183
186
 
184
187
  - **Single-file plugins (start here to write a plugin)**: [docs/PLUGINS.md](docs/PLUGINS.md)
188
+ - Using presets (enabling, config layers, debugging): [docs/USING-PRESETS.md](docs/USING-PRESETS.md)
185
189
  - Hooks API reference (stage inputs, ctx fields, debugging workflow): [docs/HOOKS-API.md](docs/HOOKS-API.md)
186
190
  - Architecture, stage table, node contract: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
187
191
  - Extending: hook vs preset node vs new preset: [docs/EXTENDING.md](docs/EXTENDING.md)
package/README.zh-CN.md CHANGED
@@ -125,7 +125,7 @@ text_pipeline:
125
125
 
126
126
  ## 内置 preset:`obsidian`
127
127
 
128
- 把 Obsidian Flavored Markdown 编译为 Hexo 友好输出。`presets: [obsidian]` 启用。
128
+ 把 Obsidian Flavored Markdown 编译为 Hexo 友好输出。在 `text_pipeline:` 键下写 `presets: [obsidian]` 启用——完整使用指南见 [docs/USING-PRESETS.zh-CN.md](docs/USING-PRESETS.zh-CN.md)。
129
129
 
130
130
  | Node | 语法 | 默认 | 行为 |
131
131
  |------|------|------|------|
@@ -139,13 +139,16 @@ text_pipeline:
139
139
  | `callout` | `> [!type] 标题` | **关** | `<div class="callout callout-type">`;主流渲染器/主题已多自带支持,所以默认关 |
140
140
 
141
141
  ```yaml
142
- presets:
143
- - name: obsidian
144
- config:
145
- domain_prefix: '' # wikilink/mdlink/embed 的链接前缀
146
- callout: { enable: true } # 按需打开
147
- embed: { asset_prefix: /images } # 嵌入图片路径的前缀
148
- mermaid: { theme: dark, priority: 15 } # 任意 node:子配置 + priority 覆盖
142
+ text_pipeline:
143
+ presets:
144
+ - name: obsidian
145
+ config:
146
+ domain_prefix: '' # wikilink/mdlink/embed 的链接前缀
147
+ callout:
148
+ enable: true # 按需打开
149
+ css: ./source/css/callout.css # 可选:用自己的样式文件整体替换内置样式
150
+ embed: { asset_prefix: /images } # 嵌入图片路径的前缀
151
+ mermaid: { theme: dark, priority: 15 } # 任意 node:子配置 + priority 覆盖
149
152
  ```
150
153
 
151
154
  ## 安装
@@ -161,7 +164,7 @@ text_pipeline:
161
164
  enable: true # 总开关
162
165
  debug: false # 详细日志
163
166
  strict: false # 配置错误 / 节点失败让构建失败(CI 用)
164
- inject_css: true # node 的默认样式(如 callout
167
+ inject_css: true # node 的默认样式(如 callout);node 子配置 css: <文件|列表> 可换成自己的
165
168
  inject_js: true # node 的前端脚本(如 mermaid 加载器)
166
169
  presets: [] # 内置名 | npm 包 | ./本地路径 | { name, config }
167
170
  hooks: [] # { script | command, stage, slot, priority, name, timeout, match, enable }
@@ -175,13 +178,14 @@ text_pipeline:
175
178
 
176
179
  ## 开发
177
180
 
178
- 零运行时依赖,Node >= 16。
181
+ 唯一运行时依赖是 `hexo-util`(Hexo 本体自带,npm 会直接复用站点已有的那份,零额外安装成本)。Node >= 16。
179
182
 
180
183
  ```bash
181
184
  npm test # node --test
182
185
  ```
183
186
 
184
187
  - **单文件插件(写插件从这里开始)**:[docs/PLUGINS.zh-CN.md](docs/PLUGINS.zh-CN.md)
188
+ - 使用 preset(启用、配置分层、调试):[docs/USING-PRESETS.zh-CN.md](docs/USING-PRESETS.zh-CN.md)
185
189
  - Hooks 接口文档(stage 输入、ctx 字段、调试工作流):[docs/HOOKS-API.zh-CN.md](docs/HOOKS-API.zh-CN.md)
186
190
  - 架构、stage 表、node 契约:[docs/ARCHITECTURE.zh-CN.md](docs/ARCHITECTURE.zh-CN.md)
187
191
  - 扩展指南:hook vs preset node vs 新 preset:[docs/EXTENDING.zh-CN.md](docs/EXTENDING.zh-CN.md)
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
3
5
  const { normalizeConfig } = require('./config');
4
6
  const { STAGES, LATE_FILTER_PRIORITY } = require('./stages');
5
7
  const { createRegistry, createPublicApi } = require('./api');
@@ -107,8 +109,22 @@ function register(hexo) {
107
109
  const canInject = hexo.extend.injector && typeof hexo.extend.injector.register === 'function';
108
110
  const injectAssets = (node) => {
109
111
  if (!canInject) return;
110
- if (pluginConfig.injectCss && node.css) {
111
- hexo.extend.injector.register('head_end', '<style>' + node.css + '</style>');
112
+ const sub = node.config || {};
113
+ if (pluginConfig.injectCss) {
114
+ // 用户样式文件(css: 路径 | 路径列表,相对 Hexo 根目录)直接替换 node 的默认样式
115
+ const cssFiles = typeof sub.css === 'string' ? [sub.css] : Array.isArray(sub.css) ? sub.css : [];
116
+ if (cssFiles.length) {
117
+ for (const file of cssFiles) {
118
+ try {
119
+ const userCss = fs.readFileSync(path.resolve(baseDir || process.cwd(), String(file)), 'utf8');
120
+ hexo.extend.injector.register('head_end', '<style>' + userCss + '</style>');
121
+ } catch (err) {
122
+ engineLog.warn('node "' + node.name + '" css file not readable: ' + file + ' (' + (err && err.message) + ')');
123
+ }
124
+ }
125
+ } else if (node.css) {
126
+ hexo.extend.injector.register('head_end', '<style>' + node.css + '</style>');
127
+ }
112
128
  }
113
129
  if (pluginConfig.injectJs && node.js) {
114
130
  const js = typeof node.js === 'function' ? node.js(node.config || {}) : node.js;
@@ -1,8 +1,9 @@
1
1
  'use strict';
2
2
 
3
3
  /**
4
- * 默认样式,经 hexo injector 注入 head_end;text_pipeline.inject_css: false 可关闭,
5
- * 关闭后结构类名(callout / callout-title / callout-content / data-callout)由主题自行接管。
4
+ * 默认样式(色板参照 Obsidian 默认主题),经 hexo injector 注入 head_end
5
+ * 子配置 css: <文件路径|列表> 可用自己的样式整体替换这份默认;全局 text_pipeline.inject_css: false 全关。
6
+ * 替换/关闭后结构类名(callout / callout-title / callout-content / data-callout)由用户样式或主题接管。
6
7
  */
7
8
  module.exports = `
8
9
  .callout{--callout-color:68,138,255;border-left:4px solid rgb(var(--callout-color));border-radius:4px;padding:.75rem 1rem;margin:1rem 0;background:rgba(var(--callout-color),.08)}
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ const { escapeHTML } = require('hexo-util');
4
+
3
5
  const DEFAULT_SCRIPT_SRC = 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js';
4
6
  const DEFAULT_CLASS = 'mermaid';
5
7
 
@@ -11,10 +13,6 @@ const DEFAULT_CLASS = 'mermaid';
11
13
  * 前端脚本默认按需注入(页面没有 .mermaid 元素时不加载 CDN):
12
14
  * converters.mermaid 子配置:class / inject_script / script_src / theme。
13
15
  */
14
- function escapeHtml(text) {
15
- return text.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
16
- }
17
-
18
16
  function transformMermaidBlocks(content, className) {
19
17
  const lines = content.split('\n');
20
18
  const out = [];
@@ -42,7 +40,7 @@ function transformMermaidBlocks(content, className) {
42
40
  body.push(lines[j]);
43
41
  }
44
42
  if (closed) {
45
- out.push('<pre class="' + className + '">' + escapeHtml(body.join('\n')) + '</pre>');
43
+ out.push('<pre class="' + className + '">' + escapeHTML(body.join('\n')) + '</pre>');
46
44
  i = j;
47
45
  continue;
48
46
  }
@@ -1,5 +1,6 @@
1
1
  'use strict';
2
2
 
3
+ const { slugize } = require('hexo-util');
3
4
  const { replaceOutsideCode } = require('../../../../core/markdown-guard');
4
5
  const { normalizeBase } = require('../../../../core/config');
5
6
  const { getPostIndex, resolvePostByTarget, buildPostHref } = require('../../post-index');
@@ -11,6 +12,8 @@ const { getPostIndex, resolvePostByTarget, buildPostHref } = require('../../post
11
12
  * - ![[...]] 是嵌入,归 embed node 管,这里用 lookbehind 跳过
12
13
  * - #^block-id 块引用锚点在渲染后的 HTML 里不存在,丢弃锚点只留文章链接
13
14
  * - [[#标题]](无 target 的同页链接)改写为页内锚点
15
+ * - 锚点用 hexo-util 的 slugize 生成——与 hexo-renderer-marked 给标题生成 id
16
+ * 的是同一个函数(含 marked.modifyAnchors 配置),保证页内跳转不断
14
17
  */
15
18
  function parseWikiLink(raw) {
16
19
  const firstPipe = raw.indexOf('|');
@@ -24,7 +27,14 @@ function parseWikiLink(raw) {
24
27
  return { target, anchor, alias };
25
28
  }
26
29
 
27
- function createWikiLinkReplacer(index, domainPrefix) {
30
+ // hexo-renderer-marked 的标题 id 同源:slugize + modifyAnchors(1 小写 / 2 大写)。
31
+ // slug 再过 encodeURIComponent 保证 markdown 链接语法安全(中文等百分号编码后,
32
+ // 浏览器解码 fragment 仍命中原 id)。
33
+ function anchorToHash(anchor, anchorTransform) {
34
+ return '#' + encodeURIComponent(slugize(anchor, { transform: anchorTransform }));
35
+ }
36
+
37
+ function createWikiLinkReplacer(index, domainPrefix, anchorTransform) {
28
38
  return function replaceWikiLinks(segment) {
29
39
  return segment.replace(/(?<!!)\[\[([^\]]+)\]\]/g, (full, inner) => {
30
40
  const parsed = parseWikiLink(inner);
@@ -32,7 +42,7 @@ function createWikiLinkReplacer(index, domainPrefix) {
32
42
  // 同页链接 [[#标题]]:没有 target,只有锚点
33
43
  if (!parsed.target) {
34
44
  if (!parsed.anchor || parsed.anchor.startsWith('^')) return full;
35
- return '[' + (parsed.alias || parsed.anchor) + '](#' + encodeURIComponent(parsed.anchor) + ')';
45
+ return '[' + (parsed.alias || parsed.anchor) + '](' + anchorToHash(parsed.anchor, anchorTransform) + ')';
36
46
  }
37
47
 
38
48
  const post = resolvePostByTarget(index, parsed.target);
@@ -42,7 +52,8 @@ function createWikiLinkReplacer(index, domainPrefix) {
42
52
  if (!href) return full;
43
53
 
44
54
  // 块引用锚点(#^id)在 HTML 里没有对应元素,降级为文章链接
45
- const anchor = parsed.anchor && !parsed.anchor.startsWith('^') ? '#' + encodeURIComponent(parsed.anchor) : '';
55
+ const anchor =
56
+ parsed.anchor && !parsed.anchor.startsWith('^') ? anchorToHash(parsed.anchor, anchorTransform) : '';
46
57
  const text = parsed.alias || parsed.target;
47
58
 
48
59
  return '[' + text + '](' + href + anchor + ')';
@@ -58,7 +69,12 @@ module.exports = {
58
69
  },
59
70
  convert(content, ctx) {
60
71
  const index = getPostIndex(ctx.hexo);
61
- const replacer = createWikiLinkReplacer(index, normalizeBase(ctx.presetConfig.domain_prefix));
72
+ const marked = (ctx.hexo.config && ctx.hexo.config.marked) || {};
73
+ const replacer = createWikiLinkReplacer(
74
+ index,
75
+ normalizeBase(ctx.presetConfig.domain_prefix),
76
+ marked.modifyAnchors
77
+ );
62
78
  return replaceOutsideCode(content, replacer);
63
79
  },
64
80
  _internal: { parseWikiLink, createWikiLinkReplacer }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hexo-text-pipeline",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "A general-purpose hooks bus for Hexo's render pipeline: hang your own scripts/commands on any text stage (text in, text out, edit-and-use), with a checker system as the safety net. Ships an Obsidian Flavored Markdown preset.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -37,5 +37,8 @@
37
37
  },
38
38
  "engines": {
39
39
  "node": ">=16"
40
+ },
41
+ "dependencies": {
42
+ "hexo-util": "^2.7.0 || ^3.0.0 || ^4.0.0"
40
43
  }
41
44
  }