Markdown格式化工具

在toolhelper.cn的Markdown编辑器中添加格式化功能按钮和快捷键(ctrl+shift+F)

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Markdown格式化工具
// @namespace    http://tampermonkey.net/
// @version      1.5
// @description  在toolhelper.cn的Markdown编辑器中添加格式化功能按钮和快捷键(ctrl+shift+F)
// @author       damu
// @match        https://www.toolhelper.cn/Code/Markdown
// @grant        none
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // 显示临时提示
    const showTemporaryMessage = (message, duration = 2000) => {
        const messageDiv = document.createElement('div');
        messageDiv.textContent = message;
        messageDiv.style.cssText = `
            position: fixed;
            top: 20px;
            right: 20px;
            background: #4CAF50;
            color: white;
            padding: 10px 20px;
            border-radius: 5px;
            z-index: 10000;
            font-family: Arial, sans-serif;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        `;
        document.body.appendChild(messageDiv);

        setTimeout(() => {
            document.body.removeChild(messageDiv);
        }, duration);
    };

    // 等待编辑器加载完成
    const waitForEditor = setInterval(() => {
        const toolbar = document.querySelector('.editormd-toolbar-container ul');
        if (!toolbar) return;

        clearInterval(waitForEditor);

        // 创建格式化按钮
        const formatBtn = document.createElement('li');
        formatBtn.innerHTML = `<a href="javascript:;" title="格式化Markdown (Alt+Shift+F)"><i class="fa fa-align-center" name="format-markdown"></i></a>`;
        toolbar.insertBefore(formatBtn, toolbar.querySelector('.divider'));

        // 获取编辑器内容
        const getEditorContent = () => {
            const cmEditor = document.querySelector('.CodeMirror')?.CodeMirror;
            if (cmEditor) return cmEditor.getValue();

            const editor = document.querySelector('.editormd-markdown-textarea');
            return editor ? editor.value : '';
        };

        // 设置编辑器内容
        const setEditorContent = (content) => {
            const cmEditor = document.querySelector('.CodeMirror')?.CodeMirror;
            if (cmEditor) {
                cmEditor.setValue(content);
                return;
            }

            const editor = document.querySelector('.editormd-markdown-textarea');
            if (editor) {
                editor.value = content;
                editor.dispatchEvent(new Event('input'));
            }
        };

        // 格式化表格行
        const formatTableLine = (line) => {
            const cells = line.split('|').map(cell => cell.trim());
            let result = '|';

            for (let i = 1; i < cells.length - 1; i++) {
                const content = cells[i] === '' ? '  ' : ` ${cells[i]} `;
                result += content + '|';
            }

            return result;
        };

        // 格式化分隔线
        const formatSeparatorLine = (line) => {
            const cells = line.split('|').map(cell => cell.trim());
            let result = '|';

            for (let i = 1; i < cells.length - 1; i++) {
                result += ' --- |';
            }

            return result;
        };

        // 表格格式化函数
        const formatTable = () => {
            const content = getEditorContent();
            if (!content) return;

            const lines = content.split('\n');
            let tableStart = -1;
            let tableEnd = -1;
            const tables = [];

            // 识别表格范围
            for (let i = 0; i < lines.length; i++) {
                const line = lines[i].trim();
                if (line.startsWith('|') && line.endsWith('|')) {
                    if (tableStart === -1) {
                        tableStart = i;
                    }
                    tableEnd = i;
                } else if (tableStart !== -1) {
                    // 检查是否是分隔线
                    if (i === tableStart + 1 && line.includes('|') && line.replace(/[^|-]/g, '').length > 0) {
                        tableEnd = i;
                    } else {
                        // 表格结束
                        if (tableEnd - tableStart >= 1) {
                            tables.push({ start: tableStart, end: tableEnd });
                        }
                        tableStart = -1;
                        tableEnd = -1;
                    }
                }
            }

            // 捕获最后一个表格
            if (tableStart !== -1 && tableEnd - tableStart >= 1) {
                tables.push({ start: tableStart, end: tableEnd });
            }

            if (tables.length === 0) {
                showTemporaryMessage('未检测到Markdown表格');
                return;
            }

            // 格式化每个表格
            const newLines = [...lines];
            tables.forEach(({ start, end }) => {
                // 处理表头
                newLines[start] = formatTableLine(newLines[start]);

                // 处理分隔线
                if (start + 1 <= end) {
                    newLines[start + 1] = formatSeparatorLine(newLines[start + 1]);
                }

                // 处理表格内容
                for (let i = start + 2; i <= end; i++) {
                    newLines[i] = formatTableLine(newLines[i]);
                }
            });

            setEditorContent(newLines.join('\n'));
            showTemporaryMessage('表格格式化完成');
        };
        // Markdown格式化函数
        const formatMarkdown = () => {
            // 获取编辑器内容
            let content = getEditorContent();
            // 如果内容为空则直接返回
            if (!content) return;
            // 将内容按行分割进行进一步处理
            const lines = content.split(' ');
            // 存储上一个行数据
            let lastLines = '';
            // 存储格式化后的行数组
            const formattedLines = [];
            const summarizeWord = ["综上", "因此", "所以", "总之", "总结", "最终答案", "最终结果", "结论", "总结", "综上", "综上而言", "综上所述", "综上来看", "总的总结是", "核心总结为", "所以", "因此", "因而", "故此", "由此", "由此可见", "由此可得", "由此总结", "基于此", "综上可推出", "综上可得出", "总而言之", "总而言之一句话", "总的来说", "总的来讲", "总的看", "概括来说", "概括来讲", "简而言之", "简言之"];
            // 遍历每一行进行处理
            for (let i = 0; i < lines.length; i++) {
                // 去除行首尾空白字符
                const line = lines[i].trim();
                let len = formattedLines.length;
                const isVoidLine = (line === ''); // 空行
                const isVoidLastLine = (lastLines === ''); // 上一个是空行
                const isPunctuationAfter = (lastLines.match(/[。:!;]$/));// 特定标点符号后
                const isFirst = (len === 0) // 是第一行有效数据
                const isTitle = (line.match(/^#+$/)); // 标题
                const isTitleAfter = (lastLines.match(/^#+\s.+$/)); // 标题后一行
                const isOl = (line.match(/^\d+[.:、]$/));// 有序列表
                const isUl = (line.match(/^[\*\-\+]$/) && isPunctuationAfter); // 无序列表
                const isVerticalLine = (line.match(/^\|$/)); // 是竖线|
                const isEndVerticalLastLine = (lastLines.endsWith('\|')); // 上一个结尾是|
                const isSummarize = (isPunctuationAfter && summarizeWord.some(word => line.startsWith(word))); // 总结句

                if (isVoidLine) {
                    lastLines = line;
                } else if (isTitle || isSummarize || (isVerticalLine && !getLastLinesNotNull(lastLines, formattedLines)?.startsWith('\|'))) {
                    if (len !== 0) formattedLines.push(''); // 非第一行前添加空行
                    formattedLines.push(lastLines = line); // 换行
                } else if ((isVerticalLine && isEndVerticalLastLine) || (!isVerticalLine && (isTitleAfter || isOl || isUl || isPunctuationAfter || isVoidLastLine || isFirst))) {
                    formattedLines.push(lastLines = line); // 换行
                } else {
                    formattedLines[len - 1] = (lastLines = formattedLines[len - 1] + " " + line); // 默认情况下用空格连接,拼接到上一行后面
                }
            }

            // 将格式化后的行数组合并为字符串
            let finalContent = formattedLines.join('\n').trim();
            // 将格式化后的内容设置回编辑器
            setEditorContent(finalContent);
            // 显示操作完成提示
            showTemporaryMessage('Markdown格式化完成');

        };

        // 获取上一个非空的内容
        function getLastLinesNotNull(lastLines, formattedLines) {
            let lastLinesNotNull = lastLines;
            if (!lastLinesNotNull || lastLinesNotNull === '' || lastLinesNotNull === null) {
                for (let i = 0; i < formattedLines.length; i++) {
                    if (formattedLines[formattedLines.length - i] && formattedLines[formattedLines.length - i] !== '') {
                        lastLinesNotNull = formattedLines[formattedLines.length - i];
                        break;
                    }
                }
            }
            return lastLinesNotNull;
        }

        // 按钮点击事件
        formatBtn.querySelector('a').addEventListener('click', formatMarkdown);

        // 添加快捷键 Alt+Shift+F
        document.addEventListener('keydown', e => {
            if (e.altKey && e.shiftKey && e.key === 'F') {
                e.preventDefault();
                formatMarkdown();
            }
        });

        // 添加表格格式化按钮
        const tableFormatBtn = document.createElement('li');
        tableFormatBtn.innerHTML = `<a href="javascript:;" title="仅格式化表格"><i class="fa fa-table" name="format-table-only"></i></a>`;
        toolbar.insertBefore(tableFormatBtn, formatBtn.nextSibling);

        tableFormatBtn.querySelector('a').addEventListener('click', formatTable);

    }, 100);
})();