0%

hexo中引用其他文档片段的设置

对obsidian的markdown格式转hexo格式时,如何如obsidian一样很好的引入其他markdown的内容。


需求

  1. 可以对引用的标题,按不同级别,设置不同大小的字号。
  2. 引用的内容不影响了文章的目录。badcase如图所示
  3. 引用的代码块要正常显示。

解决方案

  1. 在 hexo根目录,若没有,则新建文件夹:scripts。
  2. 在文件夹中新建文件include_custom.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
    const fs = require('fs'); // 引入文件系统模块
    const path = require('path'); // 引入路径模块
    const { escapeHTML } = require('hexo-util'); // 引入 Hexo 工具模块中的 escapeHTML 函数

    // 递归解析嵌套的 include_custom 标签
    function renderIncludeCustom(content, hexo) {
    const includeTagRegex = /{% include_custom ([^%]+) %}/g;
    let match;

    while ((match = includeTagRegex.exec(content)) !== null) {
    const args = match[1].split(' ');
    const nestedContent = includeCustom(args, hexo);
    content = content.replace(match[0], nestedContent);
    }

    return content;
    }

    function includeCustom(args, hexo) {
    const relativePath = args[0]; // 获取相对路径
    const heading = args[1] ? args[1].replace(/['"]/g, '') : null; // 获取标题,如果有
    const absolutePath = path.join(hexo.source_dir, relativePath); // 获取文件的绝对路径

    if (!fs.existsSync(absolutePath)) {
    return `File not found: ${relativePath}`; // 返回错误信息
    }

    let content = fs.readFileSync(absolutePath, 'utf8'); // 读取文件内容

    if (heading) {
    const headingLevel = heading.match(/^#+/)[0].length; // 确定标题级别
    const escapedHeading = escapeHTML(heading.replace(/#+\s*/, '').trim()); // 转义标题内容
    const regex = new RegExp(`(^#{${headingLevel}}\\s+${escapedHeading}\\s*$)`, 'gm'); // 创建正则表达式,匹配指定级别的标题
    const match = regex.exec(content); // 查找匹配的标题

    if (match) {
    const startIndex = match.index; // 获取标题的起始位置
    let endIndex = content.length; // 初始化内容的结束位置为文件末尾
    let inCodeBlock = false; // 初始化代码块标记
    let currentPos = 0; // 当前字符位置

    // 查找下一个同级或更高级的标题,确定结束位置
    const lines = content.split('\n');
    for (let i = 0; i < lines.length; i++) {
    if (currentPos > startIndex) {
    const line = lines[i];
    if ((line.match(/```/g) || []).length % 2 !== 0) {
    inCodeBlock = !inCodeBlock; // 切换代码块标记
    }
    if (!inCodeBlock && new RegExp(`^#{1,${headingLevel}}\\s`).test(line)) {
    endIndex = currentPos; // 更新内容的结束位置
    break;
    }
    }
    currentPos += lines[i].length + 1; // 更新当前字符位置,包含换行符
    }

    content = content.substring(startIndex, endIndex).trim(); // 提取指定标题下的内容
    } else {
    return `No match found for heading: ${heading}`; // 返回错误信息
    }
    }

    // 标记代码块,排除代码块中的内容
    let inCodeBlock = false; // 重置代码块标记
    let processedContent = content.split('\n').map(line => {
    if ((line.match(/```/g) || []).length % 2 !== 0) {
    inCodeBlock = !inCodeBlock; // 切换代码块标记
    return line; // 返回当前行(代码块起始或结束)
    }
    if (!inCodeBlock) {
    // 如果不在代码块中,处理标题
    return line.replace(/^# (.+)$/gm, '<div class="custom-quote-h1">$1</div>')
    .replace(/^## (.+)$/gm, '<div class="custom-quote-h2">$1</div>')
    .replace(/^### (.+)$/gm, '<div class="custom-quote-h3">$1</div>')
    .replace(/^#### (.+)$/gm, '<div class="custom-quote-h4">$1</div>')
    .replace(/^##### (.+)$/gm, '<div class="custom-quote-h5">$1</div>')
    .replace(/^###### (.+)$/gm, '<div class="custom-quote-h6">$1</div>');
    }
    return line; // 返回当前行
    }).join('\n'); // 合并处理后的行

    // 处理代码块,将其包裹在 <pre><code> 标签中,并添加 Prism.js 类
    processedContent = processedContent.replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
    const languageClass = lang ? `language-${lang}` : 'language-plaintext'; // 确定代码语言
    const escapedCode = escapeHTML(code.trim()); // 转义代码内容
    return `<pre class="line-numbers ${languageClass}"><code class="${languageClass}">${escapedCode}</code></pre>`; // 返回包裹后的代码块
    });

    // 处理内联代码块,将其包裹在 <code> 标签中
    processedContent = processedContent.replace(/`([^`]+)`/g, (match, code) => {
    const escapedCode = escapeHTML(code.trim()); // 转义内联代码内容
    return `<code>${escapedCode}</code>`; // 返回包裹后的内联代码块
    });

    // 递归解析嵌套的 include_custom 标签
    processedContent = renderIncludeCustom(processedContent, hexo);

    // 使用 Hexo 自带的 Markdown 渲染器解析剩余内容
    processedContent = hexo.render.renderSync({ text: processedContent, engine: 'markdown' });

    // 包装为自定义样式
    return `<div class="custom-quote">${processedContent}</div>`;
    }

    hexo.extend.tag.register('include_custom', function(args) {
    return includeCustom(args, hexo);
    }, { async: false });
  3. 确保自定义 CSS 文件中有正确的样式,即在 themes/next/source/css/custom.css或source/css/custom.css 文件中添加以下样式(若没有,则新建,建议themes/next/source/css/custom.css,因为这种做法有助于保持不同主题之间的隔离,避免样式冲突。):

    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
    .custom-quote {
    border-left: 4px solid #ccc;
    padding-left: 10px;
    margin: 20px 0;
    background-color: #f9f9f9;
    font-style: italic;
    }

    .custom-quote .custom-quote-h1 {
    font-size: 1.5em;
    font-weight: bold;
    }

    .custom-quote .custom-quote-h2 {
    font-size: 1.3em;
    font-weight: bold;
    }

    .custom-quote .custom-quote-h3 {
    font-size: 1.1em;
    font-weight: bold;
    }

    .custom-quote .custom-quote-h4 {
    font-size: 1em;
    font-weight: bold;
    }

    .custom-quote .custom-quote-h5 {
    font-size: 0.9em;
    font-weight: bold;
    }

    .custom-quote .custom-quote-h6 {
    font-size: 0.8em;
    font-weight: bold;
    }

    .custom-quote pre {
    background: #f5f5f5;
    padding: 10px;
    border-radius: 5px;
    overflow: auto;
    white-space: pre-wrap; /* 确保换行符被正确处理 */
    }

    .custom-quote code {
    background: #f5f5f5;
    padding: 2px 4px;
    border-radius: 3px;
    }

    /* 更改代码块的背景颜色,确保生效 */
    pre[class*="language-"] {
    background: #f8f6f6 !important; /* 设置为你喜欢的背景颜色 */
    }


    .image-row {
    display: flex;
    gap: 10px; /* 图片之间的间隔 */
    }

    .image-row img {
    max-width: 100%; /* 确保图片不会超出容器宽度 */
    }
  4. themes/next/layout/_partials/head/head.swig末尾添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {% if theme.prism_plugin.enable %}
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/themes/prism.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/prism.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/plugins/line-numbers/prism-line-numbers.min.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/plugins/line-numbers/prism-line-numbers.min.css">
    <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/components/prism-python.min.js"></script> <!-- Python支持 -->
    <script>
    document.addEventListener('DOMContentLoaded', (event) => {
    Prism.highlightAll();
    });
    </script>
    {% endif %}
  5. 更改代码块的背景颜色

    1
    2
    3
    4
    /* 更改代码块的背景颜色,确保生效 */
    pre[class*="language-"] {
    background: #f5f2f0 !important; /* 设置为你喜欢的背景颜色 */
    }

    这里加!important用于确保Prism.js的样式没有覆盖自定义样式

  6. 在根目录的_config.yml中添加:

    1
    2
    prism_plugin:
    enable: true
  7. 重启

    1
    hexo clean && hexo g && hexo s