您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 Flomo 笔记页面增加一个可拖动、可最小化/关闭的快捷面板,支持一键展开、复制内容(带分隔符)、下载为TXT(优化格式+中文编码),并显示笔记数量。V2.4: 调整复制和下载的笔记顺序为最新在上。
// ==UserScript== // @name Flomo 快捷操作面板 (增强版) // @namespace http://tampermonkey.net/ // @version 2.4 // @description 在 Flomo 笔记页面增加一个可拖动、可最小化/关闭的快捷面板,支持一键展开、复制内容(带分隔符)、下载为TXT(优化格式+中文编码),并显示笔记数量。V2.4: 调整复制和下载的笔记顺序为最新在上。 // @author Gemini & AI Assistant // @match https://v.flomoapp.com/mine* // @grant GM_addStyle // @grant GM_setClipboard // @license MIT // ==/UserScript== (function() { 'use strict'; // 添加全局错误处理 window.addEventListener('error', function(e) { if (e.message && e.message.includes('Failed to fetch')) { console.warn('捕获到网络错误,可能是flomo网站的正常行为:', e.message); } }); window.addEventListener('unhandledrejection', function(e) { if (e.reason && e.reason.message && e.reason.message.includes('Failed to fetch')) { console.warn('捕获到Promise拒绝错误,可能是flomo网站的正常行为:', e.reason.message); e.preventDefault(); // 阻止错误显示在控制台 } }); // --- 样式定义 --- GM_addStyle(` #flomo-helper-panel { position: fixed; top: 80px; right: 20px; z-index: 9999; background-color: #ffffff; border: 1px solid #e0e0e0; border-radius: 8px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); transition: all 0.3s ease; min-width: 160px; } #flomo-helper-header { padding: 8px 10px; cursor: move; background-color: #f7f7f7; border-bottom: 1px solid #e0e0e0; border-top-left-radius: 8px; border-top-right-radius: 8px; display: flex; justify-content: space-between; align-items: center; user-select: none; } #flomo-helper-header span { font-weight: bold; color: #333; } #flomo-helper-controls button { border: none; background: transparent; cursor: pointer; font-size: 16px; font-weight: bold; padding: 0 4px; color: #888; } #flomo-helper-controls button:hover { color: #000; } #flomo-helper-content { padding: 10px; display: flex; flex-direction: column; gap: 8px; transition: all 0.3s ease; } #flomo-helper-panel.minimized #flomo-helper-content { display: none; } .flomo-helper-button { background-color: #4e4e4e; color: white; border: none; padding: 8px 12px; border-radius: 6px; cursor: pointer; font-size: 14px; text-align: center; transition: background-color 0.2s ease, transform 0.2s ease; white-space: nowrap; } .flomo-helper-button:hover { background-color: #333333; transform: translateY(-1px); } .flomo-helper-button:active { transform: translateY(0); } .flomo-helper-button.success { background-color: #28a745; } #flomo-note-counter { text-align: center; font-size: 12px; color: #888; margin-top: 5px; padding-top: 5px; border-top: 1px solid #f0f0f0; } `); // --- 创建并添加操作面板 --- setTimeout(() => { const panel = document.createElement('div'); panel.id = 'flomo-helper-panel'; const header = document.createElement('div'); header.id = 'flomo-helper-header'; header.innerHTML = ` <span>Flomo 助手</span> <div id="flomo-helper-controls"> <button id="minimize-btn" title="最小化">-</button> <button id="close-btn" title="关闭">×</button> </div> `; const content = document.createElement('div'); content.id = 'flomo-helper-content'; const expandButton = createButton('展开所有笔记', expandAllNotes); const copyButton = createButton('复制所有内容', copyAllContent); const downloadButton = createButton('下载为 .txt', downloadAsTxt); const counter = document.createElement('div'); counter.id = 'flomo-note-counter'; counter.textContent = '笔记数量: 0'; content.appendChild(expandButton); content.appendChild(copyButton); content.appendChild(downloadButton); content.appendChild(counter); panel.appendChild(header); panel.appendChild(content); document.body.appendChild(panel); header.addEventListener('mousedown', dragMouseDown); document.getElementById('minimize-btn').addEventListener('click', () => panel.classList.toggle('minimized')); document.getElementById('close-btn').addEventListener('click', () => panel.style.display = 'none'); updateNoteCount(); const observer = new MutationObserver(updateNoteCount); const targetNode = document.querySelector('.list'); if (targetNode) { observer.observe(targetNode, { childList: true, subtree: true }); } }, 2000); // --- 功能函数定义 --- function createButton(text, onClick) { const button = document.createElement('button'); button.textContent = text; button.className = 'flomo-helper-button'; button.addEventListener('click', onClick); return button; } function updateNoteCount() { const count = document.querySelectorAll('.display').length; const counterElement = document.getElementById('flomo-note-counter'); if (counterElement) { counterElement.textContent = `笔记数量: ${count}`; } } function expandAllNotes() { const expandButtons = document.querySelectorAll('.showBtn'); if (expandButtons.length === 0) { showFeedback(this, '无内容可展开', false); return; } expandButtons.forEach(btn => btn.click()); showFeedback(this, '已全部展开!', true); } /** * [MODIFIED] 获取所有笔记内容,包含日期和正文 * 优化: 改进文本格式,添加序号和统计信息 * 修复: 移除了内容过长截断的限制,并统一了笔记间的分隔符 * 更新: 调整笔记顺序为最新的在最前 * @returns {string|null} - 格式化后的所有笔记文本,或在没有内容时返回 null */ function getAllContentText() { const memoElements = document.querySelectorAll('.display'); if (memoElements.length === 0) { return null; } // 获取当前页面的标签信息 const urlParams = new URLSearchParams(window.location.search); const tag = urlParams.get('tag'); const currentDate = new Date().toLocaleString('zh-CN'); // 添加文件头部信息 let headerInfo = `Flomo 笔记导出\n`; headerInfo += `导出时间: ${currentDate}\n`; if (tag) { headerInfo += `标签筛选: #${tag}\n`; } headerInfo += `笔记总数: ${memoElements.length} 条\n`; headerInfo += `${'='.repeat(50)}\n\n`; // [MODIFIED] 直接 map,不反转,保持最新的在前 const allText = Array.from(memoElements).map((memo, index) => { const timeEl = memo.querySelector('.time .text'); const contentEl = memo.querySelector('.richText'); // 获取时间戳 const timeStamp = timeEl ? timeEl.innerText.trim() : '时间未知'; // 获取内容,保持原有格式 let content = '内容为空'; if (contentEl) { content = contentEl.innerText.trim(); } // [MODIFIED] 格式化单条笔记,序号从1开始 const noteNumber = index + 1; return `【笔记 ${noteNumber}】\n时间: ${timeStamp}\n内容:\n${content}`; }); // [MODIFIED] .reverse() 已移除 // 添加尾部统计信息 const footerInfo = `\n${'='.repeat(50)}\n导出完成 - 共 ${memoElements.length} 条笔记`; // 笔记之间使用分隔线,复制和下载功能共用此格式 return headerInfo + allText.join('\n\n' + '-'.repeat(40) + '\n\n') + footerInfo; } function copyAllContent() { const combinedText = getAllContentText(); if (combinedText === null) { showFeedback(this, '未找到内容', false); return; } GM_setClipboard(combinedText, 'text'); showFeedback(this, '复制成功!', true); } /** * [FIXED] 下载所有笔记为 TXT 文件,并使用智能文件名 * 修复: 添加BOM字符、错误处理、文件名安全性检查 */ function downloadAsTxt() { const combinedText = getAllContentText(); if (combinedText === null) { showFeedback(this, '未找到内容', false); return; } try { // 添加调试信息 console.log('开始下载,内容长度:', combinedText.length); // 从 URL 获取 tag 用于生成更智能的文件名 const urlParams = new URLSearchParams(window.location.search); const tag = urlParams.get('tag'); const date = new Date().toISOString().slice(0, 10); const time = new Date().toTimeString().slice(0, 8).replace(/:/g, '-'); // 清理 tag 中的不安全字符,确保文件名合法 const safeTag = tag ? tag.replace(/[<>:"/\\|?*]/g, '_').substring(0, 50) : ''; const filename = safeTag ? `flomo笔记_${safeTag}_${date}_${time}.txt` : `flomo笔记_${date}_${time}.txt`; // 添加 UTF-8 BOM 字符,确保中文正确显示 const BOM = '\uFEFF'; const textWithBOM = BOM + combinedText; // 创建带有正确编码的 Blob const blob = new Blob([textWithBOM], { type: 'text/plain;charset=utf-8' }); // 使用try-catch包装URL创建过程 let url; try { url = URL.createObjectURL(blob); console.log('创建的Blob URL:', url); } catch (urlError) { console.error('创建Blob URL失败:', urlError); showFeedback(this, '下载失败', false); return; } // 创建一个新的a元素,不使用现有DOM中的元素 const a = document.createElement('a'); a.href = url; a.download = filename; a.style.display = 'none'; // 隐藏链接元素 a.target = '_blank'; // 使用新窗口下载,避免URL冲突 a.rel = 'noopener noreferrer'; // 安全设置 // 使用更直接的下载方式,避免被网站代码拦截 try { // 方法1:直接点击,不添加到DOM a.click(); console.log('使用直接点击方式下载'); } catch (clickError) { console.warn('直接点击失败,尝试其他方式:', clickError); try { // 方法2:使用window.open直接打开blob URL const newWindow = window.open('', '_blank'); if (newWindow) { newWindow.location.href = url; console.log('使用新窗口方式下载'); } else { throw new Error('无法打开新窗口'); } } catch (windowError) { console.warn('新窗口方式失败,尝试最后方式:', windowError); // 方法3:使用location.href直接跳转 const originalHref = window.location.href; window.location.href = url; // 立即恢复原URL,避免页面跳转 setTimeout(() => { window.location.href = originalHref; }, 100); console.log('使用location.href方式下载'); } } // 清理URL对象 setTimeout(() => { try { URL.revokeObjectURL(url); console.log('Blob URL已清理'); } catch (revokeError) { console.warn('URL清理失败:', revokeError); } }, 1000); // 延长清理时间,确保下载完成 showFeedback(this, '下载成功!', true); } catch (error) { console.error('下载失败:', error); showFeedback(this, '下载失败', false); } } function showFeedback(btn, message, isSuccess) { if (!btn || typeof btn.textContent === 'undefined') return; const originalText = btn.textContent; btn.textContent = message; if (isSuccess) { btn.classList.add('success'); } setTimeout(() => { btn.textContent = originalText; if (isSuccess) { btn.classList.remove('success'); } }, 2000); } // --- 拖动功能实现 --- let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0; const panel = () => document.getElementById('flomo-helper-panel'); function dragMouseDown(e) { e = e || window.event; e.preventDefault(); pos3 = e.clientX; pos4 = e.clientY; document.onmouseup = closeDragElement; document.onmousemove = elementDrag; } function elementDrag(e) { e = e || window.event; e.preventDefault(); pos1 = pos3 - e.clientX; pos2 = pos4 - e.clientY; pos3 = e.clientX; pos4 = e.clientY; const elmnt = panel(); elmnt.style.top = (elmnt.offsetTop - pos2) + "px"; elmnt.style.left = (elmnt.offsetLeft - pos1) + "px"; } function closeDragElement() { document.onmouseup = null; document.onmousemove = null; } })();