hexo-theme-gnix 12.0.0 → 14.0.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.
Files changed (45) hide show
  1. package/README.md +2 -0
  2. package/include/hexo/generator/archive.js +14 -1
  3. package/include/hexo/generator/index.js +0 -5
  4. package/include/hexo/generator/page.js +18 -4
  5. package/include/hexo/generator/tag.js +1 -1
  6. package/include/hexo/helper.js +0 -4
  7. package/include/hexo/i18n.js +31 -136
  8. package/include/hexo/obsidian-callouts.js +210 -0
  9. package/include/hexo/renderer.js +4 -14
  10. package/include/hexo/shiki.js +191 -0
  11. package/include/hexo/sitemap.js +184 -0
  12. package/include/util/i18n.js +92 -106
  13. package/languages/en.yml +4 -10
  14. package/languages/zh-CN.yml +4 -10
  15. package/layout/archive.jsx +155 -78
  16. package/layout/common/article.jsx +94 -108
  17. package/layout/common/article_cover.jsx +3 -3
  18. package/layout/common/article_info.jsx +11 -48
  19. package/layout/common/article_media.jsx +9 -2
  20. package/layout/common/footer.jsx +17 -106
  21. package/layout/common/head.jsx +3 -15
  22. package/layout/common/navbar.jsx +24 -87
  23. package/layout/common/scripts.jsx +1 -1
  24. package/layout/layout.jsx +37 -19
  25. package/layout/plugin/goatcounter.jsx +25 -0
  26. package/layout/tag.jsx +3 -70
  27. package/layout/tags.jsx +26 -23
  28. package/package.json +7 -13
  29. package/scripts/index.js +1 -0
  30. package/source/css/archive.css +287 -168
  31. package/source/css/callout_blocks.css +41 -21
  32. package/source/css/default.css +154 -132
  33. package/source/css/optional/mermaid.css +12 -6
  34. package/source/css/responsive.css +1 -45
  35. package/source/css/shiki/shiki.css +5 -4
  36. package/source/css/tags.css +53 -59
  37. package/source/js/components/archive-popup.js +313 -0
  38. package/source/js/components/friends-list.js +270 -0
  39. package/source/js/components/x-info-card.js +297 -0
  40. package/source/js/main.js +38 -34
  41. package/source/js/mdit/mermaid.js +10 -0
  42. package/include/hexo/generator/home.js +0 -64
  43. package/layout/index.jsx +0 -19
  44. package/layout/misc/paginator.jsx +0 -69
  45. package/source/js/host/iconify-icon/3.0.2/iconify-icon.min.js +0 -12
