// ==UserScript==
// @name Codetop Notes 增强
// @namespace http://tampermonkey.net/
// @version 0.2
// @description 在 Codetop 题目列表每行“笔记”按钮旁插入自定义按钮(初版)
// @author YourName
// @match https://codetop.cc/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 工具函数:插入自定义按钮
function insertCustomNoteButtons() {
// 兼容所有“笔记”按钮(无论列号、class如何变化)
const noteSpans = Array.from(document.querySelectorAll('table tr td .el-button > span'))
.filter(span => span.textContent.trim() === '笔记');
noteSpans.forEach(span => {
const noteBtn = span.parentElement;
const btnGroup = noteBtn.parentElement;
// 避免重复插入
if (btnGroup.querySelector('.ctn-custom-note-btn')) {
// 按钮已存在,但要更新状态
const existingBtn = btnGroup.querySelector('.ctn-custom-note-btn');
let tr = existingBtn;
while (tr && tr.tagName !== 'TR') tr = tr.parentElement;
if (tr) {
const key = getRowKeyFromBtn(existingBtn);
loadNote(key).then(content => {
updateButtonState(existingBtn, content);
}).catch(err => {
console.error('更新按钮状态失败:', err);
});
}
return;
}
// 创建自定义按钮
const btn = document.createElement('button');
btn.className = noteBtn.className + ' ctn-custom-note-btn';
btn.style.marginLeft = '6px';
btn.innerHTML = '📝';
btn.style.maxWidth = '40px';
btn.style.padding = '0 8px';
btn.style.fontSize = '16px';
btn.style.whiteSpace = 'nowrap';
btn.style.height = noteBtn.offsetHeight + 'px';
btn.title = '自定义笔记';
btn.addEventListener('click', showCustomNoteModal);
btnGroup.insertBefore(btn, noteBtn.nextSibling);
// 优化:如果该题存在笔记,按钮显示绿色
let tr = btn;
while (tr && tr.tagName !== 'TR') tr = tr.parentElement;
if (tr) {
const key = getRowKeyFromBtn(btn);
loadNote(key).then(content => {
updateButtonState(btn, content);
}).catch(err => {
console.error('加载笔记失败:', err);
});
}
});
}
// IndexedDB 简单封装
const DB_NAME = 'codetop_notes';
const STORE_NAME = 'notes';
// 只做最基础的open,不做任何超时、自动删除、reset、test等
function openDB() {
return new Promise((resolve, reject) => {
const req = window.indexedDB.open(DB_NAME, 1);
req.onupgradeneeded = function(e) {
const db = e.target.result;
if (!db.objectStoreNames.contains(STORE_NAME)) {
db.createObjectStore(STORE_NAME, { keyPath: 'key' });
}
};
req.onsuccess = function(e) {
resolve(e.target.result);
};
req.onerror = function(e) {
console.error('数据库打开失败:', e);
reject(e);
};
req.onblocked = function(e) {
console.error('数据库被阻塞:', e);
reject(new Error('数据库被阻塞'));
};
});
}
function saveNote(key, content) {
return openDB().then(db => {
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readwrite');
const store = tx.objectStore(STORE_NAME);
const putRequest = store.put({ key, content, updated_at: Date.now() });
putRequest.onsuccess = () => {
resolve();
};
putRequest.onerror = (e) => {
console.error('保存笔记失败:', e);
reject(e);
};
tx.onerror = (e) => {
console.error('事务失败:', e);
reject(e);
};
});
});
}
function loadNote(key) {
return openDB().then(db => {
return new Promise((resolve, reject) => {
const tx = db.transaction(STORE_NAME, 'readonly');
const store = tx.objectStore(STORE_NAME);
const req = store.get(key);
req.onsuccess = () => {
const result = req.result ? req.result.content : '';
resolve(result);
};
req.onerror = (e) => {
console.error('加载笔记失败:', e);
reject(e);
};
tx.onerror = (e) => {
console.error('事务失败:', e);
reject(e);
};
});
});
}
// 更新按钮状态的工具函数
// ... existing code ...
function updateButtonState(btn, content) {
if (content && content.trim()) {
btn.style.background = '#e6a23c'; // 有内容时橙色
btn.style.color = '#fff';
btn.style.borderColor = '#e6a23c';
} else {
// 默认灰色
btn.style.background = '#909399';
btn.style.color = '#fff';
btn.style.borderColor = '#909399';
}
}
// ... existing code ...
// 获取当前行的题目唯一 key
function getRowKeyFromBtn(btn) {
let tr = btn;
while (tr && tr.tagName !== 'TR') tr = tr.parentElement;
if (!tr) {
console.warn('[getRowKeyFromBtn] 没找到tr', btn);
return '';
}
// 优先用 tr 的 data-row-key 或 data-id
if (tr.dataset && (tr.dataset.rowKey || tr.dataset.id)) {
return tr.dataset.rowKey || tr.dataset.id;
}
// 依次检查前两个td,优先用a标签href
const tds = tr.querySelectorAll('td');
for (let i = 0; i < Math.min(2, tds.length); i++) {
const a = tds[i].querySelector('a');
if (a && a.href) {
return a.href;
}
}
// 如果没有a标签,再用前两个td的文本
for (let i = 0; i < Math.min(2, tds.length); i++) {
const text = tds[i].textContent.trim();
if (text) {
return `${tr.rowIndex || ''}_${text}`;
}
}
// 兜底:用整行文本+行号
const key = `${tr.rowIndex || ''}_${tr.textContent.trim()}`;
if (!key) {
console.warn('[getRowKeyFromBtn] key生成失败', tr);
}
return key;
}
// 简单浮层(Modal)实现
function showCustomNoteModal(e) {
// 若已存在则不重复弹出
if (document.querySelector('.ctn-modal-mask')) return;
const btn = e.currentTarget;
const noteKey = getRowKeyFromBtn(btn);
// 先渲染 modal 骨架和 loading
const mask = document.createElement('div');
mask.className = 'ctn-modal-mask';
mask.style = `
position:fixed;left:0;top:0;width:100vw;height:100vh;background:rgba(0,0,0,0.3);z-index:9999;display:flex;align-items:center;justify-content:center;`;
const modal = document.createElement('div');
modal.className = 'ctn-modal';
// 全屏样式
modal.style = `
background:#fff;
padding:0;
border-radius:0;
width:100vw;
height:100vh;
max-width:100vw;
max-height:100vh;
box-shadow:none;
position:relative;
display:flex;
flex-direction:row;
gap:0;
overflow:hidden;
`;
// 关闭按钮
const closeBtn = document.createElement('span');
closeBtn.innerHTML = '×';
closeBtn.style = 'position:absolute;right:32px;top:24px;font-size:32px;cursor:pointer;z-index:2;';
closeBtn.title = '关闭';
closeBtn.onclick = () => {
mask.remove();
document.removeEventListener('keydown', escListener);
};
// ESC 键关闭浮层
function escListener(ev) {
if (ev.key === 'Escape') {
mask.remove();
document.removeEventListener('keydown', escListener);
}
}
document.addEventListener('keydown', escListener);
// 左右两栏骨架
const left = document.createElement('div');
left.style = 'flex:5;min-width:0;height:100vh;max-height:100vh;overflow:auto;display:flex;flex-direction:column;padding:48px 32px 32px 48px;box-sizing:border-box;';
left.innerHTML = '<div style="padding:32px;text-align:center;">加载编辑器中...</div>';
const right = document.createElement('div');
right.style = 'flex:5;min-width:0;height:100vh;max-height:100vh;overflow:auto;border-left:1px solid #eee;padding:48px 48px 32px 48px;box-sizing:border-box;';
right.innerHTML = '<div style="padding:32px;text-align:center;">加载预览中...</div>';
// 组装
modal.appendChild(closeBtn);
modal.appendChild(left);
modal.appendChild(right);
mask.appendChild(modal);
document.body.appendChild(mask);
// 加载依赖后再初始化编辑器和预览
loadEasyMDE(() => {
left.innerHTML = '';
right.innerHTML = '';
const textarea = document.createElement('textarea');
textarea.id = 'ctn-md-editor';
// 保存按钮
const saveBtn = document.createElement('button');
saveBtn.textContent = '保存';
saveBtn.style = 'margin:12px 0 0 0;align-self:flex-end;padding:6px 18px;background:#409EFF;color:#fff;border:none;border-radius:4px;cursor:pointer;font-size:16px;';
// 保存提示
const saveTip = document.createElement('span');
saveTip.style = 'margin-left:12px;color:#67c23a;font-size:14px;display:none;';
saveTip.textContent = '已保存!';
left.appendChild(textarea);
left.appendChild(saveBtn);
left.appendChild(saveTip);
right.innerHTML = '<div style="font-weight:bold;margin-bottom:8px;">实时预览</div><div id="ctn-md-preview" style="min-height:320px;"></div>';
// 初始化 EasyMDE
const easyMDE = new window.EasyMDE({
element: textarea,
autoDownloadFontAwesome: false,
status: false,
minHeight: '320px',
spellChecker: false,
placeholder: '请输入 Markdown 笔记...'
});
// 加载笔记内容
loadNote(noteKey).then(content => {
easyMDE.value(content);
updatePreview();
});
// 实时预览
function updatePreview() {
const md = easyMDE.value();
let renderMarkdown = md => md;
if (window.marked) {
renderMarkdown = typeof window.marked === 'function'
? window.marked
: (window.marked.marked ? window.marked.marked : renderMarkdown);
}
document.getElementById('ctn-md-preview').innerHTML = renderMarkdown(md);
// 强制为所有 code 标签加上 hljs 类,并手动高亮
document.getElementById('ctn-md-preview').querySelectorAll('pre code').forEach(block => {
block.classList.add('hljs');
if (window.hljs && typeof window.hljs.highlightElement === 'function') {
window.hljs.highlightElement(block);
}
});
}
easyMDE.codemirror.on('change', updatePreview);
// 保存按钮事件
saveBtn.onclick = () => {
const val = easyMDE.value();
saveNote(noteKey, val).then(() => {
saveTip.style.display = '';
setTimeout(() => { saveTip.style.display = 'none'; }, 1200);
// 保存成功后更新按钮状态
updateButtonState(btn, val);
}).catch(err => {
console.error('保存失败:', err);
alert('保存失败,请重试');
});
};
});
}
// 动态加载 EasyMDE、marked、highlight.js
function loadEasyMDE(cb) {
ensureFontAwesome();
// EasyMDE
if (!window.EasyMDE) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css';
document.head.appendChild(link);
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js';
script.onload = () => {
loadMarked(cb);
};
script.onerror = () => {
alert('EasyMDE 加载失败,请检查网络');
};
document.body.appendChild(script);
} else {
loadMarked(cb);
}
}
// 动态引入 FontAwesome 图标库
function ensureFontAwesome() {
if (!document.querySelector('link[href*="font-awesome"]')) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://cdn.jsdelivr.net/npm/[email protected]/css/font-awesome.min.css';
document.head.appendChild(link);
}
}
function loadMarked(cb) {
if (!window.marked) {
const script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/marked/marked.min.js';
script.onload = () => {
loadHLJS(cb);
};
script.onerror = () => {
alert('marked 加载失败,请检查网络');
};
document.body.appendChild(script);
} else {
loadHLJS(cb);
}
}
function loadHLJS(cb) {
if (!window.hljs) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'https://unpkg.com/@highlightjs/[email protected]/styles/atom-one-light.min.css';
document.head.appendChild(link);
const script = document.createElement('script');
script.src = 'https://unpkg.com/@highlightjs/[email protected]/highlight.min.js';
script.onload = () => {
// 配置 marked 的 highlight 选项
if (window.marked && window.hljs) {
window.marked.setOptions({
highlight: function(code, lang) {
if (window.hljs.getLanguage(lang)) {
return window.hljs.highlight(code, { language: lang }).value;
}
return window.hljs.highlightAuto(code).value;
}
});
}
cb();
};
script.onerror = () => {
alert('highlight.js 加载失败,请检查网络');
};
document.body.appendChild(script);
} else {
// 配置 marked 的 highlight 选项
if (window.marked && window.hljs) {
window.marked.setOptions({
highlight: function(code, lang) {
if (window.hljs.getLanguage(lang)) {
return window.hljs.highlight(code, { language: lang }).value;
}
return window.hljs.highlightAuto(code).value;
}
});
}
cb();
}
}
// 监听表格变化,保证按钮持续插入
function observeTable() {
// 监听整个页面的变化,不只是表格
const targetNode = document.body;
const observer = new MutationObserver((mutations) => {
let shouldUpdate = false;
mutations.forEach((mutation) => {
// 检查是否有新增的节点包含表格行
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
// 检查是否是表格相关的变化
if (node.classList?.contains('el-table__body') ||
node.querySelector?.('.el-table__body') ||
node.querySelector?.('td.el-table_1_column_6') ||
node.tagName === 'TR' ||
node.classList?.contains('el-table__row') ||
node.querySelector?.('.el-table__row')) {
shouldUpdate = true;
}
}
});
// 检查移除的节点
mutation.removedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
if (node.tagName === 'TR' ||
node.classList?.contains('el-table__row')) {
shouldUpdate = true;
}
}
});
}
// 检查属性变化(可能的翻页触发)
if (mutation.type === 'attributes' &&
(mutation.attributeName === 'class' ||
mutation.attributeName === 'style' ||
mutation.attributeName === 'data-key')) {
const target = mutation.target;
if (target.classList?.contains('el-table') ||
target.closest?.('.el-table') ||
target.classList?.contains('el-pagination') ||
target.closest?.('.el-pagination')) {
shouldUpdate = true;
}
}
});
if (shouldUpdate) {
// 延迟执行,确保DOM完全更新
setTimeout(() => {
insertCustomNoteButtons();
}, 100);
// 再次更新确保状态正确
setTimeout(() => {
insertCustomNoteButtons();
}, 300);
}
});
observer.observe(targetNode, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['class', 'style']
});
// 额外监听分页按钮点击
observePaginationClicks();
// 定期检查(后备方案)
setInterval(() => {
insertCustomNoteButtons();
}, 2000); // 每2秒检查一次,增加频率
}
// 监听分页按钮点击
function observePaginationClicks() {
// 监听分页相关的点击事件
document.addEventListener('click', (e) => {
const target = e.target;
// 检查是否点击了分页相关按钮
if (target.closest('.el-pagination') ||
target.closest('.el-pager') ||
target.classList.contains('btn-prev') ||
target.classList.contains('btn-next') ||
target.classList.contains('number') ||
target.closest('.el-pagination__jump') ||
target.closest('.el-pagination__sizes')) {
// 立即尝试更新一次
setTimeout(() => {
insertCustomNoteButtons();
}, 500);
// 再次延迟更新(确保加载完成)
setTimeout(() => {
insertCustomNoteButtons();
}, 1000);
// 最后一次更新(确保状态正确)
setTimeout(() => {
insertCustomNoteButtons();
}, 1500);
}
});
// 监听键盘事件(可能的分页快捷键)
document.addEventListener('keydown', (e) => {
if (e.key === 'PageUp' || e.key === 'PageDown' ||
(e.key === 'Enter' && e.target.closest('.el-pagination'))) {
setTimeout(() => {
insertCustomNoteButtons();
}, 800);
}
});
// 监听URL变化(可能的路由变化)
let currentUrl = window.location.href;
setInterval(() => {
if (window.location.href !== currentUrl) {
currentUrl = window.location.href;
setTimeout(() => {
insertCustomNoteButtons();
}, 1000);
}
}, 1000);
}
// 初始化
function init() {
insertCustomNoteButtons();
observeTable();
}
// 页面插入导出/导入按钮区(只保留主按钮)
function insertExportButton() {
if (document.querySelector('.ctn-export-notes-btn-group')) return;
const group = document.createElement('div');
group.className = 'ctn-export-notes-btn-group';
group.style = `
position:fixed;
right:36px;
bottom:36px;
z-index:10000;
display:flex;
flex-direction:column;
gap:18px;
align-items:flex-end;
`;
// 导出按钮
const exportBtn = document.createElement('button');
exportBtn.className = 'ctn-export-notes-btn';
exportBtn.textContent = '导出笔记';
exportBtn.style = btnStyle();
exportBtn.onclick = exportAllNotes;
// codetop导入按钮
const importCodetopBtn = document.createElement('button');
importCodetopBtn.className = 'ctn-import-codetop-btn';
importCodetopBtn.textContent = 'codetop官方笔记 导入';
importCodetopBtn.style = btnStyle('#67c23a');
importCodetopBtn.onclick = showImportCodetopModal;
// 插件导入按钮
const importPluginBtn = document.createElement('button');
importPluginBtn.className = 'ctn-import-plugin-btn';
importPluginBtn.textContent = '插件笔记 导入';
importPluginBtn.style = btnStyle('#e6a23c');
importPluginBtn.onclick = showImportPluginModal;
// 组装
group.appendChild(exportBtn);
group.appendChild(importCodetopBtn);
group.appendChild(importPluginBtn);
document.body.appendChild(group);
}
function btnStyle(bg) {
return `
background:${bg || '#409EFF'};
color:#fff;
border:none;
border-radius:24px;
padding:12px 28px;
font-size:18px;
box-shadow:0 2px 8px rgba(0,0,0,0.12);
cursor:pointer;
`;
}
// codetop导入弹窗
function showImportCodetopModal() {
showImportModal('codetop');
}
// 插件导入弹窗
function showImportPluginModal() {
showImportModal('plugin');
}
// 通用导入弹窗
function showImportModal(type) {
if (document.querySelector('.ctn-modal-mask')) {
return;
}
// 遮罩
const mask = document.createElement('div');
mask.className = 'ctn-modal-mask';
mask.style = 'position:fixed;left:0;top:0;width:100vw;height:100vh;background:rgba(0,0,0,0.3);z-index:99999;display:flex;align-items:center;justify-content:center;';
// 弹窗
const modal = document.createElement('div');
modal.className = 'ctn-modal';
modal.style = 'background:#fff;padding:32px 32px 24px 32px;border-radius:12px;min-width:420px;max-width:90vw;box-shadow:0 2px 16px rgba(0,0,0,0.15);position:relative;';
// 关闭按钮
const closeBtn = document.createElement('span');
closeBtn.innerHTML = '×';
closeBtn.style = 'position:absolute;right:18px;top:12px;font-size:28px;cursor:pointer;z-index:2;';
closeBtn.title = '关闭';
closeBtn.onclick = () => mask.remove();
// 标题
const title = document.createElement('div');
title.style = 'font-size:20px;font-weight:bold;margin-bottom:18px;';
title.textContent = type === 'codetop' ? '从 codetop 导入笔记' : '从插件导入笔记';
// 内容区
const content = document.createElement('div');
content.style = 'margin-bottom:18px;';
if (type === 'codetop') {
content.innerHTML = '<textarea style="width:100%;height:120px;font-size:16px;padding:8px;box-sizing:border-box;resize:vertical;" placeholder="粘贴 codetop API 返回的 JSON 或 JSON 数组..."></textarea>';
} else {
content.innerHTML = '<input type="file" accept="application/json" style="font-size:16px;">';
}
// 导入按钮
const importBtn = document.createElement('button');
importBtn.textContent = '导入';
importBtn.style = 'margin-top:8px;padding:8px 32px;background:#409EFF;color:#fff;border:none;border-radius:6px;font-size:16px;cursor:pointer;';
// 提示
const tip = document.createElement('div');
tip.style = 'margin-top:12px;color:#67c23a;font-size:15px;display:none;';
tip.textContent = '导入成功!';
// 组装
modal.appendChild(closeBtn);
modal.appendChild(title);
modal.appendChild(content);
modal.appendChild(importBtn);
modal.appendChild(tip);
mask.appendChild(modal);
document.body.appendChild(mask);
// 导入逻辑
importBtn.onclick = () => {
importBtn.disabled = true;
importBtn.textContent = '导入中...';
if (type === 'codetop') {
const val = content.querySelector('textarea').value;
if (!val.trim()) {
alert('请输入要导入的JSON数据');
importBtn.disabled = false;
importBtn.textContent = '导入';
return;
}
let arr;
try {
arr = JSON.parse(val);
} catch (e) {
console.error('JSON解析失败:', e);
alert('JSON 格式错误: ' + e.message);
importBtn.disabled = false;
importBtn.textContent = '导入';
return;
}
if (!Array.isArray(arr)) arr = [arr];
batchImportNotes(arr, type, tip, () => {
importBtn.disabled = false;
importBtn.textContent = '导入';
});
} else {
const file = content.querySelector('input[type=file]').files[0];
if (!file) {
alert('请选择文件');
importBtn.disabled = false;
importBtn.textContent = '导入';
return;
}
const reader = new FileReader();
reader.onload = function(e) {
let arr;
try {
arr = JSON.parse(e.target.result);
} catch (err) {
console.error('文件JSON解析失败:', err);
alert('JSON 格式错误: ' + err.message);
importBtn.disabled = false;
importBtn.textContent = '导入';
return;
}
if (!Array.isArray(arr)) arr = [arr];
batchImportNotes(arr, type, tip, () => {
importBtn.disabled = false;
importBtn.textContent = '导入';
});
};
reader.onerror = function(e) {
console.error('文件读取失败:', e);
alert('文件读取失败');
importBtn.disabled = false;
importBtn.textContent = '导入';
};
reader.readAsText(file);
}
};
}
// 批量导入
function batchImportNotes(arr, type, tip, callback) {
// 先检查 IndexedDB 是否可用
openDB().then(() => {
return openDB();
}).then(db => {
const tx = db.transaction(STORE_NAME, 'readwrite');
const store = tx.objectStore(STORE_NAME);
let count = 0;
let importedKeys = [];
let processedCount = 0;
let errors = [];
if (arr.length === 0) {
tip.textContent = '没有找到可导入的数据';
tip.style.display = '';
setTimeout(() => { tip.style.display = 'none'; }, 3000);
if (callback) callback();
return;
}
arr.forEach((item, index) => {
let key, content;
if (type === 'codetop') {
const slug = item.leetcodeInfo && item.leetcodeInfo.slug_title;
if (!slug) {
processedCount++;
checkComplete();
return;
}
key = `https://leetcode.cn/problems/${slug}`;
content = item.content || '';
} else {
key = item.key || '';
content = item.content || '';
}
if (key) { // 只要有key就尝试导入,即使content为空
const putRequest = store.put({
key,
content: content || '', // 确保content不为undefined
updated_at: Date.now(),
...(item.leetcodeInfo ? { leetcodeInfo: item.leetcodeInfo } : {})
});
putRequest.onsuccess = () => {
importedKeys.push(key);
count++;
processedCount++;
checkComplete();
};
putRequest.onerror = (e) => {
console.error('导入单条记录失败:', key, e);
errors.push(`${key}: ${e.message || e}`);
processedCount++;
checkComplete();
};
} else {
processedCount++;
checkComplete();
}
});
function checkComplete() {
if (processedCount === arr.length) {
let message = `导入完成!成功导入 ${count} 条,跳过 ${arr.length - count} 条。`;
if (errors.length > 0) {
message += `\n错误 ${errors.length} 条`;
}
tip.textContent = message;
tip.style.color = count > 0 ? '#67c23a' : '#f56c6c';
tip.style.display = '';
tip.style.whiteSpace = 'pre-line'; // 支持换行显示
setTimeout(() => { tip.style.display = 'none'; }, 4000);
// 导入完成后刷新按钮状态
setTimeout(() => {
insertCustomNoteButtons();
}, 100);
if (callback) callback();
}
}
tx.onerror = (e) => {
console.error('事务失败:', e);
tip.textContent = '导入失败,请重试';
tip.style.color = '#f56c6c';
tip.style.display = '';
setTimeout(() => { tip.style.display = 'none'; }, 3000);
if (callback) callback();
};
}).catch(err => {
console.error('打开数据库失败:', err);
tip.textContent = '数据库错误,请重试';
tip.style.color = '#f56c6c';
tip.style.display = '';
setTimeout(() => { tip.style.display = 'none'; }, 3000);
if (callback) callback();
});
}
// 导出所有笔记为 JSON 文件
function exportAllNotes() {
openDB().then(db => {
const tx = db.transaction(STORE_NAME, 'readonly');
const store = tx.objectStore(STORE_NAME);
const req = store.getAll();
req.onsuccess = () => {
const data = req.result || [];
if (data.length === 0) {
alert('没有笔记可以导出');
return;
}
const json = JSON.stringify(data, null, 2);
const blob = new Blob([json], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
const date = new Date().toISOString().slice(0, 10);
a.href = url;
a.download = `codetop_notes_${date}.json`;
document.body.appendChild(a);
a.click();
setTimeout(() => {
document.body.removeChild(a);
URL.revokeObjectURL(url);
}, 100);
};
req.onerror = (e) => {
console.error('导出失败:', e);
alert('导出失败,请重试');
};
}).catch(err => {
console.error('打开数据库失败:', err);
alert('数据库错误,无法导出');
});
}
// 页面加载后执行
window.addEventListener('load', () => {
setTimeout(init, 300); // 延迟,确保表格渲染
setTimeout(insertExportButton, 1200); // 插入导出/导入按钮
});
})();