// ==UserScript== // @name DeepSeek 代码块折叠 + 表格优化导出 // @namespace https://github.com/yourname/deepseek-tools // @version 2.1.0 // @description 代码块折叠(阈值/预览可配)+ 表格样式优化(始终生效)+ PNG/CSV 导出(可开关) // @tag 工具 // @tag 优化 // @tag DeepSeek // @author 友野YouyEr // @icon https://fe-static.deepseek.com/chat/favicon.svg // @match https://chat.deepseek.com/* // @match https://www.deepseek.com/* // @match https://deepseek.com/* // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_registerMenuCommand // @require https://cdn.jsdelivr.net/npm/html2canvas@1.4.1/dist/html2canvas.min.js // @run-at document-end // ==/UserScript== (function() { 'use strict'; // ==================== 1. 代码块折叠相关配置 ==================== const STORAGE_FOLD_THRESHOLD = 'deepseek_fold_threshold'; const STORAGE_PREVIEW_LINES = 'deepseek_fold_preview_lines'; let foldThreshold = GM_getValue(STORAGE_FOLD_THRESHOLD, 20); let previewLines = GM_getValue(STORAGE_PREVIEW_LINES, 0); let enablePreviewLines = previewLines > 0; const btnTextFold = '折叠'; const btnTextUnfold = '展开'; // ==================== 2. 表格按钮开关配置 ==================== const STORAGE_TABLE_BUTTONS_ENABLED = 'deepseek_table_buttons_enabled'; let tableButtonsEnabled = GM_getValue(STORAGE_TABLE_BUTTONS_ENABLED, true); // ==================== 3. SVG 图标(代码块折叠用) ==================== const ICON_CHEVRON_DOWN = ``; const ICON_CHEVRON_UP = ``; // ==================== 4. 通用 Toast ==================== function showToast(message, duration = 2000) { const existingToast = document.getElementById('ds-fold-toast'); if (existingToast) existingToast.remove(); const toast = document.createElement('div'); toast.id = 'ds-fold-toast'; toast.textContent = message; toast.style.cssText = ` position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%); background: rgba(0, 0, 0, 0.8); backdrop-filter: blur(8px); color: white; padding: 10px 20px; border-radius: 8px; font-size: 14px; font-family: system-ui, -apple-system, 'Segoe UI', sans-serif; z-index: 10001; opacity: 0; transition: opacity 0.2s ease; pointer-events: none; white-space: nowrap; box-shadow: 0 2px 8px rgba(0,0,0,0.2); `; document.body.appendChild(toast); setTimeout(() => toast.style.opacity = '1', 10); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 200); }, duration); } // ==================== 5. 配置弹窗(代码块用) ==================== function showConfigDialog(title, description, currentValue, storageKey, onConfirm) { const existingDialog = document.getElementById('ds-fold-config-dialog'); if (existingDialog) existingDialog.remove(); const overlay = document.createElement('div'); overlay.id = 'ds-fold-config-overlay'; overlay.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); backdrop-filter: blur(4px); z-index: 10000; display: flex; align-items: center; justify-content: center; `; const dialog = document.createElement('div'); dialog.id = 'ds-fold-config-dialog'; dialog.style.cssText = ` background: var(--ds-bg-primary, #1e1e2f); border-radius: 12px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2); width: 360px; max-width: 90%; padding: 24px; font-family: system-ui, -apple-system, 'Segoe UI', sans-serif; color: var(--ds-text-primary, #e2e2e2); `; const titleEl = document.createElement('h3'); titleEl.textContent = title; titleEl.style.cssText = `margin: 0 0 16px 0; font-size: 18px; font-weight: 500;`; const descEl = document.createElement('p'); descEl.textContent = description; descEl.style.cssText = `margin: 0 0 20px 0; font-size: 13px; opacity: 0.7; line-height: 1.4;`; const inputWrapper = document.createElement('div'); inputWrapper.style.marginBottom = '24px'; const input = document.createElement('input'); input.type = 'number'; input.value = currentValue; input.min = 0; input.step = 1; input.style.cssText = ` width: 100%; padding: 10px 12px; border-radius: 8px; border: 1px solid rgba(128, 128, 128, 0.3); background: var(--ds-bg-secondary, #2a2a36); color: var(--ds-text-primary, #e2e2e2); font-size: 14px; box-sizing: border-box; outline: none; transition: border-color 0.2s; `; input.addEventListener('focus', () => input.style.borderColor = 'rgba(128, 128, 128, 0.6)'); input.addEventListener('blur', () => input.style.borderColor = 'rgba(128, 128, 128, 0.3)'); inputWrapper.appendChild(input); const buttonGroup = document.createElement('div'); buttonGroup.style.display = 'flex'; buttonGroup.style.gap = '12px'; buttonGroup.style.justifyContent = 'flex-end'; const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.style.cssText = ` padding: 8px 16px; border-radius: 8px; border: none; background: transparent; color: var(--ds-text-primary, #e2e2e2); cursor: pointer; font-size: 14px; transition: background 0.2s; `; cancelBtn.addEventListener('mouseenter', () => cancelBtn.style.background = 'rgba(128, 128, 128, 0.1)'); cancelBtn.addEventListener('mouseleave', () => cancelBtn.style.background = 'transparent'); cancelBtn.addEventListener('click', () => overlay.remove()); const confirmBtn = document.createElement('button'); confirmBtn.textContent = '确认'; confirmBtn.style.cssText = ` padding: 8px 16px; border-radius: 8px; border: none; background: #0f6e4a; color: white; cursor: pointer; font-size: 14px; transition: background 0.2s; `; confirmBtn.addEventListener('mouseenter', () => confirmBtn.style.background = '#0a5a3c'); confirmBtn.addEventListener('mouseleave', () => confirmBtn.style.background = '#0f6e4a'); confirmBtn.addEventListener('click', () => { let newValue = parseInt(input.value, 10); if (isNaN(newValue)) newValue = 0; if (newValue < 0) newValue = 0; GM_setValue(storageKey, newValue); overlay.remove(); if (onConfirm) onConfirm(newValue); showToast(`已设置为 ${newValue === 0 ? '关闭' : newValue}`, 1800); setTimeout(() => location.reload(), 1800); }); buttonGroup.appendChild(cancelBtn); buttonGroup.appendChild(confirmBtn); dialog.appendChild(titleEl); dialog.appendChild(descEl); dialog.appendChild(inputWrapper); dialog.appendChild(buttonGroup); overlay.appendChild(dialog); document.body.appendChild(overlay); overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.remove(); }); input.addEventListener('keypress', (e) => { if (e.key === 'Enter') confirmBtn.click(); }); input.focus(); } // ==================== 6. 菜单命令 ==================== GM_registerMenuCommand('⚙️ 设置自动折叠阈值', () => { showConfigDialog( '自动折叠阈值设置', '设置代码块自动折叠的行数阈值(0 表示禁用自动折叠)', foldThreshold, STORAGE_FOLD_THRESHOLD, (newVal) => { foldThreshold = newVal; } ); }); GM_registerMenuCommand('⚙️ 设置折叠预览行数', () => { showConfigDialog( '折叠预览行数设置', '设置折叠时显示的行数(0 表示关闭预览,完全隐藏代码块)', previewLines, STORAGE_PREVIEW_LINES, (newVal) => { previewLines = newVal; } ); }); GM_registerMenuCommand(`🖼️ 表格导出按钮: ${tableButtonsEnabled ? '开启' : '关闭'}`, () => { tableButtonsEnabled = !tableButtonsEnabled; GM_setValue(STORAGE_TABLE_BUTTONS_ENABLED, tableButtonsEnabled); showToast(`表格导出按钮已${tableButtonsEnabled ? '开启' : '关闭'},刷新页面生效`, 2000); setTimeout(() => location.reload(), 2000); }); // ==================== 7. 全局样式(合并) ==================== GM_addStyle(` /* 代码块折叠样式 */ .ds-fold-btn { background: transparent; border: none; border-radius: 12px; font-size: 13px; padding: 4px 8px; cursor: pointer; transition: all 0.2s ease; font-family: system-ui, -apple-system, 'Segoe UI', monospace; user-select: none; display: inline-flex; align-items: center; gap: 2px; margin-left: 0; opacity: 0.7; } .ds-fold-btn:hover { background: rgba(128, 128, 128, 0.2); opacity: 1; } .ds-fold-btn .fold-icon { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; } .ds-fold-btn svg { width: 20px; height: 20px; display: block; } .efa13877 .ds-fold-btn { margin-left: 4px; } .ds-fold-preview::after { content: " ..."; display: block; text-align: center; color: inherit; opacity: 0.6; margin-top: 4px; } /* 表格样式(始终生效) */ .ds-markdown table { width: 100% !important; border-collapse: separate !important; border-spacing: 0 !important; margin: 1em 0 !important; background-color: #ffffff !important; border-radius: 12px !important; overflow: hidden !important; box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05), 0 1px 2px -1px rgba(0, 0, 0, 0.05) !important; position: relative; } .ds-markdown th, .ds-markdown td { border: 1px solid #e5e7eb !important; padding: 12px 16px !important; vertical-align: top !important; font-size: 14px !important; line-height: 1.5 !important; } .ds-markdown th { background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%) !important; font-weight: 600 !important; color: #1f2937 !important; border-bottom: 1px solid #e5e7eb !important; letter-spacing: 0.02em !important; } .ds-markdown tbody tr:nth-child(even) { background-color: #fafafa !important; } .ds-markdown tbody tr:hover { background-color: #eff6ff !important; transition: background-color 0.2s ease !important; } /* 表格按钮样式(仅在开关开启时才会出现) */ .table-internal-buttons { position: absolute; bottom: 12px; right: 12px; display: flex; flex-direction: column; gap: 8px; z-index: 10; opacity: 0; visibility: hidden; transition: opacity 0.2s ease, visibility 0.2s ease; pointer-events: none; } .ds-markdown table:hover .table-internal-buttons, .table-internal-buttons:hover { opacity: 1; visibility: visible; pointer-events: auto; } .internal-export-btn { width: 32px; height: 32px; background: rgba(255, 255, 255, 0.95); backdrop-filter: blur(4px); border: 1px solid #e2e8f0; border-radius: 8px; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 2px 6px rgba(0,0,0,0.1); transition: all 0.2s ease; font-size: 16px; position: relative; } .internal-export-btn:hover { background: #ffffff; transform: scale(1.05); box-shadow: 0 4px 10px rgba(0,0,0,0.15); border-color: #cbd5e1; } .internal-export-btn:active { transform: scale(0.98); } .internal-export-btn::after { content: attr(data-tooltip); position: absolute; right: 40px; top: 50%; transform: translateY(-50%); background: #1f2937; color: white; font-size: 12px; padding: 4px 8px; border-radius: 6px; white-space: nowrap; opacity: 0; visibility: hidden; transition: 0.1s; pointer-events: none; } .internal-export-btn:hover::after { opacity: 1; visibility: visible; } `); // ==================== 8. 代码块折叠核心逻辑(不变) ==================== const processedAttr = 'data-fold-processed'; function getLineCount(preEl) { const text = preEl.innerText || preEl.textContent || ''; let lines = text.split('\n'); if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop(); return lines.length; } function getLineHeight(preEl) { const style = window.getComputedStyle(preEl); let lineHeight = style.lineHeight; if (lineHeight === 'normal') { const fontSize = parseFloat(style.fontSize); lineHeight = fontSize * 1.2 + 'px'; } return parseFloat(lineHeight); } function shouldUsePreviewMode(preEl) { if (!enablePreviewLines) return false; const lineCount = getLineCount(preEl); return lineCount > previewLines; } function collapseBlock(preEl, btn) { if (shouldUsePreviewMode(preEl)) { const lineHeight = getLineHeight(preEl); const maxHeight = lineHeight * previewLines; if (!preEl.dataset.origMaxHeight) { preEl.dataset.origMaxHeight = preEl.style.maxHeight || ''; preEl.dataset.origOverflow = preEl.style.overflow || ''; } preEl.style.maxHeight = maxHeight + 'px'; preEl.style.overflow = 'hidden'; preEl.classList.add('ds-fold-preview'); } else { if (!preEl.dataset.origDisplay) { preEl.dataset.origDisplay = window.getComputedStyle(preEl).display; } preEl.style.display = 'none'; preEl.classList.remove('ds-fold-preview'); } const iconDiv = btn.querySelector('.fold-icon'); if (iconDiv) iconDiv.innerHTML = ICON_CHEVRON_UP; btn.querySelector('span').textContent = btnTextUnfold; btn.setAttribute('aria-label', '展开代码块'); } function expandBlock(preEl, btn) { if (preEl.dataset.origMaxHeight !== undefined) { preEl.style.maxHeight = preEl.dataset.origMaxHeight || ''; preEl.style.overflow = preEl.dataset.origOverflow || ''; preEl.classList.remove('ds-fold-preview'); } if (preEl.dataset.origDisplay !== undefined) { preEl.style.display = preEl.dataset.origDisplay || ''; } else { preEl.style.display = ''; } const iconDiv = btn.querySelector('.fold-icon'); if (iconDiv) iconDiv.innerHTML = ICON_CHEVRON_DOWN; btn.querySelector('span').textContent = btnTextFold; btn.setAttribute('aria-label', '折叠代码块'); } function findButtonContainer(preEl) { let parent = preEl.closest('.md-code-block'); if (!parent) return null; let btnGroup = parent.querySelector('.efa13877'); if (btnGroup) return btnGroup; btnGroup = parent.querySelector('[class*="button-group"], [class*="actions"], [class*="buttons"]'); return btnGroup || null; } function createFoldButton(preEl) { if (!preEl.dataset.origDisplay) { preEl.dataset.origDisplay = window.getComputedStyle(preEl).display; } const shouldAutoFold = foldThreshold > 0 && getLineCount(preEl) > foldThreshold; let isFolded = false; if (shouldAutoFold) { if (shouldUsePreviewMode(preEl)) { const lineHeight = getLineHeight(preEl); const maxHeight = lineHeight * previewLines; if (!preEl.dataset.origMaxHeight) { preEl.dataset.origMaxHeight = preEl.style.maxHeight || ''; preEl.dataset.origOverflow = preEl.style.overflow || ''; } preEl.style.maxHeight = maxHeight + 'px'; preEl.style.overflow = 'hidden'; preEl.classList.add('ds-fold-preview'); } else { preEl.style.display = 'none'; preEl.classList.remove('ds-fold-preview'); } isFolded = true; } const btn = document.createElement('button'); btn.className = 'ds-fold-btn'; const iconDiv = document.createElement('div'); iconDiv.className = 'fold-icon'; iconDiv.innerHTML = isFolded ? ICON_CHEVRON_UP : ICON_CHEVRON_DOWN; const textSpan = document.createElement('span'); textSpan.textContent = isFolded ? btnTextUnfold : btnTextFold; btn.appendChild(iconDiv); btn.appendChild(textSpan); btn.setAttribute('aria-label', isFolded ? '展开代码块' : '折叠代码块'); btn.addEventListener('click', (e) => { e.stopPropagation(); let currentlyFolded; if (preEl.dataset.origMaxHeight !== undefined && preEl.style.maxHeight !== '' && preEl.style.maxHeight !== 'none') { currentlyFolded = true; } else if (preEl.style.display === 'none') { currentlyFolded = true; } else { currentlyFolded = false; } if (currentlyFolded) { expandBlock(preEl, btn); } else { collapseBlock(preEl, btn); } }); return btn; } function addFoldButtonToCodeBlock(preEl) { if (preEl.hasAttribute(processedAttr)) return; const targetContainer = findButtonContainer(preEl); if (targetContainer) { if (targetContainer.querySelector('.ds-fold-btn')) { preEl.setAttribute(processedAttr, 'true'); return; } targetContainer.appendChild(createFoldButton(preEl)); } else { const wrapper = document.createElement('div'); wrapper.className = 'ds-fold-btn-wrapper'; wrapper.style.textAlign = 'right'; wrapper.style.marginBottom = '6px'; wrapper.appendChild(createFoldButton(preEl)); preEl.parentNode.insertBefore(wrapper, preEl); } preEl.setAttribute(processedAttr, 'true'); } function processAllExistingCodeBlocks() { document.querySelectorAll('pre').forEach(block => { if (!block.hasAttribute(processedAttr)) { addFoldButtonToCodeBlock(block); } }); } function cleanupLegacyWrappers() { document.querySelectorAll('.ds-fold-btn-wrapper').forEach(w => w.remove()); } function deduplicateButtons() { document.querySelectorAll('.efa13877').forEach(container => { const btns = container.querySelectorAll('.ds-fold-btn'); if (btns.length > 1) { for (let i = 1; i < btns.length; i++) btns[i].remove(); } }); } function observeCodeBlocks() { const observer = new MutationObserver((mutations) => { for (const mutation of mutations) { if (mutation.type === 'childList' && mutation.addedNodes.length) { for (const node of mutation.addedNodes) { if (node.nodeType === Node.ELEMENT_NODE) { if (node.matches && node.matches('pre')) { addFoldButtonToCodeBlock(node); } if (node.querySelectorAll) { node.querySelectorAll('pre').forEach(innerPre => addFoldButtonToCodeBlock(innerPre)); } } } } } }); observer.observe(document.body, { childList: true, subtree: true }); } // ==================== 9. 表格优化核心逻辑(拆分为样式修复 + 按钮添加) ==================== // 9.1 表格样式修复(始终执行,不依赖开关) function applyTableStyles(table) { // 宽度修复:参考虚拟列表宽度 const virtualListContainer = document.querySelector('.ds-virtual-list-visible-items'); let availableWidth = null; if (virtualListContainer) { availableWidth = virtualListContainer.clientWidth; virtualListContainer.style.overflowX = 'visible'; virtualListContainer.style.maxWidth = '100%'; } if (availableWidth && availableWidth > 0) { table.style.maxWidth = `${availableWidth}px`; } else { table.style.maxWidth = '100%'; } table.style.width = '100%'; table.style.tableLayout = 'fixed'; if (getComputedStyle(table).position !== 'relative') { table.style.position = 'relative'; } // 单元格换行 const cells = table.querySelectorAll('th, td'); cells.forEach(cell => { cell.style.whiteSpace = 'normal'; cell.style.wordWrap = 'break-word'; cell.style.overflowWrap = 'break-word'; cell.style.wordBreak = 'break-word'; }); // 列宽均分 const headerRow = table.querySelector('thead tr') || table.querySelector('tr'); if (headerRow) { const colCount = headerRow.cells.length; if (colCount > 0) { const colPercent = (100 / colCount).toFixed(2) + '%'; for (let i = 0; i < colCount; i++) { if (headerRow.cells[i]) headerRow.cells[i].style.width = colPercent; } } } // 清除父级滚动条 let parent = table.parentElement; while (parent && parent !== document.body) { const computed = window.getComputedStyle(parent); if (computed.overflowX === 'auto' || computed.overflowX === 'scroll') { parent.style.overflowX = 'visible'; } if (parent.style.maxWidth && parent.style.maxWidth !== 'none') { parent.style.maxWidth = '100%'; } parent = parent.parentElement; } } // 9.2 导出功能函数(不依赖开关,但只在添加按钮时用到) async function exportTableAsPNG(table) { if (!window.html2canvas) { alert('html2canvas 库未加载,请检查网络或稍后再试。'); return; } try { const btnContainer = table.querySelector('.table-internal-buttons'); let originalDisplay = null; if (btnContainer) { originalDisplay = btnContainer.style.display; btnContainer.style.display = 'none'; } const canvas = await html2canvas(table, { scale: 2, backgroundColor: '#ffffff', logging: false, useCORS: false }); if (btnContainer) btnContainer.style.display = originalDisplay; const link = document.createElement('a'); link.download = `table_${Date.now()}.png`; link.href = canvas.toDataURL('image/png'); link.click(); } catch (err) { console.error('导出PNG失败:', err); alert('导出失败,请查看控制台错误信息。'); } } function exportTableAsCSV(table) { const rows = []; const thead = table.querySelector('thead'); if (thead) { thead.querySelectorAll('tr').forEach(tr => { const rowData = []; tr.querySelectorAll('th').forEach(th => rowData.push(getCellText(th))); if (rowData.length) rows.push(rowData); }); } const tbody = table.querySelector('tbody'); if (tbody) { tbody.querySelectorAll('tr').forEach(tr => { const rowData = []; tr.querySelectorAll('td').forEach(td => rowData.push(getCellText(td))); if (rowData.length) rows.push(rowData); }); } else { table.querySelectorAll('tr').forEach(tr => { const rowData = []; tr.querySelectorAll('td, th').forEach(cell => rowData.push(getCellText(cell))); if (rowData.length) rows.push(rowData); }); } if (rows.length === 0) { alert('表格无数据可导出'); return; } const csvContent = rows.map(row => row.map(cell => { if (cell.includes(',') || cell.includes('"') || cell.includes('\n')) { cell = cell.replace(/"/g, '""'); cell = `"${cell}"`; } return cell; }).join(',') ).join('\n'); const blob = new Blob(['\uFEFF' + csvContent], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.href = url; link.setAttribute('download', `table_${Date.now()}.csv`); document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } function getCellText(cell) { let text = ''; cell.childNodes.forEach(node => { if (node.nodeType === Node.TEXT_NODE) { text += node.textContent; } else if (node.nodeName === 'BR') { text += '\n'; } else if (node.nodeType === Node.ELEMENT_NODE) { text += getCellText(node); } }); return text.replace(/\s+/g, ' ').trim(); } // 9.3 添加按钮到单个表格(受开关控制) function addButtonsToTable(table) { if (!tableButtonsEnabled) return; if (table.getAttribute('data-internal-buttons-added') === 'true') return; table.setAttribute('data-internal-buttons-added', 'true'); const btnContainer = document.createElement('div'); btnContainer.className = 'table-internal-buttons'; const pngBtn = document.createElement('button'); pngBtn.className = 'internal-export-btn'; pngBtn.innerHTML = '📸'; pngBtn.setAttribute('data-tooltip', '导出为 PNG'); pngBtn.addEventListener('click', async (e) => { e.stopPropagation(); await exportTableAsPNG(table); }); const csvBtn = document.createElement('button'); csvBtn.className = 'internal-export-btn'; csvBtn.innerHTML = '📄'; csvBtn.setAttribute('data-tooltip', '导出为 CSV'); csvBtn.addEventListener('click', (e) => { e.stopPropagation(); exportTableAsCSV(table); }); btnContainer.appendChild(pngBtn); btnContainer.appendChild(csvBtn); table.appendChild(btnContainer); // 智能显隐:鼠标进入表格或按钮容器时显示,离开延迟隐藏 let hideTimer = null; const showButtons = () => { if (hideTimer) clearTimeout(hideTimer); btnContainer.style.opacity = '1'; btnContainer.style.visibility = 'visible'; btnContainer.style.pointerEvents = 'auto'; }; const hideButtons = () => { if (hideTimer) clearTimeout(hideTimer); hideTimer = setTimeout(() => { btnContainer.style.opacity = ''; btnContainer.style.visibility = ''; btnContainer.style.pointerEvents = ''; }, 300); }; table.addEventListener('mouseenter', showButtons); table.addEventListener('mouseleave', hideButtons); btnContainer.addEventListener('mouseenter', showButtons); btnContainer.addEventListener('mouseleave', hideButtons); } // 9.4 统一处理所有表格(样式修复 + 可选按钮) function processAllTables() { const markdownContainers = document.querySelectorAll('.ds-markdown'); if (!markdownContainers.length) return; markdownContainers.forEach(container => { container.style.overflowX = 'visible'; container.style.maxWidth = '100%'; const tables = container.querySelectorAll('table'); tables.forEach(table => { applyTableStyles(table); // 始终修复样式 addButtonsToTable(table); // 根据开关决定是否加按钮 }); }); } // 9.5 监听表格变化(新增表格时同样处理) function observeTables() { const observer = new MutationObserver(() => { processAllTables(); }); observer.observe(document.body, { childList: true, subtree: true }); // 页面加载完成和窗口缩放时也重新处理 window.addEventListener('load', processAllTables); window.addEventListener('resize', () => { clearTimeout(window._resizeFix); window._resizeFix = setTimeout(processAllTables, 100); }); setInterval(processAllTables, 2000); processAllTables(); } // ==================== 10. 初始化入口 ==================== function init() { // 代码块折叠 cleanupLegacyWrappers(); deduplicateButtons(); processAllExistingCodeBlocks(); observeCodeBlocks(); // 表格优化(样式修复始终执行,按钮根据开关决定) observeTables(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();