// ==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);
})();