用 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>
相关推荐
恋猫de小郭16 分钟前
Android Studio Cloud 正式上线,不只是 Android,随时随地改 bug
android·前端·flutter
清岚_lxn5 小时前
原生SSE实现AI智能问答+Vue3前端打字机流效果
前端·javascript·人工智能·vue·ai问答
ZoeLandia5 小时前
Element UI 设置 el-table-column 宽度 width 为百分比无效
前端·ui·element-ui
橘子味的冰淇淋~6 小时前
解决 vite.config.ts 引入scss 预处理报错
前端·vue·scss
小小小小宇7 小时前
V8 引擎垃圾回收机制详解
前端
lauo8 小时前
智体知识库:ai-docs对分布式智体编程语言Poplang和javascript的语法的比较(知识库问答)
开发语言·前端·javascript·分布式·机器人·开源
拉不动的猪8 小时前
设计模式之------单例模式
前端·javascript·面试
一袋米扛几楼988 小时前
【React框架】什么是 Vite?如何使用vite自动生成react的目录?
前端·react.js·前端框架
Alt.98 小时前
SpringMVC基础二(RestFul、接收数据、视图跳转)
java·开发语言·前端·mvc
进取星辰9 小时前
1、从零搭建魔法工坊:React 19 新手村生存指南
前端·react.js·前端框架