用 HTML 网页来管理 Markdown 标题序号

文章目录

工具介绍

在日常的文档编写和博客创作中,Markdown因其简洁的语法和良好的可读性而广受欢迎。然而,当文档结构复杂、标题层级较多时,手动维护标题序号不仅耗时耗力,而且容易出错。所以,开发了这款Markdown标题序号编辑器,它具备以下突出优点。

核心优势

  1. 完全本地运行

    • 单HTML文件实现,无需安装任何软件
    • 不依赖网络连接,所有操作均在浏览器中完成
    • 保护隐私安全,文件内容不会上传到任何服务器
  2. 智能序号管理

    • 自动识别文档中的最大标题层级,智能生成序号
    • 支持多级标题序号自动编排(如1.1, 1.2.1等)
    • 完美处理已有序号的文档,重新编号时保持逻辑正确
  3. 极致简洁体验

    • 拖拽文件即可使用,操作直观简单
    • 响应式设计,适配各种设备屏幕
    • 清爽的界面布局,专注核心功能
  4. 完备的功能集

    • 一键添加/移除标题序号
    • 一键复制处理结果到剪贴板
    • 一键下载覆盖原文件
    • 一键重置编辑器状态
  5. 贴心的交互设计

    • 顶部弹出式通知提示,不干扰内容布局
    • 右下角返回顶部按钮,方便长文档操作
    • 实时预览修改内容,所见即所得
    • 动画效果平滑过渡,提升使用体验
  6. 专业的技术实现

    • 纯前端实现,无需后端支持
    • 采用现代Web API(如Clipboard API)
    • 精心设计的算法确保序号逻辑准确
    • 完整错误处理机制,操作安全可靠

使用指南

基本使用方法

  1. 打开工具

    • 复制下方完整代码,将其以HTML文件的形式保存到本地
    • 双击用浏览器打开(推荐Chrome/Firefox/Edge等现代浏览器)
  2. 导入Markdown文件

    • 方式一:直接拖拽.md文件到指定区域
    • 方式二:点击区域选择文件
  3. 编辑标题序号

    • 点击"添加标题序号":自动为所有标题添加智能编号
    • 点击"移除标题序号":一键清除所有标题编号
  4. 获取结果

    • "复制内容":将处理后的文本复制到剪贴板
    • "下载文件":保存处理后的文件(默认使用原文件名)
    • "重置":清空当前内容,重新开始
  5. 处理复杂文档

    • 工具会自动识别文档结构,无论最大标题是几级,都会从1开始编号
    • 例如:如果文档最大标题是###,编号会是1., 1.1., 1.2.等
  6. 编辑中途调整

    • 随时可以切换"添加"和"移除"序号,系统会保持正确的编号逻辑
    • 新增无序号标题后,再次添加序号会自动重新编排

