您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Professional notes manager with editable URLs, darkmode, and quick delete functionality
当前为
// ==UserScript== // @name Professional Website Notes Manager // @namespace http://tampermonkey.net/ // @version 0.3 // @description Professional notes manager with editable URLs, darkmode, and quick delete functionality // @author Byakuran // @match https://*/* // @grant GM_setValue // @grant GM_getValue // @grant GM_registerMenuCommand // ==/UserScript== (function() { 'use strict'; // Check system dark mode preference const prefersDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; const isDarkMode = GM_getValue('darkMode', prefersDarkMode); const darkModeStyles = { modal: { bg: '#1f2937', text: '#f3f4f6' }, input: { bg: '#374151', border: '#4b5563', text: '#f3f4f6' }, button: { primary: '#3b82f6', primaryHover: '#2563eb', secondary: '#4b5563', secondaryHover: '#374151', text: '#ffffff' }, listItem: { bg: '#374151', bgHover: '#4b5563', text: '#f3f4f6' } }; const lightModeStyles = { modal: { bg: '#ffffff', text: '#111827' }, input: { bg: '#f9fafb', border: '#e5e7eb', text: '#111827' }, button: { primary: '#3b82f6', primaryHover: '#2563eb', secondary: '#f3f4f6', secondaryHover: '#e5e7eb', text: '#ffffff' }, listItem: { bg: '#ffffff', bgHover: '#f9fafb', text: '#1f2937' } }; const currentTheme = isDarkMode ? darkModeStyles : lightModeStyles; const styles = ` .notes-modal { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: ${currentTheme.modal.bg}; color: ${currentTheme.modal.text}; padding: 32px; border-radius: 16px; box-shadow: 0 8px 32px rgba(0,0,0,0.25); z-index: 10000; max-width: 700px; width: 90%; font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } .notes-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,${isDarkMode ? '0.8' : '0.7'}); z-index: 9999; backdrop-filter: blur(4px); } .notes-input { width: 100%; margin: 12px 0; padding: 12px 16px; border: 2px solid ${currentTheme.input.border}; border-radius: 8px; font-size: 15px; transition: all 0.2s ease; background: ${currentTheme.input.bg}; color: ${currentTheme.input.text}; } .notes-input:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .notes-textarea { width: 100%; height: 200px; margin: 12px 0; padding: 16px; border: 2px solid ${currentTheme.input.border}; border-radius: 8px; font-size: 15px; resize: vertical; transition: all 0.2s ease; background: ${currentTheme.input.bg}; color: ${currentTheme.input.text}; line-height: 1.5; } .notes-textarea:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } .notes-button { background: ${currentTheme.button.primary}; color: ${currentTheme.button.text}; border: none; padding: 12px 24px; border-radius: 8px; cursor: pointer; margin: 5px; font-size: 15px; font-weight: 500; transition: all 0.2s ease; } .notes-button:hover { background: ${currentTheme.button.primaryHover}; transform: translateY(-1px); } .notes-button.secondary { background: ${currentTheme.button.secondary}; color: ${isDarkMode ? '#f3f4f6' : '#4b5563'}; } .notes-button.secondary:hover { background: ${currentTheme.button.secondaryHover}; } .notes-button.delete { background: #ef4444; } .notes-button.delete:hover { background: #dc2626; } .notes-button.edit { background: #10b981; } .notes-button.edit:hover { background: #059669; } .notes-list-item { display: flex; justify-content: space-between; align-items: center; padding: 16px; border: 1px solid ${currentTheme.input.border}; border-radius: 8px; margin: 8px 0; cursor: pointer; transition: all 0.2s ease; background: ${currentTheme.listItem.bg}; color: ${currentTheme.listItem.text}; } .notes-list-item:hover { background: ${currentTheme.listItem.bgHover}; transform: translateY(-1px); box-shadow: 0 4px 12px rgba(0,0,0,${isDarkMode ? '0.3' : '0.05'}); } .close-button { position: absolute; top: 16px; right: 16px; cursor: pointer; font-size: 24px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'}; transition: all 0.2s; width: 32px; height: 32px; display: flex; align-items: center; justify-content: center; border-radius: 6px; } .close-button:hover { color: ${isDarkMode ? '#f3f4f6' : '#111827'}; background: ${isDarkMode ? '#374151' : '#f3f4f6'}; } .modal-title { font-size: 20px; font-weight: 600; margin-bottom: 24px; color: ${currentTheme.modal.text}; } .url-text { font-size: 14px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'}; word-break: break-all; margin-bottom: 16px; padding: 8px 12px; background: ${isDarkMode ? '#374151' : '#f3f4f6'}; border-radius: 6px; } .timestamp { font-size: 12px; color: ${isDarkMode ? '#9ca3af' : '#6b7280'}; margin-top: 4px; } .delete-note-button { background: none; border: none; color: #ef4444; font-size: 18px; cursor: pointer; padding: 4px 8px; border-radius: 4px; transition: all 0.2s ease; } .delete-note-button:hover { background: #ef4444; color: #ffffff; } `; // Add styles to document const styleSheet = document.createElement("style"); styleSheet.innerText = styles; document.head.appendChild(styleSheet); // Add dark mode toggle command GM_registerMenuCommand('Toggle Dark Mode', () => { const newMode = !isDarkMode; GM_setValue('darkMode', newMode); location.reload(); }); // Helper functions function createModal(content) { const overlay = document.createElement('div'); overlay.className = 'notes-overlay'; const modal = document.createElement('div'); modal.className = 'notes-modal'; const closeButton = document.createElement('span'); closeButton.className = 'close-button'; closeButton.textContent = '×'; closeButton.onclick = () => overlay.remove(); modal.appendChild(closeButton); modal.appendChild(content); overlay.appendChild(modal); document.body.appendChild(overlay); // Add escape key listener const escapeListener = (e) => { if (e.key === 'Escape') { overlay.remove(); document.removeEventListener('keydown', escapeListener); } }; document.addEventListener('keydown', escapeListener); } function getAllNotes() { return GM_getValue('website-notes', {}); } function saveNote(title, url, content, timestamp = Date.now()) { const notes = getAllNotes(); if (!notes[url]) notes[url] = []; notes[url].push({ title, content, timestamp }); GM_setValue('website-notes', notes); } function updateNote(oldUrl, index, title, newUrl, content) { const notes = getAllNotes(); deleteNote(oldUrl, index); saveNote(title, newUrl, content); } function deleteNote(url, index) { const notes = getAllNotes(); if (notes[url]) { notes[url].splice(index, 1); if (notes[url].length === 0) delete notes[url]; GM_setValue('website-notes', notes); } } function showNoteForm(editMode = false, existingNote = null, url = null, index = null) { const container = document.createElement('div'); container.innerHTML = `<h3 class="modal-title">${editMode ? 'Edit Note' : 'Create New Note'}</h3>`; const titleInput = document.createElement('input'); titleInput.className = 'notes-input'; titleInput.placeholder = 'Enter title'; titleInput.value = editMode ? existingNote.title : ''; const urlInput = document.createElement('input'); urlInput.className = 'notes-input'; urlInput.placeholder = 'Enter URL or URL pattern (e.g., https://domain.com/*)'; urlInput.value = editMode ? url : window.location.href; const patternHelp = document.createElement('div'); patternHelp.style.fontSize = '12px'; patternHelp.style.color = isDarkMode ? '#9ca3af' : '#6b7280'; patternHelp.style.marginTop = '-8px'; patternHelp.style.marginBottom = '8px'; patternHelp.innerHTML = 'Use * for wildcard matching (e.g., https://domain.com/*)'; const contentArea = document.createElement('textarea'); contentArea.className = 'notes-textarea'; contentArea.placeholder = 'Enter your notes here'; contentArea.value = editMode ? existingNote.content : ''; const buttonGroup = document.createElement('div'); buttonGroup.className = 'button-group'; const saveButton = document.createElement('button'); saveButton.className = 'notes-button'; saveButton.textContent = editMode ? 'Update Note' : 'Save Note'; saveButton.onclick = () => { if (titleInput.value && contentArea.value) { if (editMode) { updateNote(url, index, titleInput.value, urlInput.value, contentArea.value); } else { saveNote(titleInput.value, urlInput.value, contentArea.value); } container.parentElement.parentElement.remove(); showCurrentPageNotes(); } }; const cancelButton = document.createElement('button'); cancelButton.className = 'notes-button secondary'; cancelButton.textContent = 'Cancel'; cancelButton.onclick = () => container.parentElement.parentElement.remove(); buttonGroup.appendChild(saveButton); buttonGroup.appendChild(cancelButton); container.appendChild(titleInput); container.appendChild(urlInput); container.appendChild(patternHelp); container.appendChild(contentArea); container.appendChild(buttonGroup); createModal(container); } function formatDate(timestamp) { return new Date(timestamp).toLocaleString(); } function showNoteContent(note, url, index) { const container = document.createElement('div'); container.innerHTML = ` <h3 class="modal-title">${note.title}</h3> <div class="url-text">${url}</div> <div class="timestamp">Created: ${formatDate(note.timestamp)}</div> <p style="white-space: pre-wrap; margin: 16px 0; line-height: 1.6;">${note.content}</p> `; const buttonGroup = document.createElement('div'); buttonGroup.className = 'button-group'; const editButton = document.createElement('button'); editButton.className = 'notes-button edit'; editButton.textContent = 'Edit'; editButton.onclick = () => { container.parentElement.parentElement.remove(); showNoteForm(true, note, url, index); }; const deleteButton = document.createElement('button'); deleteButton.className = 'notes-button delete'; deleteButton.textContent = 'Delete'; deleteButton.onclick = () => { if (confirm('Are you sure you want to delete this note?')) { deleteNote(url, index); container.parentElement.parentElement.remove(); showCurrentPageNotes(); } }; buttonGroup.appendChild(editButton); buttonGroup.appendChild(deleteButton); container.appendChild(buttonGroup); createModal(container); } function doesUrlMatchPattern(urlPattern, currentUrl) { // Escape special characters for regex const escapeRegex = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // Convert Tampermonkey-style pattern to regex const patternToRegex = (pattern) => { const parts = pattern.split('*'); let regexString = '^'; for (let i = 0; i < parts.length; i++) { regexString += escapeRegex(parts[i]); if (i < parts.length - 1) { if (parts[i + 1] === '') { // '**' matches any number of path segments regexString += '.*'; i++; // Skip the next '*' } else { // Single '*' matches anything except '/' regexString += '[^/]*'; } } } // If the pattern ends with '**', allow anything at the end if (pattern.endsWith('**')) { regexString += '.*'; } else if (!pattern.endsWith('*')) { regexString += '$'; } return new RegExp(regexString); }; try { const regex = patternToRegex(urlPattern); return regex.test(currentUrl); } catch (e) { console.error('Invalid URL pattern:', e); return false; } } // Modified showCurrentPageNotes function function showCurrentPageNotes() { const notes = getAllNotes(); const currentUrl = window.location.href; let matchingNotes = []; // Collect all matching notes for (const urlPattern in notes) { if (doesUrlMatchPattern(urlPattern, currentUrl)) { matchingNotes.push({ pattern: urlPattern, notes: notes[urlPattern] }); } } const container = document.createElement('div'); container.innerHTML = ` <h3 class="modal-title">Notes for Current Page</h3> <div class="url-text">${currentUrl}</div> `; if (matchingNotes.length === 0) { container.innerHTML += '<p style="color: #6b7280;">No matching notes found for this page</p>'; } else { matchingNotes.forEach(({pattern, notes: patternNotes}) => { const patternDiv = document.createElement('div'); patternDiv.innerHTML = `<div class="url-text">Pattern: ${pattern}</div>`; patternNotes.forEach((note, index) => { const noteDiv = document.createElement('div'); noteDiv.className = 'notes-list-item'; noteDiv.innerHTML = ` <span>${note.title}</span> <button class="delete-note-button" title="Delete note">×</button> `; noteDiv.querySelector('span').onclick = () => { container.parentElement.parentElement.remove(); showNoteContent(note, pattern, index); }; noteDiv.querySelector('.delete-note-button').onclick = (e) => { e.stopPropagation(); if (confirm('Are you sure you want to delete this note?')) { deleteNote(pattern, index); noteDiv.remove(); if (patternNotes.length === 1) { patternDiv.remove(); } } }; patternDiv.appendChild(noteDiv); }); container.appendChild(patternDiv); }); } // Update helper text for URL patterns const helperText = document.createElement('div'); helperText.style.marginTop = '20px'; helperText.style.padding = '12px'; helperText.style.backgroundColor = isDarkMode ? '#374151' : '#f3f4f6'; helperText.style.borderRadius = '8px'; helperText.style.fontSize = '14px'; helperText.style.color = isDarkMode ? '#9ca3af' : '#6b7280'; helperText.innerHTML = ` <strong>URL Pattern Examples:</strong><br> - https://domain.com/* (matches entire domain, one level deep)<br> - https://domain.com/** (matches entire domain, any number of levels)<br> - https://domain.com/specific/* (matches specific path and one level below)<br> - https://domain.com/specific/** (matches specific path and any levels below)<br> - https://domain.com/*/specific (matches specific ending, one level in between)<br> - https://domain.com/**/specific (matches specific ending, any number of levels in between) `; container.appendChild(helperText); createModal(container); } // Modified showAllNotes function function showAllNotes() { const notes = getAllNotes(); const container = document.createElement('div'); container.innerHTML = '<h3 class="modal-title">All Notes</h3>'; if (Object.keys(notes).length === 0) { container.innerHTML += '<p style="color: #6b7280;">No notes found</p>'; } else { for (const url in notes) { const urlDiv = document.createElement('div'); urlDiv.innerHTML = `<div class="url-text">${url}</div>`; notes[url].forEach((note, index) => { const noteDiv = document.createElement('div'); noteDiv.className = 'notes-list-item'; noteDiv.innerHTML = ` <span>${note.title}</span> <button class="delete-note-button" title="Delete note">×</button> `; noteDiv.querySelector('span').onclick = () => { container.parentElement.parentElement.remove(); showNoteContent(note, url, index); }; noteDiv.querySelector('.delete-note-button').onclick = (e) => { e.stopPropagation(); if (confirm('Are you sure you want to delete this note?')) { deleteNote(url, index); noteDiv.remove(); if (notes[url].length === 1) { urlDiv.remove(); } } }; urlDiv.appendChild(noteDiv); }); container.appendChild(urlDiv); } } createModal(container); } // Add keyboard shortcut handler document.addEventListener('keydown', function(e) { if (e.ctrlKey && e.shiftKey && e.key === 'S') { e.preventDefault(); showNoteForm() } if (e.ctrlKey && e.shiftKey && e.key === 'C') { e.preventDefault(); showCurrentPageNotes() } if (e.ctrlKey && e.shiftKey && e.key === 'L') { e.preventDefault(); showAllNotes() } }); // Register menu commands GM_registerMenuCommand('New Note', () => showNoteForm()); GM_registerMenuCommand('View Notes (Current Page)', showCurrentPageNotes); GM_registerMenuCommand('View All Notes', showAllNotes); })();