package/README.md CHANGED
@@ -60,5 +60,7 @@ Support multiple light and dark themes:
60
60
  - [ppoffice/hexo-theme-icarus: A simple, delicate, and modern theme for the static site generator Hexo.](https://github.com/ppoffice/hexo-theme-icarus)
61
61
  - [D0n9X1n/hexo-blog-encrypt: Yet, just another hexo plugin for security.](https://github.com/D0n9X1n/hexo-blog-encrypt)
62
62
  - [hexojs/hexo-generator-feed: Feed generator for Hexo.](https://github.com/hexojs/hexo-generator-feed)
63
+ - [hexojs/hexo-generator-sitemap: Sitemap generator for Hexo.](https://github.com/hexojs/hexo-generator-sitemap)
63
64
  - [hexojs/hexo-generator-tag: Tag generator for Hexo.](https://github.com/hexojs/hexo-generator-tag)
64
65
  - [hexojs/hexo-generator-archive: Archive generator for Hexo.](https://github.com/hexojs/hexo-generator-archive)
66
+ - [ebullient/markdown-it-obsidian-callouts: markdown-it plugin for GitHub and Obsidian callouts.](https://github.com/ebullient/markdown-it-obsidian-callouts?tab=Apache-2.0-1-ov-file)
@@ -1,8 +1,13 @@
1
1
  const pagination = require("hexo-pagination");
2
- const { filterByLanguage, getLanguage, getLanguageBasePath, getLanguageKeys, isI18nEnabled } = require("../../util/i18n");
2
+ const { filterByLanguage, getDefaultLanguageKey, getLanguage, getLanguageBasePath, getLanguageKeys, isI18nEnabled } = require("../../util/i18n");
3
3
 
4
4
  const fmtNum = (num) => num.toString().padStart(2, "0");
5
5
 
6
+ function redirectTo(path) {
7
+ const target = path.startsWith("/") ? path : `/${path}`;
8
+ return `<!doctype html><html><head><meta charset="utf-8"><meta name="robots" content="noindex"><meta http-equiv="refresh" content="0;url=${target}"><link rel="canonical" href="${target}"></head><body><a href="${target}">Continue</a></body></html>`;
9
+ }
10
+
6
11
  module.exports = (hexo) => {
7
12
  hexo.extend.generator.register("archive", function (locals) {
8
13
  const { config } = this;
@@ -44,6 +49,7 @@ module.exports = (hexo) => {
44
49
  : {};
45
50
 
46
51
  generate(baseArchiveDir, allPosts, languageData);
52
+ generate(languageBase || "", allPosts, languageData);
47
53
 
48
54
  const yearly = themeConfig.yearly ?? true;
49
55
  const monthly = themeConfig.monthly ?? true;
@@ -106,6 +112,13 @@ module.exports = (hexo) => {
106
112
  }
107
113
 
108
114
  if (isI18nEnabled(fullConfig)) {
115
+ const defaultLanguageBase = getLanguageBasePath(fullConfig, getDefaultLanguageKey(fullConfig));
116
+ if (defaultLanguageBase) {
117
+ result.push({
118
+ path: "index.html",
119
+ data: redirectTo(defaultLanguageBase),
120
+ });
121
+ }
109
122
  getLanguageKeys(fullConfig).forEach((langKey) => generateLanguageArchives(langKey));
110
123
  return result;
111
124
  }
@@ -73,10 +73,5 @@ module.exports = (hexo) => {
73
73
  require("./archive")(hexo);
74
74
  }
75
75
 
76
- const indexConfig = hexo.theme.config.index_generator || {};
77
- if (indexConfig.enabled !== false) {
78
- require("./home")(hexo);
79
- }
80
-
81
76
  require("./page")(hexo);
82
77
  };
@@ -1,11 +1,25 @@
1
- const { getI18nKey, getLanguage, getLanguageBasePath, getPageLanguageKey, inferI18nKeyFromSource, isI18nEnabled, localizePath, trimSlashes } = require("../../util/i18n");
1
+ const {
2
+ getI18nKey,
3
+ getLanguage,
4
+ getLanguageBasePath,
5
+ getPageLanguageKey,
6
+ inferI18nKeyFromSource,
7
+ isI18nEnabled,
8
+ localizePath,
9
+ parseLocalizedSource,
10
+ trimSlashes,
11
+ } = require("../../util/i18n");
2
12
 
3
13
  function getLocalizedPagePath(page, langKey, config) {
4
- const sourcePrefix = `${trimSlashes(langKey)}/`;
5
14
  let route = page.path || "";
6
15
 
7
- if (route.startsWith(sourcePrefix)) {
8
- route = route.slice(sourcePrefix.length);
16
+ const parsed = parseLocalizedSource(page.source || "");
17
+ if (parsed.langKey) {
18
+ const suffix = `__${parsed.langKey}`;
19
+ const escapedSuffix = suffix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
20
+ // Strip the __<lang> suffix when followed by '/', '.', or end of string
21
+ // e.g. "about__en/index.html" → "about/index.html"; "about__en.html" → "about.html"
22
+ route = route.replace(new RegExp(`${escapedSuffix}(?=[/.]|$)`), "");
9
23
  }
10
24
 
11
25
  return trimSlashes(localizePath(`/${route}`, langKey, config));
@@ -6,7 +6,7 @@ module.exports = (hexo) => {
6
6
  const config = this.config;
7
7
  const fullConfig = Object.assign({}, config, config.theme_config, hexo.theme.config);
8
8
  const themeConfig = hexo.theme.config.tag_generator || {};
9
- const perPage = themeConfig.per_page ?? 10;
9
+ const perPage = themeConfig.per_page ?? 0;
10
10
  const orderBy = themeConfig.order_by ?? "-date";
11
11
  const paginationDir = config.pagination_dir || "page";
12
12
  const tags = locals.tags;
@@ -14,7 +14,6 @@ function getCDN(cdn, pkg, version, filename) {
14
14
  }
15
15
 
16
16
  const {
17
- getLanguageLabel,
18
17
  getPageLanguageKey,
19
18
  getPageLocale,
20
19
  getLocalizedTagPath,
@@ -40,9 +39,6 @@ module.exports = (hexo) => {
40
39
  hexo.extend.helper.register("language_locale", function (page = null) {
41
40
  return getPageLocale(page || this.page, this.config);
42
41
  });
43
- hexo.extend.helper.register("language_label", function (keyOrLocale) {
44
- return getLanguageLabel(this.config, keyOrLocale);
45
- });
46
42
  hexo.extend.helper.register("localized_path", function (targetPath, langKey = null) {
47
43
  return localizePath(targetPath, langKey || getPageLanguageKey(this.page, this.config), this.config);
48
44
  });
@@ -1,144 +1,44 @@
1
- const { extname } = require("node:path");
2
- const createPostProcessor = require("hexo/dist/plugins/processor/post");
3
- const { isHiddenFile, isMatch, isTmpFile } = require("hexo/dist/plugins/processor/common");
4
1
  const {
5
2
  getI18nKey,
6
- getLanguageBasePath,
7
3
  getLanguage,
8
- getLanguageKeys,
4
+ getLanguageBasePath,
9
5
  getPageLanguageKey,
10
6
  inferI18nKeyFromSource,
11
7
  isI18nEnabled,
12
- localizePath,
13
- trimSlashes,
8
+ parseLocalizedSource,
14
9
  } = require("../util/i18n");
15
10
 
16
- function getConfig(hexo, locals = {}) {
17
- return Object.assign({}, hexo.config, hexo.config.theme_config, hexo.theme.config, locals.theme);
18
- }
19
-
20
- function getLocalizedPostParams(hexo, sourcePath) {
21
- const config = getConfig(hexo);
22
- if (!isI18nEnabled(config) || isTmpFile(sourcePath)) return null;
23
-
24
- const match = sourcePath.replace(/\\/g, "/").match(/^([^/]+)\/(_posts|_drafts)\/(.+)$/);
25
- if (!match) return null;
26
-
27
- const langKey = match[1];
28
- if (!getLanguageKeys(config).includes(langKey)) return null;
29
-
30
- const postPath = match[3];
31
- if (isHiddenFile(postPath)) return null;
32
-
33
- let renderable = hexo.render.isRenderable(sourcePath) && !isMatch(sourcePath, hexo.config.skip_render);
34
- if (renderable && hexo.config.post_asset_folder) {
35
- renderable = extname(hexo.config.new_post_name) === extname(sourcePath);
36
- }
37
-
38
- return {
39
- i18n_lang: langKey,
40
- path: postPath,
41
- published: match[2] === "_posts",
42
- renderable,
43
- };
44
- }
45
-
46
- function getLocalizedPageParams(hexo, sourcePath) {
47
- const config = getConfig(hexo);
48
- if (!isI18nEnabled(config) || isTmpFile(sourcePath)) return null;
49
-
50
- const normalized = sourcePath.replace(/\\/g, "/");
51
- const match = normalized.match(/^([^/]+)\/(.+)$/);
52
- if (!match) return null;
53
-
54
- const langKey = match[1];
55
- if (!getLanguageKeys(config).includes(langKey)) return null;
11
+ let cachedConfig = null;
12
+ let cachedHexo = null;
56
13
 
57
- const pagePath = match[2];
58
- if (pagePath.startsWith("_posts/") || pagePath.startsWith("_drafts/") || isHiddenFile(pagePath)) return null;
59
-
60
- const renderable = hexo.render.isRenderable(sourcePath) && !isMatch(sourcePath, hexo.config.skip_render);
61
- if (!renderable) return null;
62
-
63
- return {
64
- i18n_lang: langKey,
65
- };
66
- }
67
-
68
- function buildPostI18nUpdate(post, langKey, config) {
69
- const language = getLanguage(config, langKey);
70
- const update = {
71
- i18n_path: getLanguageBasePath(config, langKey),
72
- i18n_lang: langKey,
73
- lang: language.locale,
74
- language: language.locale,
75
- };
76
-
77
- if (!post.i18n_key) {
78
- update.i18n_key = getI18nKey(post) || inferI18nKeyFromSource(post.source);
14
+ function getConfig(hexo, locals = {}) {
15
+ if (hexo !== cachedHexo || !cachedConfig) {
16
+ cachedHexo = hexo;
17
+ cachedConfig = Object.assign({}, hexo.config, hexo.config.theme_config, hexo.theme.config);
79
18
  }
80
-
81
- return { $set: update };
82
- }
83
-
84
- function getLocalizedPagePath(page, langKey, config) {
85
- const sourcePrefix = `${trimSlashes(langKey)}/`;
86
- let route = page.path || "";
87
-
88
- if (route.startsWith(sourcePrefix)) {
89
- route = route.slice(sourcePrefix.length);
19
+ if (locals.theme) {
20
+ return Object.assign({}, cachedConfig, locals.theme);
90
21
  }
91
-
92
- return trimSlashes(localizePath(`/${route}`, langKey, config));
22
+ return cachedConfig;
93
23
  }
94
24
 
95
- function buildPageI18nUpdate(page, langKey, config) {
25
+ function applyI18nFields(item, config) {
26
+ const langKey = getPageLanguageKey(item, config);
96
27
  const language = getLanguage(config, langKey);
97
- const update = {
98
- i18n_path: getLanguageBasePath(config, langKey),
99
- i18n_lang: langKey,
100
- lang: language.locale,
101
- language: language.locale,
102
- path: getLocalizedPagePath(page, langKey, config),
103
- };
104
28
 
105
- if (!page.i18n_key) {
106
- update.i18n_key = getI18nKey(page) || inferI18nKeyFromSource(page.source);
29
+ item.i18n_lang = item.i18n_lang || langKey;
30
+ item.i18n_path = getLanguageBasePath(config, langKey);
31
+ item.lang = language.locale;
32
+ item.language = language.locale;
33
+
34
+ if (!item.i18n_key) {
35
+ item.i18n_key = getI18nKey(item) || inferI18nKeyFromSource(item.source);
107
36
  }
108
37
 
109
- return { $set: update };
38
+ return langKey;
110
39
  }
111
40
 
112
41
  module.exports = (hexo) => {
113
- const defaultPostProcessor = createPostProcessor(hexo);
114
-
115
- hexo.extend.processor.register(
116
- (sourcePath) => getLocalizedPostParams(hexo, sourcePath),
117
- (file) =>
118
- Promise.resolve(defaultPostProcessor.process(file)).then(() => {
119
- if (!isI18nEnabled(getConfig(hexo))) return null;
120
- if (!file.params.renderable) return null;
121
-
122
- const post = hexo.model("Post").findOne({ source: file.path });
123
- if (!post) return null;
124
-
125
- return post.update(buildPostI18nUpdate(post, file.params.i18n_lang, getConfig(hexo)));
126
- }),
127
- );
128
-
129
- hexo.extend.processor.register(
130
- (sourcePath) => getLocalizedPageParams(hexo, sourcePath),
131
- (file) => {
132
- if (!isI18nEnabled(getConfig(hexo))) return null;
133
- if (file.type === "delete") return null;
134
-
135
- const page = hexo.model("Page").findOne({ source: file.path });
136
- if (!page) return null;
137
-
138
- return page.update(buildPageI18nUpdate(page, file.params.i18n_lang, getConfig(hexo)));
139
- },
140
- );
141
-
142
42
  hexo.extend.filter.register(
143
43
  "post_permalink",
144
44
  (post) => {
@@ -146,14 +46,16 @@ module.exports = (hexo) => {
146
46
  const activeConfig = getConfig(hexo);
147
47
  if (!isI18nEnabled(activeConfig)) return post;
148
48
 
149
- const langKey = getPageLanguageKey(post, activeConfig);
150
- const language = getLanguage(activeConfig, langKey);
49
+ applyI18nFields(post, activeConfig);
151
50
 
152
- post.i18n_lang = post.i18n_lang || langKey;
153
- post.i18n_path = getLanguageBasePath(activeConfig, langKey);
154
- post.lang = language.locale;
155
- post.language = language.locale;
156
- post.i18n_key = post.i18n_key || getI18nKey(post) || inferI18nKeyFromSource(post.source);
51
+ // permalink 模板里的 :name 取到 baseName 而非 foo__en
52
+ if (post.source) {
53
+ const parsed = parseLocalizedSource(post.source);
54
+ if (parsed.langKey) {
55
+ const baseName = inferI18nKeyFromSource(post.source);
56
+ if (baseName) post.slug = baseName;
57
+ }
58
+ }
157
59
 
158
60
  return post;
159
61
  },
@@ -167,14 +69,7 @@ module.exports = (hexo) => {
167
69
  const activeConfig = getConfig(hexo, locals);
168
70
  if (!page || !isI18nEnabled(activeConfig)) return locals;
169
71
 
170
- const langKey = getPageLanguageKey(page, activeConfig);
171
- const language = getLanguage(activeConfig, langKey);
172
-
173
- page.i18n_lang = page.i18n_lang || langKey;
174
- page.i18n_path = getLanguageBasePath(activeConfig, langKey);
175
- page.lang = language.locale;
176
- page.language = language.locale;
177
- page.i18n_key = page.i18n_key || getI18nKey(page) || inferI18nKeyFromSource(page.source);
72
+ applyI18nFields(page, activeConfig);
178
73
 
179
74
  return locals;
180
75
  },
@@ -0,0 +1,210 @@
1
+ // Adapted from markdown-it-obsidian-callouts (Apache-2.0, (c) Erin Schnabel)
2
+ // https://github.com/ebullient/markdown-it-obsidian-callouts
3
+
4
+ const DEFAULT_ICONS = {
5
+ abstract: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-clipboard-list"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><path d="M12 11h4"/><path d="M12 16h4"/><path d="M8 11h.01"/><path d="M8 16h.01"/></svg>',
6
+ bug: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bug"><path d="m8 2 1.88 1.88"/><path d="M14.12 3.88 16 2"/><path d="M9 7.13v-1a3.003 3.003 0 1 1 6 0v1"/><path d="M12 20c-3.3 0-6-2.7-6-6v-3a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v3c0 3.3-2.7 6-6 6"/><path d="M12 20v-9"/><path d="M6.53 9C4.6 8.8 3 7.1 3 5"/><path d="M6 13H2"/><path d="M3 21c0-2.1 1.7-3.9 3.8-4"/><path d="M20.97 5c0 2.1-1.6 3.8-3.5 4"/><path d="M22 13h-4"/><path d="M17.2 17c2.1.1 3.8 1.9 3.8 4"/></svg>',
7
+ danger: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-zap"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>',
8
+ example: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-list"><line x1="8" x2="21" y1="6" y2="6"/><line x1="8" x2="21" y1="12" y2="12"/><line x1="8" x2="21" y1="18" y2="18"/><line x1="3" x2="3.01" y1="6" y2="6"/><line x1="3" x2="3.01" y1="12" y2="12"/><line x1="3" x2="3.01" y1="18" y2="18"/></svg>',
9
+ failure: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x"><path d="M18 6 6 18"/><path d="m6 6 12 12"/></svg>',
10
+ info: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-info"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>',
11
+ note: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-pencil"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>',
12
+ question: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-help-circle"><circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/><path d="M12 17h.01"/></svg>',
13
+ quote: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-quote"><path d="M3 21c3 0 7-1 7-8V5c0-1.25-.756-2.017-2-2H4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2 1 0 1 0 1 1v1c0 1-1 2-2 2s-1 .008-1 1.031V20c0 1 0 1 1 1z"/><path d="M15 21c3 0 7-1 7-8V5c0-1.25-.757-2.017-2-2h-4c-1.25 0-2 .75-2 1.972V11c0 1.25.75 2 2 2h.75c0 2.25.25 4-2.75 4v3c0 1 0 1 1 1z"/></svg>',
14
+ success: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check"><path d="M20 6 9 17l-5-5"/></svg>',
15
+ tip: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-flame"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg>',
16
+ todo: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-check-circle-2"><circle cx="12" cy="12" r="10"/><path d="m9 12 2 2 4-4"/></svg>',
17
+ warning: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-alert-triangle"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>',
18
+ };
19
+
20
+ DEFAULT_ICONS.attention = DEFAULT_ICONS.warning;
21
+ DEFAULT_ICONS.caution = DEFAULT_ICONS.warning;
22
+ DEFAULT_ICONS.check = DEFAULT_ICONS.success;
23
+ DEFAULT_ICONS.cite = DEFAULT_ICONS.quote;
24
+ DEFAULT_ICONS.done = DEFAULT_ICONS.success;
25
+ DEFAULT_ICONS.error = DEFAULT_ICONS.danger;
26
+ DEFAULT_ICONS.fail = DEFAULT_ICONS.failure;
27
+ DEFAULT_ICONS.faq = DEFAULT_ICONS.question;
28
+ DEFAULT_ICONS.help = DEFAULT_ICONS.question;
29
+ DEFAULT_ICONS.hint = DEFAULT_ICONS.tip;
30
+ DEFAULT_ICONS.important = DEFAULT_ICONS.tip;
31
+ DEFAULT_ICONS.missing = DEFAULT_ICONS.failure;
32
+ DEFAULT_ICONS.summary = DEFAULT_ICONS.abstract;
33
+ DEFAULT_ICONS.tldr = DEFAULT_ICONS.abstract;
34
+
35
+ const CALLOUT_RE = /^\[!([^\]]+)\](\+|-|) *(.*)? */;
36
+ const ADMONITION_RE = /^ad-([^\s]+) */;
37
+ const ADMONITION_HEADER_RE = /^(title|collapse|icon|color):(.*)/;
38
+ const HEADER_TO_ATTR = {
39
+ title: "data-callout-title",
40
+ icon: "data-callout-icon",
41
+ color: "data-callout-color",
42
+ };
43
+
44
+ function resolveIcon(token, options) {
45
+ const explicit = token.attrGet("data-callout-icon");
46
+ if (explicit) return explicit.trim();
47
+ const type = token.attrGet("data-callout");
48
+ if (!type) return "";
49
+ return options.icons?.[type] || DEFAULT_ICONS[type] || DEFAULT_ICONS.note;
50
+ }
51
+
52
+ function toTitleCase(str) {
53
+ return str
54
+ .split(" ")
55
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
56
+ .join(" ");
57
+ }
58
+
59
+ async function resolveTitle(token, md) {
60
+ const title = token.attrGet("data-callout-title");
61
+ if (title) return md.renderInlineAsync(title.trim());
62
+ const type = token.attrGet("data-callout");
63
+ return type ? toTitleCase(type) : "";
64
+ }
65
+
66
+ function escapeAttr(str) {
67
+ return String(str).replaceAll("&", "&amp;").replaceAll('"', "&quot;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
68
+ }
69
+
70
+ function colorStyle(token) {
71
+ const color = token.attrGet("data-callout-color");
72
+ return color ? ` style="--callout-color: ${escapeAttr(color)}"` : "";
73
+ }
74
+
75
+ async function renderCalloutPrefix(token, md, options) {
76
+ const type = token.attrGet("data-callout");
77
+ if (!type) return "";
78
+ const fold = token.attrGet("data-callout-fold");
79
+ const icon = resolveIcon(token, options);
80
+ const title = await resolveTitle(token, md);
81
+ const style = colorStyle(token);
82
+
83
+ if (fold) {
84
+ return `
85
+ <details class="callout" data-callout="${type}" data-callout-fold="${fold}"${fold === "+" ? " open" : ""}${style}>
86
+ <summary class="callout-title">
87
+ <div class="callout-title-icon">
88
+ ${icon}
89
+ </div>
90
+ <div class="callout-title-inner">${title}</div>
91
+ <div class="callout-fold"></div>
92
+ </summary>
93
+ <div class="callout-content">`;
94
+ }
95
+
96
+ return `
97
+ <div class="callout" data-callout="${type}"${style}>
98
+ <div class="callout-title">
99
+ <div class="callout-title-icon">
100
+ ${icon}
101
+ </div>
102
+ <div class="callout-title-inner">${title}</div>
103
+ </div>
104
+ <div class="callout-content">`;
105
+ }
106
+
107
+ function renderCalloutPostfix(token) {
108
+ const type = token.attrGet("data-callout");
109
+ if (!type) return "";
110
+ const fold = token.attrGet("data-callout-fold");
111
+ return fold ? "</div></details>" : "</div></div>";
112
+ }
113
+
114
+ function inspectBlockquote(tokens, startIdx) {
115
+ let content = "";
116
+ let depth = 0;
117
+ let endIdx = startIdx;
118
+ let contentIdx = startIdx;
119
+
120
+ for (let i = startIdx; i < tokens.length; i++) {
121
+ const token = tokens[i];
122
+
123
+ if (token.type === "blockquote_open") depth++;
124
+ else if (token.type === "blockquote_close") {
125
+ endIdx = i;
126
+ depth--;
127
+ }
128
+
129
+ if (depth === 0) break;
130
+ if (depth > 1) continue;
131
+
132
+ if (token.type === "inline") {
133
+ if (contentIdx === startIdx && token.content.match(CALLOUT_RE)) {
134
+ contentIdx = i;
135
+ }
136
+ content += token.content;
137
+ } else if (token.type === "paragraph_close") {
138
+ content += "\n";
139
+ }
140
+ }
141
+
142
+ const match = content.match(CALLOUT_RE);
143
+ if (!match || startIdx === endIdx) return;
144
+
145
+ const type = match[1].toLowerCase();
146
+ const fold = match[2];
147
+ const title = match[3];
148
+
149
+ tokens[startIdx].type = "callout_open";
150
+ tokens[startIdx].attrPush(["class", "callout"]);
151
+ tokens[startIdx].attrPush(["data-callout", type]);
152
+ tokens[startIdx].attrPush(["data-callout-fold", fold]);
153
+ if (title) tokens[startIdx].attrPush(["data-callout-title", title]);
154
+
155
+ tokens[endIdx].type = "callout_close";
156
+ tokens[endIdx].attrPush(["data-callout", type]);
157
+ tokens[endIdx].attrPush(["data-callout-fold", fold]);
158
+
159
+ if (contentIdx !== startIdx && tokens[contentIdx]?.children) {
160
+ tokens[contentIdx].content = tokens[contentIdx].content.replace(CALLOUT_RE, "").trim();
161
+ }
162
+ }
163
+
164
+ function inspectFence(tokens, startIdx, options) {
165
+ const token = tokens[startIdx];
166
+ if (!token.info) return;
167
+
168
+ const match = token.info.replace(options.langPrefix || "", "").match(ADMONITION_RE);
169
+ if (!match) return;
170
+
171
+ token.type = "admonition_block";
172
+ token.attrPush(["class", "callout"]);
173
+ token.attrPush(["data-callout", match[1].toLowerCase()]);
174
+
175
+ let lines = token.content.split("\n");
176
+ while (lines.length > 0 && ADMONITION_HEADER_RE.test(lines[0])) {
177
+ const headerMatch = lines[0].match(ADMONITION_HEADER_RE);
178
+ if (!headerMatch) break;
179
+ const attrName = HEADER_TO_ATTR[headerMatch[1].trim().toLowerCase()];
180
+ if (attrName) token.attrPush([attrName, headerMatch[2].trim()]);
181
+ lines = lines.slice(1);
182
+ }
183
+
184
+ token.content = lines.join("\n");
185
+ }
186
+
187
+ function obsidianCallouts(md, options = {}) {
188
+ md.core.ruler.after("block", "obsidian-callouts", (state) => {
189
+ const tokens = state.tokens;
190
+ for (let i = 0; i < tokens.length; i++) {
191
+ const token = tokens[i];
192
+ if (token.type === "blockquote_open") inspectBlockquote(tokens, i);
193
+ if (token.type === "fence") inspectFence(tokens, i, options);
194
+ }
195
+ });
196
+
197
+ md.renderer.rules.callout_open = async (tokens, idx) => renderCalloutPrefix(tokens[idx], md, options);
198
+
199
+ md.renderer.rules.admonition_block = async (tokens, idx) => {
200
+ const token = tokens[idx];
201
+ const prefix = await renderCalloutPrefix(token, md, options);
202
+ const body = await md.renderAsync(token.content);
203
+ return `${prefix}${body}\n</div>\n</div>`;
204
+ };
205
+
206
+ md.renderer.rules.callout_close = (tokens, idx) => renderCalloutPostfix(tokens[idx]);
207
+ }
208
+
209
+ module.exports = obsidianCallouts;
210
+ module.exports.DEFAULT_ICONS = DEFAULT_ICONS;
@@ -2,14 +2,11 @@ const path = require("node:path");
2
2
  const { createMarkdownExit } = require("markdown-exit");
3
3
  const mermaidDiagram = require("markdown-exit-mermaid");
4
4
  const ratex = require("markdown-exit-ratex");
5
- const code = require("markdown-exit-shiki");
6
- const abbr = require("markdown-it-abbr");
5
+ const code = require("./shiki");
6
+ const obsidianCallouts = require("./obsidian-callouts");
7
7
  const anchor = require("markdown-it-anchor");
8
8
  const footnote = require("markdown-it-footnote");
9
- const ins = require("markdown-it-ins");
10
9
  const mark = require("markdown-it-mark");
11
- const sub = require("markdown-it-sub");
12
- const sup = require("markdown-it-sup");
13
10
  const taskLists = require("markdown-it-task-lists");
14
11
 
15
12
  function resolveDefault(module) {
@@ -222,11 +219,7 @@ class MarkdownRenderer {
222
219
  typographer: true,
223
220
  xhtmlOut: false,
224
221
  },
225
- code_options: {
226
- themes: {
227
- light: "catppuccin-latte",
228
- },
229
- },
222
+
230
223
  mermaid_options: {
231
224
  theme: "default",
232
225
  },
@@ -243,14 +236,11 @@ class MarkdownRenderer {
243
236
  this.md
244
237
  .use(resolveDefault(footnote))
245
238
  .use(resolveDefault(mark))
246
- .use(resolveDefault(sub))
247
- .use(resolveDefault(sup))
248
- .use(resolveDefault(abbr))
249
- .use(resolveDefault(ins))
250
239
  .use(resolveDefault(taskLists))
251
240
  .use(resolveDefault(code), this.config.code_options)
252
241
  .use(resolveDefault(mermaidDiagram), this.config.mermaid_options)
253
242
  .use(resolveDefault(ratex), this.config.ratex_options)
243
+ .use(obsidianCallouts, this.config.callout_options)
254
244
  .use(customTabs)
255
245
  .use(wrapMarkdownItTable)
256
246
  .use(resolveDefault(anchor), {