hexo-text-pipeline 0.2.0 → 0.3.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
@@ -175,7 +175,7 @@ text_pipeline:
175
175
 
176
176
  ## Development
177
177
 
178
- Zero runtime dependencies, Node >= 16.
178
+ 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
179
 
180
180
  ```bash
181
181
  npm test # node --test
package/README.zh-CN.md CHANGED
@@ -175,7 +175,7 @@ text_pipeline:
175
175
 
176
176
  ## 开发
177
177
 
178
- 零运行时依赖,Node >= 16。
178
+ 唯一运行时依赖是 `hexo-util`(Hexo 本体自带,npm 会直接复用站点已有的那份,零额外安装成本)。Node >= 16。
179
179
 
180
180
  ```bash
181
181
  npm test # node --test
@@ -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.3.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
  }