.md 文档自动编号 js 脚本

.md 文档自动编号 js 脚本

1、契机

在使用 typora 的时候,没有自动编号,每一级标题要手动编号,比较累。之前有找过修改主题的 css 来实现自动编号的,但是只是个样式而已,没有真正的编号,而且导出的 pdf 中是没有编号的。这次找的脚本是直接修改 md 文档的,根据行首的 # 来判断是不是标题。

md 文档一共有六级标题,不对一级标题编号,二级、三级、四级、五级、六级标题进行级联编号。

根据这篇文章的 java 代码修改而来,并做了一点小小的改进,用 java 感觉有点重,最近喜欢用 js 写点脚本,比如一些字符串处理的,挺方便的。

文章链接:

https://blog.csdn.net/oneby1314/article/details/107311743

2、脚本代码

js 代码如下:

const fs = require('fs');
const readline = require('readline');
const {once} = require('node:events')
const ncp = require("copy-paste");
const path = require('path')

/**
 * 执行标题自动编号
 *
 * @param destMdFilePath MD 文件路径
 */
function doTitleAutoNumbering(destMdFilePath) {
    // 获取标题自动编号的MD文件内容
    const mdFileContent = getAutoTitledMdContent(destMdFilePath);
    mdFileContent.then((res) => {
        // 执行保存(覆盖原文件)
        saveMdContentToFile(destMdFilePath, res);
    })
}

/**
 * 获取标题自动编号的MD文件内容
 *
 * @param destMdFilePath MD 文件路径
 * @return
 */
async function getAutoTitledMdContent(destMdFilePath) {
    // 标题编号
    /*
        标题编号规则:
        - 一级标题为文章的题目,不对一级标题编号
        - 二级、三级、四级、五级、六级标题需要级联编号
     */
    let titleNumber = [0, 0, 0, 0, 0]
    // 存储md文件内容
    let mdContent = ''

    const md = readline.createInterface({
        input: fs.createReadStream(destMdFilePath),
        output: process.stdout,
        terminal: false
    });

    // 一行一行读取数据
    md.on('line', (line) => {
        // 判断是否为标题行,如果是标题,是几级标题
        const curTitleLevel = calcTitleLevel(line);
        if (curTitleLevel !== -1) {

            // 插入标题序号
            line = insertTitleNumber(line, titleNumber);

            // 重新计算标题计数器
            reCalcTitleCounter(curTitleLevel, titleNumber);

        }
        mdContent = mdContent.concat(line, '\r\n')
    })

    // 等待监听事件完成
    await once(md, 'close')
    return mdContent;
}

/**
 * 计算当前标题等级
 *
 * @param curLine 当前行的内容
 * @return -1 :非标题行;大于等于 2 的正数:当前行的标题等级
 */
function calcTitleLevel(curLine) {
    // 由于一级标题无需编号,所以从二级标题开始判断
    let isTitle = curLine.startsWith("##");
    if (!isTitle) {
        // 返回 -1 表示非标题行
        return -1;
    }

    // 现在来看看是几级标题
    return curLine.indexOf(" ");
}

/**
 * 向标题行中插入标题序号
 *
 * @param curLine     当前行内容
 * @param titleNumber 标题计数器
 * @return
 */