注意事项

  1. 工具仅处理标准的Markdown标题语法(#、##等)
  2. 建议在处理前备份重要文档
  3. 极少数情况下,特殊格式的标题可能需要手动调整
  4. 如需处理超大文件(10MB以上),建议分章节操作

部分截图

亮色主题:

夜间模式:

完整代码

复制下方的代码,保存至一个后缀为 .html 的文件中,双击用浏览器打开即可使用。

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Markdown标题序号编辑器</title>
    <style>
        :root {
            --primary-color: #4a6fa5;
            --secondary-color: #6b8cae;
            --accent-color: #ff7e5f;
            --light-color: #f8f9fa;
            --dark-color: #343a40;
            --success-color: #28a745;
            --info-color: #17a2b8;
            --warning-color: #ffc107;
            --error-color: #dc3545;
            --border-radius: 8px;
            --box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            --bg-color: #f5f7fa;
            --text-color: #343a40;
            --container-bg: white;
            --preview-bg: #f8f9fa;
            --preview-border: #ddd;
            --placeholder-color: #6c757d;
        }

        /* 科技感主题 */
        .tech-theme {
            --primary-color: #3a86ff;
            --secondary-color: #8338ec;
            --accent-color: #ff006e;
            --light-color: #1a1a2e;
            --dark-color: #e6e6e6;
            --success-color: #06d6a0;
            --info-color: #118ab2;
            --warning-color: #ffd166;
            --error-color: #ef476f;
            --bg-color: #0f0f1b;
            --text-color: #e6e6e6;
            --container-bg: #1a1a2e;
            --preview-bg: #16213e;
            --preview-border: #3a3a5d;
            --placeholder-color: #8a8a9d;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            color: var(--text-color);
            background-color: var(--bg-color);
            margin: 0;
            padding: 20px;
            /* padding-bottom: 0px;
            padding-top: 0px; */
            display: flex;
            flex-direction: column;
            align-items: center;
            min-height: 90vh;
            position: relative;
            transition: all 0.3s ease;
        }

        .container {
            width: 100%;
            max-width: 900px;
            background-color: var(--container-bg);
            border-radius: var(--border-radius);
            box-shadow: var(--box-shadow);
            padding: 25px;
            margin-bottom: 10px;
            transition: all 0.3s ease;
        }

        h1 {
            color: var(--primary-color);
            text-align: center;
            margin-bottom: 25px;
            font-weight: 600;
            transition: all 0.3s ease;
        }

        .drop-area {
            border: 3px dashed var(--secondary-color);
            border-radius: var(--border-radius);
            padding: 35px 20px;
            text-align: center;
            margin-bottom: 25px;
            transition: all 0.3s ease;
            background-color: rgba(106, 140, 174, 0.05);
            cursor: pointer;
            position: relative;
        }

        .drop-area.highlight {
            border-color: var(--accent-color);
            background-color: rgba(255, 126, 95, 0.1);
        }

        .drop-area p {
            font-size: 18px;
            color: var(--secondary-color);
            margin: 0;
            transition: all 0.3s ease;
        }

        .drop-area .icon {
            font-size: 48px;
            color: var(--secondary-color);
            margin-bottom: 15px;
            transition: all 0.3s ease;
        }

        .new-file-btn {
            position: absolute;
            right: 15px;
            bottom: 15px;
            background: none;
            border: none;
            color: var(--secondary-color);
            font-size: 14px;
            cursor: pointer;
            display: flex;
            align-items: center;
            gap: 5px;
            padding: 5px 10px;
            border-radius: 15px;
            transition: all 0.3s ease;
        }

        .new-file-btn:hover {
            background-color: rgba(106, 140, 174, 0.1);
            color: var(--primary-color);
        }

        .file-info {
            margin-top: 20px;
            padding: 15px;
            background-color: var(--light-color);
            border-radius: var(--border-radius);
            display: none;
            transition: all 0.3s ease;
        }

        .file-info.show {
            display: block;
        }

        .file-info p {
            margin: 5px 0;
            word-break: break-all;
            transition: all 0.3s ease;
        }

        .toolbar {
            position: sticky;
            top: 0;
            background-color: var(--container-bg);
            padding: 15px 0;
            z-index: 100;
            display: flex;
            justify-content: center;
            gap: 15px;
            flex-wrap: wrap;
            border-bottom: 1px solid var(--preview-border);
            margin-bottom: 15px;
        }

        button {
            padding: 10px 18px;
            border: none;
            border-radius: var(--border-radius);
            background-color: var(--primary-color);
            color: white;
            font-size: 15px;
            cursor: pointer;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            gap: 8px;
        }

        button:hover {
            background-color: var(--secondary-color);
            transform: translateY(-2px);
        }

        button:disabled {
            background-color: #cccccc;
            cursor: not-allowed;
            transform: none;
            opacity: 0.7;
        }

        button.download {
            background-color: var(--success-color);
        }

        button.download:hover {
            background-color: #218838;
        }

        button.remove {
            background-color: var(--accent-color);
        }

        button.remove:hover {
            background-color: #e66a4d;
        }

        button.copy {
            background-color: var(--info-color);
        }

        button.copy:hover {
            background-color: #138496;
        }

        button.reset {
            background-color: #6c757d;
        }

        button.reset:hover {
            background-color: #5a6268;
        }

        .preview-container {
            margin-top: 15px;
            display: none;
        }

        .preview-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 10px;
        }

        .preview-title {
            font-size: 18px;
            font-weight: 600;
            color: var(--primary-color);
            transition: all 0.3s ease;
        }

        .edit-toggle-container {
            display: flex;
            align-items: center;
            gap: 8px;
        }

        .edit-toggle-label {
            font-size: 14px;
            color: var(--text-color);
            cursor: pointer;
            user-select: none;
        }

        .edit-toggle {
            width: 16px;
            height: 16px;
            cursor: pointer;
        }

        .preview-content {
            max-height: calc(100vh - 400px);
            min-height: 200px;
            overflow-y: auto;
            padding: 15px;
            background-color: var(--preview-bg);
            border-radius: var(--border-radius);
            border: 1px solid var(--preview-border);
            white-space: pre-wrap;
            font-family: 'Courier New', Courier, monospace;
            transition: all 0.3s ease;
            outline: none;
        }

        .preview-content.editable {
            cursor: text;
            border: 1px solid var(--info-color);
        }

        .preview-content[placeholder]:empty:before {
            content: attr(placeholder);
            color: var(--placeholder-color);
            opacity: 0.8;
        }

        footer {
            /* margin-top: 20px; */
            text-align: center;
            color: var(--secondary-color);
            font-size: 13px;
            padding: 15px;
            width: 100%;
            transition: all 0.3s ease;
        }

        .back-to-top {
            position: fixed;
            bottom: 30px;
            right: 30px;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background-color: var(--primary-color);
            color: white;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            opacity: 0;
            visibility: hidden;
            transition: all 0.3s ease;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            z-index: 999;
        }

        .back-to-top.visible {
            opacity: 1;
            visibility: visible;
        }

        .back-to-top:hover {
            background-color: var(--secondary-color);
            transform: translateY(-3px);
        }

        .theme-toggle {
            position: fixed;
            bottom: 90px;
            right: 30px;
            width: 50px;
            height: 50px;
            border-radius: 50%;
            background-color: var(--accent-color);
            color: white;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: all 0.3s ease;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2);
            z-index: 999;
        }

        .theme-toggle:hover {
            transform: rotate(30deg) scale(1.1);
        }

        .notification-container {
            position: fixed;
            top: 20px;
            left: 0;
            right: 0;
            display: flex;
            justify-content: center;
            z-index: 1000;
            pointer-events: none;
        }

        .notification {
            padding: 12px 24px;
            border-radius: var(--border-radius);
            color: white;
            font-weight: 500;
            box-shadow: var(--box-shadow);
            transform: translateY(-100px);
            opacity: 0;
            transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
            margin-bottom: 10px;
            max-width: 80%;
            text-align: center;
        }

        .notification.show {
            transform: translateY(0);
            opacity: 1;
        }

        .notification.success {
            background-color: var(--success-color);
        }

        .notification.info {
            background-color: var(--info-color);
        }

        .notification.warning {
            background-color: var(--warning-color);
        }

        .notification.error {
            background-color: var(--error-color);
        }

        .modal {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 2000;
            justify-content: center;
            align-items: center;
        }

        .modal-content {
            background-color: var(--container-bg);
            padding: 25px;
            border-radius: var(--border-radius);
            width: 90%;
            max-width: 400px;
            box-shadow: var(--box-shadow);
        }

        .modal-title {
            margin-top: 0;
            color: var(--primary-color);
        }

        .modal-input {
            width: 100%;
            padding: 10px;
            margin: 15px 0;
            border: 1px solid var(--preview-border);
            border-radius: var(--border-radius);
            background-color: var(--preview-bg);
            color: var(--text-color);
        }

        .modal-buttons {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
        }

        @media (max-width: 768px) {
            .toolbar {
                position: static;
                flex-direction: column;
                align-items: center;
            }

            button {
                width: 100%;
            }

            .back-to-top, .theme-toggle {
                bottom: 20px;
                right: 20px;
                width: 40px;
                height: 40px;
                font-size: 18px;
            }

            .theme-toggle {
                bottom: 70px;
            }
        }
    </style>
