2024 / 7 / 25
// ==UserScript== // @name Highlight to Anki by xxhk.org // @namespace http://tampermonkey.net/ // @version 20240608 // @description Highlight selected text, save highlighted content to a .txt file on button click, and undo the last highlight action with debounce mechanism. Needed template of Anki : https://www.jianguoyun.com/p/DcgfQDIQ89bQBxiJsNQFIAA // @match https://*.com/* // @grant none // ==/UserScript== (function() { 'use strict'; const HIGHLIGHT_CLASS = 'user-highlight'; const STORAGE_KEY = 'highlightedText'; const HISTORY_KEY = 'highlightedTextHistory'; let highlightTimeout = null; let isHighlightEnabled = true; // Default enabled let lastUndoTime = 0; // Function to extract and format the title function extractTitle() { let title = document.title.replace(/[\\/:*?"<>|]+/g, '-').replace(/\s+/g, '-').replace(/-+/g, '-'); return title; // Return the full formatted title } const Htitle = extractTitle(); // Function to highlight selected text function highlightSelection() { if (!isHighlightEnabled) return; const selection = window.getSelection(); if (!selection.rangeCount || selection.isCollapsed) return; const range = selection.getRangeAt(0); const fragment = range.cloneContents(); const span = document.createElement('span'); span.className = HIGHLIGHT_CLASS; span.setAttribute('style', 'background-color: yellow; color: inherit;'); while (fragment.firstChild) { span.appendChild(fragment.firstChild); } range.deleteContents(); range.insertNode(span); saveHighlights(true); // Save highlights and history selection.removeAllRanges(); // Clear the selection after highlighting } // Function to save highlighted text to localStorage function saveHighlights(saveHistory = false) { const highlights = []; document.querySelectorAll(`.${HIGHLIGHT_CLASS}`).forEach(span => { highlights.push({ html: span.outerHTML, parentPath: getElementPath(span.parentNode) }); }); localStorage.setItem(STORAGE_KEY, JSON.stringify(highlights)); if (saveHistory) { saveHistoryState(); } } // Function to save the current state of highlighted text to localStorage function saveHistoryState() { const currentHighlights = localStorage.getItem(STORAGE_KEY); const highlightsHistory = JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]'); highlightsHistory.push(currentHighlights); localStorage.setItem(HISTORY_KEY, JSON.stringify(highlightsHistory)); } // Function to undo the last highlight action function undoHighlight() { const currentTime = new Date().getTime(); if (currentTime - lastUndoTime < 1000) { return; // If last undo was less than 1 second ago, ignore this undo request } lastUndoTime = currentTime; const highlights = document.querySelectorAll(`.${HIGHLIGHT_CLASS}`); if (highlights.length === 0) { alert('没有可以撤销的高亮操作。'); return; } const lastHighlight = highlights[highlights.length - 1]; const parent = lastHighlight.parentNode; parent.replaceChild(document.createTextNode(lastHighlight.textContent), lastHighlight); saveHighlights(true); // Update the saved highlights and history } // Function to get the unique path of an element in the DOM function getElementPath(element) { if (element.id) return `#${element.id}`; const path = []; while (element.parentNode) { let siblings = Array.from(element.parentNode.children).filter(e => e.tagName === element.tagName); path.unshift(`${element.tagName}:nth-of-type(${siblings.indexOf(element) + 1})`); element = element.parentNode; } return path.join(' > '); } // Function to restore highlighted text from localStorage function restoreHighlights() { const highlights = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]'); highlights.forEach(item => { const parent = document.querySelector(item.parentPath); if (parent) { const tempDiv = document.createElement('div'); tempDiv.innerHTML = item.html; const span = tempDiv.firstChild; parent.innerHTML = parent.innerHTML.replace(span.innerHTML, span.outerHTML); } }); } // Function to prompt to save highlighted text to a .txt file function saveHighlightsToFile() { const highlights = []; const url = window.location.href; document.querySelectorAll(`.${HIGHLIGHT_CLASS}`).forEach(span => { let text = span.textContent.trim(); if (text && text !== "🔗") { highlights.push(`${text} <a href="${url}">🔗</a>`); // Placeholder link } }); // Merge paragraphs and handle empty lines correctly const mergedHighlights = []; let tempParagraph = []; highlights.forEach(line => { if (line.includes('🔗')) { tempParagraph.push(line); mergedHighlights.push(tempParagraph.join(' ').replace(/\s+/g, ' ')); tempParagraph = []; } else if (line.trim() === '') { if (tempParagraph.length > 0) { mergedHighlights.push(tempParagraph.join(' ').replace(/\s+/g, ' ')); tempParagraph = []; } mergedHighlights.push(''); // Keep the empty line } else { tempParagraph.push(line); } }); if (tempParagraph.length > 0) { mergedHighlights.push(tempParagraph.join(' ').replace(/\s+/g, ' ')); } const date = new Date().toISOString().slice(0, 10); const title = document.title.replace(/[\\/:*?"<>|]+/g, '_').replace(/\s+/g, '-').replace(/-+/g, '-'); const content = `#方案支持:学习骇客(xxhk.org)\n#separator:Tab\n#html:true\n#tags:Highlight::${Htitle}\n\n${mergedHighlights.join('\n\n')}`; const blob = new Blob([content], { type: 'text/plain' }); const blobUrl = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = blobUrl; a.download = `标注笔记:${date}_${title}.txt`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(blobUrl); } // Function to handle close button click function handleCloseButtonClick() { isHighlightEnabled = false; const button = document.getElementById('highlight-button-container'); button.style.display = 'none'; alert('高亮标注功能已禁用。'); } // Function to create and append a save button with close button function createSaveButton() { const buttonContainer = document.createElement('div'); buttonContainer.id = 'highlight-button-container'; buttonContainer.style.position = 'fixed'; buttonContainer.style.top = '50%'; buttonContainer.style.right = '10px'; buttonContainer.style.transform = 'translateY(-50%)'; buttonContainer.style.zIndex = '1000'; buttonContainer.style.display = 'flex'; buttonContainer.style.flexDirection = 'column'; buttonContainer.style.alignItems = 'center'; buttonContainer.style.backgroundColor = 'black'; buttonContainer.style.color = '#fff'; buttonContainer.style.borderRadius = '5px'; buttonContainer.style.overflow = 'hidden'; const downloadButton = document.createElement('button'); downloadButton.textContent = '⬇️'; downloadButton.style.width = '2em'; downloadButton.style.height = '2em'; downloadButton.style.backgroundColor = 'black'; downloadButton.style.color = '#fff'; downloadButton.style.border = 'none'; downloadButton.style.cursor = 'pointer'; const undoButton = document.createElement('button'); undoButton.textContent = '↩️'; undoButton.style.width = '2em'; undoButton.style.height = '2em'; undoButton.style.backgroundColor = 'black'; undoButton.style.color = '#fff'; undoButton.style.border = 'none'; undoButton.style.cursor = 'pointer'; const closeButton = document.createElement('button'); closeButton.textContent = '✖'; closeButton.style.width = '2em'; closeButton.style.height = '2em'; closeButton.style.backgroundColor = 'red'; closeButton.style.color = '#fff'; closeButton.style.border = 'none'; closeButton.style.cursor = 'pointer'; downloadButton.addEventListener('click', saveHighlightsToFile); undoButton.addEventListener('click', undoHighlight); closeButton.addEventListener('click', handleCloseButtonClick); buttonContainer.appendChild(downloadButton); buttonContainer.appendChild(undoButton); buttonContainer.appendChild(closeButton); document.body.appendChild(buttonContainer); } // Restore highlights on page load window.addEventListener('load', () => { restoreHighlights(); createSaveButton(); }); // Function to handle text selection with a delay function handleSelectionChange() { if (highlightTimeout) clearTimeout(highlightTimeout); highlightTimeout = setTimeout(() => { const selection = window.getSelection(); if (selection && selection.rangeCount > 0 && selection.toString().length > 0) { highlightSelection(); } }, 500); // Adjust the delay as needed } // Add event listeners document.addEventListener('selectionchange', handleSelectionChange); document.addEventListener('touchend', handleSelectionChange); document.addEventListener('mouseup', handleSelectionChange); })();