function insertTitleNumber(curLine, titleNumber) {
    // 标题等级(以空格分隔的前提是 Typora 开启严格模式)
    let titleLevel = curLine.indexOf(" ");
    // 标题等级部分
    let titleLevelStr = curLine.substring(0, titleLevel);
    // 标题内容部分
    let titleContent = curLine.substring(titleLevel + 1);
    // 先去除之前的编号
    titleContent = removePreviousTitleNumber(titleContent);
    // 标题等级递增
    let titleIndex = titleLevel - 2;
    titleNumber[titleIndex] += 1;
    // 标题序号
    let titleNumberStr = "";
    switch (titleLevel) {
        case 2:
            titleNumberStr = `${titleNumber[0]}`;
            break;
        case 3:
            titleNumberStr = `${titleNumber[0]}.${titleNumber[1]}`
            break;
        case 4:
            titleNumberStr = `${titleNumber[0]}.${titleNumber[1]}.${titleNumber[2]}`
            break;
        case 5:
            titleNumberStr = `${titleNumber[0]}.${titleNumber[1]}.${titleNumber[2]}.${titleNumber[3]}`
            break;
        case 6:
            titleNumberStr = `${titleNumber[0]}.${titleNumber[1]}.${titleNumber[2]}.${titleNumber[3]}.${titleNumber[4]}`
            break;
    }
    titleNumberStr += "、";
    // 插入标题序号
    titleContent = titleNumberStr + titleContent;
    // 返回带序号的标题
    curLine = titleLevelStr + " " + titleContent;
    return curLine;
}

/**
 * 去除之前标题的编号
 * @param titleContent 标题内容
 * @return 去除标题编号之后的标题内容
 */
function removePreviousTitleNumber(titleContent) {
    // 寻找标题中的 、 字符
    let index = titleContent.indexOf("、");
    if (index > 0 && index < 10) {
        // 之前已经进行过标号
        return titleContent.substring(index + 1);
    } else {
        // 之前未进行过标号,直接返回
        return titleContent;
    }
}

/**
 *  重新计算标题计数器的值
 *
 * @param titleLevel  当前行的标题等级
 * @param titleNumber 标题计数器
 */
function reCalcTitleCounter(titleLevel, titleNumber) {
    // 二级标题更新时,三级及三级以下的标题序号重置为 0
    let startIndex = titleLevel - 1;
    for (let i = startIndex; i < titleNumber.length; i++) {
        titleNumber[i] = 0;
    }
}

/**
 * 保存MD文件
 *
 * @param destMdFilePath MD文件路径
 * @param mdFileContent  MD文件内容
 */
function saveMdContentToFile(destMdFilePath, mdFileContent) {
    // 不保存空文件
    if (mdFileContent == null || mdFileContent === "") {
        return;
    }
    // 执行保存
    fs.writeFile(destMdFilePath, mdFileContent, (err) => {
        if (err) {
            return console.log(err)
        }
        console.log('数据写入成功!')
    })
}

function getNcpPath() {
    return new Promise((resolve, reject) => {
        ncp.paste((err, p) => {
            if (err) {
                reject(err)
            } else {
                if (typeof p === 'string') {
                    resolve(p)
                }
            }
        })
    })
}

(async () => {
    const arguments = process.argv;
    let mdPath = ''
    // 可以使用循环迭代所有的命令行参数(包括node路径和文件路径)
    // 命令行输入参数的情况
    if (arguments.length >= 3) {
        // 解决路径带空格的情况
        for (let i = 2; i < arguments.length; i++) {
            mdPath = mdPath.concat(arguments[i], ' ')
        }
        mdPath = mdPath.trim()
    }

    // 没有输入参数的情况,则去粘贴板寻找是否有文件路径
    if (arguments.length < 3) {
        mdPath = await getNcpPath()
    }

    let stat = null
    try {
        // 路径带空格,需要输入双引号
        stat = fs.lstatSync(mdPath)
    } catch (err) {
        console.log('参数错误,文件不存在')
        return
    }
    if (stat.isFile()) {
        if (!mdPath.endsWith('.md')) {
            console.log('参数错误,请输入md文件的路径')
            return
        }
        // 执行标题自动编号
        doTitleAutoNumbering(mdPath)
    }
    if (stat.isDirectory()) {
        let dirFiles = fs.readdirSync(mdPath);
        dirFiles.forEach((item) => {
            let filePath = path.join(mdPath, item)
            if (filePath.endsWith('.md')) {
                // 执行标题自动编号
                doTitleAutoNumbering(filePath)
            }
        })
    }
})()

3、使用说明