</head>
<body>
    <div id="notificationContainer" class="notification-container"></div>
    
    <div id="newFileModal" class="modal">
        <div class="modal-content">
            <h3 class="modal-title">创建新Markdown文件</h3>
            <input type="text" id="newFileName" class="modal-input" placeholder="输入文件名(无需后缀)">
            <div class="modal-buttons">
                <button id="cancelNewFile" class="reset">取消</button>
                <button id="confirmNewFile" class="download">创建</button>
            </div>
        </div>
    </div>

    <div class="container">
        <h1>Markdown标题序号编辑器</h1>
        
        <div id="dropArea" class="drop-area">
            <div class="icon">📄</div>
            <p>拖拽Markdown文件到此处</p>
            <p><small>或点击选择文件</small></p>
            <button id="newFileBtn" class="new-file-btn">
                <span>+</span> 新建文件
            </button>
            <input type="file" id="fileInput" accept=".md,.markdown" style="display: none;">
        </div>
        
        <div id="fileInfo" class="file-info">
            <p><strong>文件名:</strong> <span id="fileName"></span></p>
            <p><strong>大小:</strong> <span id="fileSize"></span></p>
        </div>
        
        <div class="toolbar" id="toolbar">
            <button id="addNumbersBtn" disabled>
                <span>🔢</span> 添加标题序号
            </button>
            <button id="removeNumbersBtn" disabled class="remove">
                <span>✖️</span> 移除标题序号
            </button>
            <button id="copyBtn" disabled class="copy">
                <span>⎘</span> 复制内容
            </button>
            <button id="downloadBtn" disabled class="download">
                <span>⤵️</span> 下载文件
            </button>
            <button id="resetBtn" class="reset">
                <span>↺</span> 重置
            </button>
        </div>
        
        <div id="previewContainer" class="preview-container">
            <div class="preview-header">
                <div class="preview-title">预览</div>
                <div class="edit-toggle-container">
                    <label class="edit-toggle-label" for="editToggle">编辑模式</label>
                    <input type="checkbox" id="editToggle" class="edit-toggle">
                </div>
            </div>
            <div id="previewContent" class="preview-content" placeholder="请加载或创建Markdown文件..."></div>
        </div>
    </div>
    
    <div id="backToTop" class="back-to-top" title="返回顶部">↑</div>
    <div id="themeToggle" class="theme-toggle" title="切换主题">🌓</div>
    
    <!-- <footer>
        Markdown标题序号编辑器 &copy; 2025 | 双击HTML文件即可使用
    </footer> -->

    <script>
        // DOM元素
        const dropArea = document.getElementById('dropArea');
        const fileInput = document.getElementById('fileInput');
        const fileInfo = document.getElementById('fileInfo');
        const fileName = document.getElementById('fileName');
        const fileSize = document.getElementById('fileSize');
        const addNumbersBtn = document.getElementById('addNumbersBtn');
        const removeNumbersBtn = document.getElementById('removeNumbersBtn');
        const copyBtn = document.getElementById('copyBtn');
        const downloadBtn = document.getElementById('downloadBtn');
        const resetBtn = document.getElementById('resetBtn');
        const editToggle = document.getElementById('editToggle');
        const previewContainer = document.getElementById('previewContainer');
        const previewContent = document.getElementById('previewContent');
        const backToTop = document.getElementById('backToTop');
        const themeToggle = document.getElementById('themeToggle');
        const notificationContainer = document.getElementById('notificationContainer');
        const newFileBtn = document.getElementById('newFileBtn');
        const newFileModal = document.getElementById('newFileModal');
        const newFileName = document.getElementById('newFileName');
        const cancelNewFile = document.getElementById('cancelNewFile');
        const confirmNewFile = document.getElementById('confirmNewFile');
        const toolbar = document.getElementById('toolbar');

        let currentFile = null;
        let originalContent = '';
        let modifiedContent = '';
        let isTechTheme = false;

        // 初始化 - 监听滚动显示返回顶部按钮
        window.addEventListener('scroll', () => {
            if (window.pageYOffset > 300) {
                backToTop.classList.add('visible');
            } else {
                backToTop.classList.remove('visible');
            }
        });

        // 返回顶部功能
        backToTop.addEventListener('click', () => {
            window.scrollTo({
                top: 0,
                behavior: 'smooth'
            });
        });

        // 切换主题
        themeToggle.addEventListener('click', () => {
            isTechTheme = !isTechTheme;
            if (isTechTheme) {
                document.body.classList.add('tech-theme');
                showNotification('已切换至科技主题', 'info');
            } else {
                document.body.classList.remove('tech-theme');
                showNotification('已切换至默认主题', 'info');
            }
        });

        // 拖拽事件处理
        dropArea.addEventListener('click', (e) => {
            if (e.target === dropArea || e.target === dropArea.querySelector('.icon') || e.target === dropArea.querySelector('p')) {
                fileInput.click();
            }
        });

        fileInput.addEventListener('change', (e) => {
            if (e.target.files.length) {
                handleFile(e.target.files[0]);
            }
        });

        // 阻止默认拖拽行为
        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
            dropArea.addEventListener(eventName, preventDefaults, false);
            document.body.addEventListener(eventName, preventDefaults, false);
        });

        function preventDefaults(e) {
            e.preventDefault();
            e.stopPropagation();
        }

        // 高亮拖拽区域
        ['dragenter', 'dragover'].forEach(eventName => {
            dropArea.addEventListener(eventName, highlight, false);
        });

        ['dragleave', 'drop'].forEach(eventName => {
            dropArea.addEventListener(eventName, unhighlight, false);
        });

        function highlight() {
            dropArea.classList.add('highlight');
        }

        function unhighlight() {
            dropArea.classList.remove('highlight');
        }

        // 处理拖放文件
        dropArea.addEventListener('drop', (e) => {
            const dt = e.dataTransfer;
            const file = dt.files[0];
            
            if (file && (file.name.endsWith('.md') || file.name.endsWith('.markdown'))) {
                handleFile(file);
            } else {
                showNotification('请拖拽Markdown文件(.md或.markdown)', 'warning');
            }
        }, false);

        // 新建文件处理
        newFileBtn.addEventListener('click', (e) => {
            e.stopPropagation(); // 阻止事件冒泡
            newFileModal.style.display = 'flex';
            newFileName.focus();
        });

        cancelNewFile.addEventListener('click', () => {
            newFileModal.style.display = 'none';
            newFileName.value = '';
        });

        confirmNewFile.addEventListener('click', () => {
            const name = newFileName.value.trim();
            if (name) {
                const filename = name.endsWith('.md') ? name : `${name}.md`;
                createNewFile(filename);
                newFileModal.style.display = 'none';
                newFileName.value = '';
            } else {
                showNotification('请输入有效的文件名', 'warning');
            }
        });

        function createNewFile(filename) {
            currentFile = { name: filename };
            originalContent = '';
            modifiedContent = '';
            fileName.textContent = filename;
            updateFileSize();
            fileInfo.classList.add('show');
            updatePreview();
            enableButtons();
            editToggle.checked = true;
            toggleEditMode();
            previewContent.focus();
            showNotification(`已创建新文件: ${filename}`, 'success');
        }

        // 文件处理
        function handleFile(file) {
            currentFile = file;
            fileName.textContent = file.name;
            fileInfo.classList.add('show');
            
            const reader = new FileReader();
            reader.onload = (e) => {
                originalContent = e.target.result;
                modifiedContent = originalContent;
                updateFileSize();
                updatePreview();
                enableButtons();
                showNotification('文件已加载: ' + file.name, 'success');
            };
            reader.onerror = (e) => {
                showNotification('读取文件失败: ' + e.target.error, 'error');
            };
            reader.readAsText(file);
        }

        // 更新文件大小显示
        function updateFileSize() {
            const content = previewContent.textContent;
            const bytes = new Blob([content]).size;
            fileSize.textContent = formatFileSize(bytes);
        }

        function formatFileSize(bytes) {
            if (bytes === 0) return '0 Bytes';
            const k = 1024;
            const sizes = ['Bytes', 'KB', 'MB', 'GB'];
            const i = Math.floor(Math.log(bytes) / Math.log(k));
            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
        }

        function enableButtons() {
            addNumbersBtn.disabled = false;
            removeNumbersBtn.disabled = false;
            copyBtn.disabled = false;
            downloadBtn.disabled = false;
            previewContainer.style.display = 'block';
        }

        // 编辑模式切换
        editToggle.addEventListener('change', toggleEditMode);

        function toggleEditMode() {
            if (editToggle.checked) {
                previewContent.contentEditable = true;
                previewContent.classList.add('editable');
                showNotification('已进入编辑模式', 'info');
                previewContent.focus();
            } else {
                previewContent.contentEditable = false;
                previewContent.classList.remove('editable');
                modifiedContent = previewContent.textContent;
                updateFileSize();
                showNotification('已退出编辑模式', 'info');
            }
        }

        // 处理回车键
        previewContent.addEventListener('keydown', (e) => {
            if (e.key === 'Enter' && !e.shiftKey) {
                // 阻止默认行为并插入换行
                e.preventDefault();
                document.execCommand('insertLineBreak');
            }
        });

        // 内容变化时更新文件大小
        previewContent.addEventListener('input', () => {
            updateFileSize();
        });

        // 标题序号处理
        addNumbersBtn.addEventListener('click', () => {
            modifiedContent = addHeadingNumbers(previewContent.textContent);
            updatePreview();
            showNotification('已添加标题序号', 'success');
        });

        removeNumbersBtn.addEventListener('click', () => {
            modifiedContent = removeHeadingNumbers(previewContent.textContent);
            updatePreview();
            showNotification('已移除标题序号', 'success');
        });

        function addHeadingNumbers(content) {
            const lines = content.split('\n');
            let counters = [0, 0, 0, 0, 0, 0]; // h1到h6
            let maxLevel = 6;
            
            // 首先确定文档中最大的标题级别
            const findMaxLevel = () => {
                for (const line of lines) {
                    if (line.startsWith('#')) {
                        const level = line.match(/^#+/)[0].length;
                        if (level < maxLevel) {
                            maxLevel = level;
                        }
                    }
                }
                return maxLevel;
            };
            
            maxLevel = findMaxLevel();
            
            return lines.map(line => {
                const match = line.match(/^(#+)\s*(?:\d+\.?\.?)*\s*(.*)/);
                if (match) {
                    const level = match[1].length;
                    const titleText = match[2].trim();
                    
                    // 重置子级计数器
                    for (let i = level; i < counters.length; i++) {
                        counters[i] = 0;
                    }
                    
                    // 增加当前级别计数器
                    counters[level - 1]++;
                    
                    // 生成序号 (只从最大级别开始)
                    let number = '';
                    for (let i = maxLevel - 1; i < level; i++) {
                        if (i === maxLevel - 1) {
                            number += counters[i];
                        } else {
                            number += '.' + counters[i];
                        }
                    }
                    
                    return `${match[1]} ${number}. ${titleText}`;
                }
                return line;
            }).join('\n');
        }

        function removeHeadingNumbers(content) {
            return content.replace(/^(#+)\s*(?:\d+\.?\.?)*\s*(\.?)\s*/gm, (match, hashes, dot) => {
                return `${hashes} `;
            });
        }

        // 复制内容到剪贴板
        copyBtn.addEventListener('click', async () => {
            try {
                await navigator.clipboard.writeText(previewContent.textContent);
                showNotification('内容已复制到剪贴板', 'success');
            } catch (err) {
                showNotification('复制失败: ' + err, 'error');
            }
        });

        // 重置功能
        resetBtn.addEventListener('click', () => {
            if (confirm('确定要重置编辑器吗?所有未保存的更改将丢失。')) {
                currentFile = null;
                originalContent = '';
                modifiedContent = '';
                fileInfo.classList.remove('show');
                previewContainer.style.display = 'none';
                addNumbersBtn.disabled = true;
                removeNumbersBtn.disabled = true;
                copyBtn.disabled = true;
                downloadBtn.disabled = true;
                fileInput.value = '';
                editToggle.checked = false;
                toggleEditMode();
                showNotification('编辑器已重置', 'info');
            }
        });

        // 显示通知消息
        function showNotification(message, type = 'info') {
            const notification = document.createElement('div');
            notification.className = `notification ${type}`;
            notification.textContent = message;
            notificationContainer.appendChild(notification);
            
            // 触发动画
            setTimeout(() => {
                notification.classList.add('show');
            }, 10);
            
            // 3秒后移除通知
            setTimeout(() => {
                notification.classList.remove('show');
                setTimeout(() => {
                    notification.remove();
                }, 400);
            }, 3000);
        }

        // 预览更新
        function updatePreview() {
            previewContent.textContent = modifiedContent;
            updateFileSize();
        }

        // 下载处理
        downloadBtn.addEventListener('click', () => {
            if (!currentFile) return;
            
            const blob = new Blob([previewContent.textContent], { type: 'text/markdown' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = currentFile.name;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            showNotification('文件已下载: ' + currentFile.name, 'success');
        });
    </script>
</body>
</html>
相关推荐
程序员阿超的博客42 分钟前
React动态渲染:如何用map循环渲染一个列表(List)
前端·react.js·前端框架
magic 24544 分钟前
模拟 AJAX 提交 form 表单及请求头设置详解
前端·javascript·ajax
小小小小宇5 小时前
前端 Service Worker
前端
只喜欢赚钱的棉花没有糖6 小时前
http的缓存问题
前端·javascript·http
小小小小宇6 小时前
请求竞态问题统一封装
前端
loriloy6 小时前
前端资源帖
前端
源码超级联盟6 小时前
display的block和inline-block有什么区别
前端
蓝婷儿6 小时前
Python 爬虫入门 Day 2 - HTML解析入门(使用 BeautifulSoup)
爬虫·python·html
GISer_Jing6 小时前
前端构建工具(Webpack\Vite\esbuild\Rspack)拆包能力深度解析
前端·webpack·node.js
让梦想疯狂6 小时前
开源、免费、美观的 Vue 后台管理系统模板
前端·javascript·vue.js