3.1、环境

  1. 需要本机安装了 node js,最好配置了国内镜像源

  2. 代码拷贝到 js 文件中,执行以下命令:

    # 忘了是不是这个命令初始化了
    npm init
    # 安装依赖
    npm install
    

3.2、运行

3.2.1、命令行方式
3.2.1.1、传入文件参数
node autoNumMd.js ./test.md

传入文件参数

编号效果:

编号效果

3.2.1.2、传入文件夹参数
# 是文件夹的话,就将文件夹下的 md 文档全部编号
# 但是不会递归子文件夹
node autoNumMd.js C:\...\_posts

image-20221106101249420

3.2.1.3、无参数的情况
# 没有传入参数,则会去粘贴板查看是否有复制路径
node autoNumMd.js
3.2.2、bat 脚本

新建 .bat 文件,写入以下内容,注意 node Absolute path 需要替换为本机 node 的绝对路径,js 文件也是,输入文件夹、文件路径或者直接回车(读取粘贴板路径)。

setlocal EnableDelayedExpansion
set /p val=Please enter the .md file path or folder path:
echo %val%
if "%val%" == "" (
node Absolute path "autoNumMd.js Absolute path"
) else (
node Absolute path "autoNumMd.js Absolute path" %val%
)

4、md 编写说明

  1. 分级标题要连续,按照二三四五六来,不要二级标题后接四五六级标题
  2. # 后面要接空格

5、总结

  1. js 的异步编程把我真是烦透了,很多的操作都是要同步的。。。,异步转同步改了我好久阿,心累
  2. bat 脚本也画了挺长时间的,这语法太怪了
  3. 此文档的编号使用脚本生成
    th:
    echo %val%
    if “%val%” == “” (
    node Absolute path “autoNumMd.js Absolute path”
    ) else (
    node Absolute path “autoNumMd.js Absolute path” %val%
    )

## 4、md 编写说明

1. 分级标题要连续,按照二三四五六来,不要二级标题后接四五六级标题
2. \# 后面要接空格

## 5、总结

1. js 的异步编程把我真是烦透了,很多的操作都是要同步的。。。,异步转同步改了我好久阿,心累
2. bat 脚本也画了挺长时间的,这语法太怪了
3. 此文档的编号使用脚本生成
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Editor.md 是一个基于 Markdown 编辑器的开源项目,可以方便地在 Web 应用中使用。而 Flask 是一个轻量级的 Python Web 框架,非常适合用于构建小型的 Web 应用程序。下面是 Editor.md 在 Flask 中的使用方法。 首先,我们需要使用以下命令安装 editor.md: ``` npm install editor.md ``` 安装完成后,我们可以在 Flask 项目中创建一个静态文件夹(例如 static),将编辑器的 JS 和 CSS 文件放入其中。 在 Flask 的路由文件中,我们可以添加以下代码来渲染编辑器页面: ```python from flask import Flask, render_template app = Flask(__name__) @app.route('/editor') def editor(): return render_template('editor.html') if __name__ == '__main__': app.run() ``` 在 templates 文件夹中创建一个名为 editor.html 的模板文件,其中包含编辑器的 HTML 结构和必要的脚本引入。 ```html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Editor</title> <link rel="stylesheet" href="{{ url_for('static', filename='editor.md/css/editormd.css') }}"> </head> <body> <div id="editor"> <textarea></textarea> </div> <script src="{{ url_for('static', filename='editor.md/lib/jquery.min.js') }}"></script> <script src="{{ url_for('static', filename='editor.md/lib/editormd.min.js') }}"></script> <script type="text/javascript"> $(function() { var editor = editormd("editor", { // 配置选项 }); }); </script> </body> </html> ``` 在这个例子中,我们通过 Flask 的 render_template 函数将 editor.html 渲染给用户。编辑器的配置选项可以根据需求进行设置,详情可以参考 Editor.md 的官方文档。 最后,启动 Flask 程序并访问 `http://localhost:5000/editor`,你将可以看到编辑器的界面。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值