Professional Website Notes Manager

Professional notes manager with editable URLs, modern interface, and quick delete functionality

目前为 2025-01-25 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Professional Website Notes Manager
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.5
  5. // @description Professional notes manager with editable URLs, modern interface, and quick delete functionality
  6. // @author Byakuran
  7. // @match https://*/*
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @grant GM_registerMenuCommand
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. const defaultOptions = {
  17. darkMode: window.matchMedia('(prefers-color-scheme: dark)').matches,
  18. addTimestampToTitle: false,
  19. shortcuts: {
  20. newNote: { ctrlKey: true, shiftKey: true, key: 'S' },
  21. currentPageNotes: { ctrlKey: true, shiftKey: true, key: 'C' },
  22. allNotes: { ctrlKey: true, shiftKey: true, key: 'L' }
  23. }
  24. };
  25.  
  26. let options = GM_getValue('options', defaultOptions);
  27. GM_setValue('options', options);
  28.  
  29. const isDarkMode = options.darkMode;
  30.  
  31. const darkModeStyles = {
  32. modal: {
  33. bg: '#1f2937',
  34. text: '#f3f4f6'
  35. },
  36. input: {
  37. bg: '#374151',
  38. border: '#4b5563',
  39. text: '#f3f4f6'
  40. },
  41. button: {
  42. primary: '#3b82f6',
  43. primaryHover: '#2563eb',
  44. secondary: '#4b5563',
  45. secondaryHover: '#374151',
  46. text: '#ffffff'
  47. },
  48. listItem: {
  49. bg: '#374151',
  50. bgHover: '#4b5563',
  51. text: '#f3f4f6'
  52. }
  53. };
  54.  
  55. const lightModeStyles = {
  56. modal: {
  57. bg: '#ffffff',
  58. text: '#111827'
  59. },
  60. input: {
  61. bg: '#f9fafb',
  62. border: '#e5e7eb',
  63. text: '#111827'
  64. },
  65. button: {
  66. primary: '#3b82f6',
  67. primaryHover: '#2563eb',
  68. secondary: '#f3f4f6',
  69. secondaryHover: '#e5e7eb',
  70. text: '#ffffff'
  71. },
  72. listItem: {
  73. bg: '#ffffff',
  74. bgHover: '#f9fafb',
  75. text: '#1f2937'
  76. }
  77. };
  78.  
  79. const currentTheme = isDarkMode ? darkModeStyles : lightModeStyles;
  80.  
  81. const styles = `
  82. .notes-modal {
  83. position: fixed;
  84. top: 50%;
  85. left: 50%;
  86. transform: translate(-50%, -50%);
  87. background: ${currentTheme.modal.bg};
  88. color: ${currentTheme.modal.text};
  89. padding: 32px;
  90. border-radius: 16px;
  91. box-shadow: 0 8px 32px rgba(0,0,0,0.25);
  92. z-index: 10000;
  93. max-width: 700px;
  94. width: 90%;
  95. font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  96. }
  97. .notes-overlay {
  98. position: fixed;
  99. top: 0;
  100. left: 0;
  101. right: 0;
  102. bottom: 0;
  103. background: rgba(0,0,0,${isDarkMode ? '0.8' : '0.7'});
  104. z-index: 9999;
  105. backdrop-filter: blur(4px);
  106. }
  107. .notes-input {
  108. width: 100%;
  109. margin: 12px 0;
  110. padding: 12px 16px;
  111. border: 2px solid ${currentTheme.input.border};
  112. border-radius: 8px;
  113. font-size: 15px;
  114. transition: all 0.2s ease;
  115. background: ${currentTheme.input.bg};
  116. color: ${currentTheme.input.text};
  117. box-sizing: border-box;
  118. }
  119. .notes-input:focus {
  120. outline: none;
  121. border-color: #3b82f6;
  122. box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
  123. }
  124. .notes-textarea {
  125. width: 100%;
  126. height: 200px;
  127. margin: 12px 0;
  128. padding: 16px;
  129. border: 2px solid ${currentTheme.input.border};
  130. border-radius: 8px;
  131. font-size: 15px;
  132. resize: vertical;
  133. transition: all 0.2s ease;
  134. background: ${currentTheme.input.bg};
  135. color: ${currentTheme.input.text};
  136. line-height: 1.5;
  137. box-sizing: border-box;
  138. }
  139. .notes-textarea:focus {
  140. outline: none;
  141. border-color: #3b82f6;
  142. box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
  143. }
  144. .notes-button {
  145. background: ${currentTheme.button.primary};
  146. color: ${currentTheme.button.text};
  147. border: none;
  148. padding: 12px 24px;
  149. border-radius: 8px;
  150. cursor: pointer;
  151. margin: 5px;
  152. font-size: 15px;
  153. font-weight: 500;
  154. transition: all 0.2s ease;
  155. }
  156. .notes-button:hover {
  157. background: ${currentTheme.button.primaryHover};
  158. transform: translateY(-1px);
  159. }
  160. .notes-button.secondary {
  161. background: ${currentTheme.button.secondary};
  162. color: ${isDarkMode ? '#f3f4f6' : '#4b5563'};
  163. }
  164. .notes-button.secondary:hover {
  165. background: ${currentTheme.button.secondaryHover};
  166. }
  167. .notes-button.delete {
  168. background: #ef4444;
  169. }
  170. .notes-button.delete:hover {
  171. background: #dc2626;
  172. }
  173. .notes-button.edit {
  174. background: #10b981;
  175. }
  176. .notes-button.edit:hover {
  177. background: #059669;
  178. }
  179. .notes-list-item {
  180. display: flex;
  181. justify-content: space-between;
  182. align-items: center;
  183. padding: 16px;
  184. border: 1px solid ${currentTheme.input.border};
  185. border-radius: 8px;
  186. margin: 8px 0;
  187. cursor: pointer;
  188. transition: all 0.2s ease;
  189. background: ${currentTheme.listItem.bg};
  190. color: ${currentTheme.listItem.text};
  191. }
  192. .notes-list-item:hover {
  193. background: ${currentTheme.listItem.bgHover};
  194. transform: translateY(-1px);
  195. box-shadow: 0 4px 12px rgba(0,0,0,${isDarkMode ? '0.3' : '0.05'});
  196. }
  197. .close-button {
  198. position: absolute;
  199. top: 16px;
  200. right: 16px;
  201. cursor: pointer;
  202. font-size: 24px;
  203. color: ${isDarkMode ? '#9ca3af' : '#6b7280'};
  204. transition: all 0.2s;
  205. width: 32px;
  206. height: 32px;
  207. display: flex;
  208. align-items: center;
  209. justify-content: center;
  210. border-radius: 6px;
  211. }
  212. .close-button:hover {
  213. color: ${isDarkMode ? '#f3f4f6' : '#111827'};
  214. background: ${isDarkMode ? '#374151' : '#f3f4f6'};
  215. }
  216. .modal-title {
  217. font-size: 20px;
  218. font-weight: 600;
  219. margin-bottom: 24px;
  220. color: ${currentTheme.modal.text};
  221. }
  222. .url-text {
  223. font-size: 14px;
  224. color: ${isDarkMode ? '#9ca3af' : '#6b7280'};
  225. word-break: break-all;
  226. margin-bottom: 16px;
  227. padding: 8px 12px;
  228. background: ${isDarkMode ? '#374151' : '#f3f4f6'};
  229. border-radius: 6px;
  230. }
  231. .timestamp {
  232. font-size: 12px;
  233. color: ${isDarkMode ? '#9ca3af' : '#6b7280'};
  234. margin-top: 4px;
  235. }
  236. .delete-note-button {
  237. background: none;
  238. border: none;
  239. color: #ef4444;
  240. font-size: 18px;
  241. cursor: pointer;
  242. padding: 4px 8px;
  243. border-radius: 4px;
  244. transition: all 0.2s ease;
  245. }
  246. .delete-note-button:hover {
  247. background: #ef4444;
  248. color: #ffffff;
  249. }
  250. `;
  251.  
  252. const styleSheet = document.createElement("style");
  253. styleSheet.innerText = styles;
  254. document.head.appendChild(styleSheet);
  255.  
  256. function showOptionsMenu() {
  257. const container = document.createElement('div');
  258. container.innerHTML = `
  259. <h3 class="modal-title">Options</h3>
  260. <div>
  261. <label>
  262. <input type="checkbox" id="darkModeToggle" ${options.darkMode ? 'checked' : ''}>
  263. Dark Mode
  264. </label>
  265. </div>
  266. <div style="margin-top: 10px;">
  267. <label>
  268. <input type="checkbox" id="timestampToggle" ${options.addTimestampToTitle ? 'checked' : ''}>
  269. Add timestamp to note titles
  270. </label>
  271. </div>
  272. <h4 style="margin-top: 20px;">Keyboard Shortcuts</h4>
  273. <div>
  274. <label>New Note:
  275. <input type="text" id="newNoteShortcut" value="${getShortcutString(options.shortcuts.newNote)}">
  276. </label>
  277. </div>
  278. <div>
  279. <label>Current Page Notes:
  280. <input type="text" id="currentPageNotesShortcut" value="${getShortcutString(options.shortcuts.currentPageNotes)}">
  281. </label>
  282. </div>
  283. <div>
  284. <label>All Notes:
  285. <input type="text" id="allNotesShortcut" value="${getShortcutString(options.shortcuts.allNotes)}">
  286. </label>
  287. </div>
  288. <button id="saveOptions" class="notes-button" style="margin-top: 20px;">Save Options</button>
  289. `;
  290.  
  291. createModal(container);
  292. document.getElementById('saveOptions').onclick = saveOptions;
  293. }
  294.  
  295. function getShortcutString(shortcut) {
  296. let str = '';
  297. if (shortcut.ctrlKey) str += 'Ctrl+';
  298. if (shortcut.shiftKey) str += 'Shift+';
  299. if (shortcut.altKey) str += 'Alt+';
  300. str += shortcut.key.toUpperCase();
  301. return str;
  302. }
  303.  
  304. function parseShortcutString(str) {
  305. const parts = str.toLowerCase().split('+');
  306. return {
  307. ctrlKey: parts.includes('ctrl'),
  308. shiftKey: parts.includes('shift'),
  309. altKey: parts.includes('alt'),
  310. key: parts[parts.length - 1]
  311. };
  312. }
  313.  
  314. function saveOptions() {
  315. options.darkMode = document.getElementById('darkModeToggle').checked;
  316. options.addTimestampToTitle = document.getElementById('timestampToggle').checked;
  317. options.shortcuts.newNote = parseShortcutString(document.getElementById('newNoteShortcut').value);
  318. options.shortcuts.currentPageNotes = parseShortcutString(document.getElementById('currentPageNotesShortcut').value);
  319. options.shortcuts.allNotes = parseShortcutString(document.getElementById('allNotesShortcut').value);
  320.  
  321. GM_setValue('options', options);
  322. alert('Options saved. Please refresh the page for changes to take effect.');
  323. }
  324.  
  325.  
  326. GM_registerMenuCommand('Toggle Dark Mode', () => {
  327. const newMode = !isDarkMode;
  328. GM_setValue('darkMode', newMode);
  329. location.reload();
  330. });
  331.  
  332. function createModal(content) {
  333. const overlay = document.createElement('div');
  334. overlay.className = 'notes-overlay';
  335.  
  336. const modal = document.createElement('div');
  337. modal.className = 'notes-modal';
  338.  
  339. const closeButton = document.createElement('span');
  340. closeButton.className = 'close-button';
  341. closeButton.textContent = '×';
  342. closeButton.onclick = () => overlay.remove();
  343.  
  344. modal.appendChild(closeButton);
  345. modal.appendChild(content);
  346. overlay.appendChild(modal);
  347. document.body.appendChild(overlay);
  348.  
  349. const escapeListener = (e) => {
  350. if (e.key === 'Escape') {
  351. overlay.remove();
  352. document.removeEventListener('keydown', escapeListener);
  353. }
  354. };
  355. document.addEventListener('keydown', escapeListener);
  356. }
  357.  
  358. function getAllNotes() {
  359. return GM_getValue('website-notes', {});
  360. }
  361.  
  362. function saveNote(title, url, content, timestamp = Date.now(), pinned = false) {
  363. const notes = getAllNotes();
  364. if (!notes[url]) notes[url] = [];
  365.  
  366. // Add timestamp to title if the option is enabled
  367. let finalTitle = title;
  368. if (options.addTimestampToTitle) {
  369. const date = new Date(timestamp);
  370. const formattedDate = date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
  371. finalTitle = `${title} [${formattedDate}]`;
  372. }
  373.  
  374. notes[url].push({ title: finalTitle, content, timestamp, pinned });
  375. GM_setValue('website-notes', notes);
  376. }
  377.  
  378. function updateNote(oldUrl, index, title, newUrl, content, pinned) {
  379. const notes = getAllNotes();
  380. deleteNote(oldUrl, index);
  381. saveNote(title, newUrl, content, notes[oldUrl][index].timestamp, pinned);
  382. }
  383.  
  384. function togglePinNote(url, index) {
  385. const notes = getAllNotes();
  386. if (notes[url] && notes[url][index]) {
  387. notes[url][index].pinned = !notes[url][index].pinned;
  388. GM_setValue('website-notes', notes);
  389. }
  390. }
  391.  
  392. function deleteNote(url, index) {
  393. const notes = getAllNotes();
  394. if (notes[url]) {
  395. notes[url].splice(index, 1);
  396. if (notes[url].length === 0) delete notes[url];
  397. GM_setValue('website-notes', notes);
  398. }
  399. }
  400.  
  401. function showNoteForm(editMode = false, existingNote = null, url = null, index = null) {
  402. const container = document.createElement('div');
  403. container.innerHTML = `<h3 class="modal-title">${editMode ? 'Edit Note' : 'Create New Note'}</h3>`;
  404.  
  405. const titleInput = document.createElement('input');
  406. titleInput.className = 'notes-input';
  407. titleInput.placeholder = 'Enter title';
  408. titleInput.value = editMode ? existingNote.title : '';
  409.  
  410. const urlInput = document.createElement('input');
  411. urlInput.className = 'notes-input';
  412. urlInput.placeholder = 'Enter URL or URL pattern (e.g., https://domain.com/*)';
  413. urlInput.value = editMode ? url : window.location.href;
  414.  
  415. const patternHelp = document.createElement('div');
  416. patternHelp.style.fontSize = '12px';
  417. patternHelp.style.color = isDarkMode ? '#9ca3af' : '#6b7280';
  418. patternHelp.style.marginTop = '-8px';
  419. patternHelp.style.marginBottom = '8px';
  420. patternHelp.innerHTML = 'Use * for wildcard matching (e.g., https://domain.com/*)';
  421.  
  422. const contentArea = document.createElement('textarea');
  423. contentArea.className = 'notes-textarea';
  424. contentArea.placeholder = 'Enter your notes here';
  425. contentArea.value = editMode ? existingNote.content : '';
  426.  
  427. const buttonGroup = document.createElement('div');
  428. buttonGroup.className = 'button-group';
  429.  
  430. const saveButton = document.createElement('button');
  431. saveButton.className = 'notes-button';
  432. saveButton.textContent = editMode ? 'Update Note' : 'Save Note';
  433. saveButton.onclick = () => {
  434. if (titleInput.value && contentArea.value) {
  435. if (editMode) {
  436. updateNote(url, index, titleInput.value, urlInput.value, contentArea.value);
  437. } else {
  438. saveNote(titleInput.value, urlInput.value, contentArea.value);
  439. }
  440. container.parentElement.parentElement.remove();
  441. showCurrentPageNotes();
  442. }
  443. };
  444.  
  445. const cancelButton = document.createElement('button');
  446. cancelButton.className = 'notes-button secondary';
  447. cancelButton.textContent = 'Cancel';
  448. cancelButton.onclick = () => container.parentElement.parentElement.remove();
  449.  
  450. buttonGroup.appendChild(saveButton);
  451. buttonGroup.appendChild(cancelButton);
  452.  
  453. container.appendChild(titleInput);
  454. container.appendChild(urlInput);
  455. container.appendChild(patternHelp);
  456. container.appendChild(contentArea);
  457. container.appendChild(buttonGroup);
  458.  
  459. createModal(container);
  460. }
  461.  
  462. function formatDate(timestamp) {
  463. return new Date(timestamp).toLocaleString();
  464. }
  465.  
  466. function showNoteContent(note, url, index) {
  467. const container = document.createElement('div');
  468.  
  469. // Function to convert URLs to clickable links
  470. function linkify(text) {
  471. // URL pattern for matching
  472. const urlPattern = /(\b(https?|ftp):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/gim;
  473.  
  474. // Replace URLs with anchor tags
  475. const linkedText = text.replace(urlPattern, function(url) {
  476. return `<a href="${url}" target="_blank" style="color: #3b82f6; text-decoration: underline; word-break: break-all;" onclick="event.stopPropagation();">${url}</a>`;
  477. });
  478.  
  479. // Convert line breaks to <br> tags and maintain whitespace
  480. return linkedText.replace(/\n/g, '<br>').replace(/\s{2,}/g, function(space) {
  481. return ' ' + '&nbsp;'.repeat(space.length - 1);
  482. });
  483. }
  484.  
  485. container.innerHTML = `
  486. <h3 class="modal-title">${note.title}</h3>
  487. <div class="url-text">${url}</div>
  488. <div class="timestamp">Created: ${formatDate(note.timestamp)}</div>
  489. <div style="margin: 16px 0; line-height: 1.6;">${linkify(note.content)}</div>
  490. `;
  491.  
  492. const buttonGroup = document.createElement('div');
  493. buttonGroup.className = 'button-group';
  494.  
  495. const editButton = document.createElement('button');
  496. editButton.className = 'notes-button edit';
  497. editButton.textContent = 'Edit';
  498. editButton.onclick = () => {
  499. container.parentElement.parentElement.remove();
  500. showNoteForm(true, note, url, index);
  501. };
  502.  
  503. const deleteButton = document.createElement('button');
  504. deleteButton.className = 'notes-button delete';
  505. deleteButton.textContent = 'Delete';
  506. deleteButton.onclick = () => {
  507. if (confirm('Are you sure you want to delete this note?')) {
  508. deleteNote(url, index);
  509. container.parentElement.parentElement.remove();
  510. showCurrentPageNotes();
  511. }
  512. };
  513.  
  514. const pinButton = document.createElement('button');
  515. pinButton.className = `notes-button ${note.pinned ? 'secondary' : ''}`;
  516. pinButton.textContent = note.pinned ? 'Unpin' : 'Pin';
  517. pinButton.onclick = () => {
  518. togglePinNote(url, index);
  519. pinButton.textContent = note.pinned ? 'Pin' : 'Unpin';
  520. pinButton.className = `notes-button ${!note.pinned ? 'secondary' : ''}`;
  521. displayPinnedNotes();
  522. };
  523.  
  524. buttonGroup.appendChild(editButton);
  525. buttonGroup.appendChild(deleteButton);
  526. buttonGroup.appendChild(pinButton);
  527. container.appendChild(buttonGroup);
  528.  
  529. createModal(container);
  530. }
  531.  
  532. function displayPinnedNotes() {
  533. const notes = getAllNotes();
  534. const currentUrl = window.location.href;
  535. let pinnedNotesContainer = document.getElementById('pinned-notes-container');
  536.  
  537. if (!pinnedNotesContainer) {
  538. pinnedNotesContainer = document.createElement('div');
  539. pinnedNotesContainer.id = 'pinned-notes-container';
  540. pinnedNotesContainer.style.position = 'fixed';
  541. pinnedNotesContainer.style.top = '10px';
  542. pinnedNotesContainer.style.right = '10px';
  543. pinnedNotesContainer.style.zIndex = '9999';
  544. pinnedNotesContainer.style.maxWidth = '300px';
  545. document.body.appendChild(pinnedNotesContainer);
  546. }
  547.  
  548. pinnedNotesContainer.innerHTML = '';
  549.  
  550. for (const url in notes) {
  551. if (doesUrlMatchPattern(url, currentUrl)) {
  552. notes[url].forEach((note, index) => {
  553. if (note.pinned) {
  554. const noteDiv = document.createElement('div');
  555. noteDiv.className = 'pinned-note';
  556. noteDiv.style.background = currentTheme.listItem.bg;
  557. noteDiv.style.color = currentTheme.listItem.text;
  558. noteDiv.style.padding = '10px';
  559. noteDiv.style.margin = '5px 0';
  560. noteDiv.style.borderRadius = '8px';
  561. noteDiv.style.boxShadow = '0 2px 4px rgba(0,0,0,0.1)';
  562. noteDiv.style.cursor = 'pointer';
  563. noteDiv.innerHTML = `<strong>${note.title}</strong>`;
  564. noteDiv.onclick = () => showNoteContent(note, url, index);
  565. pinnedNotesContainer.appendChild(noteDiv);
  566. }
  567. });
  568. }
  569. }
  570. }
  571.  
  572.  
  573. function doesUrlMatchPattern(urlPattern, currentUrl) {
  574. // Escape special characters for regex
  575. const escapeRegex = (string) => string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  576.  
  577. // Convert Tampermonkey-style pattern to regex
  578. const patternToRegex = (pattern) => {
  579. const parts = pattern.split('*');
  580. let regexString = '^';
  581. for (let i = 0; i < parts.length; i++) {
  582. regexString += escapeRegex(parts[i]);
  583. if (i < parts.length - 1) {
  584. if (parts[i + 1] === '') {
  585. // '**' matches any number of path segments
  586. regexString += '.*';
  587. i++; // Skip the next '*'
  588. } else {
  589. // Single '*' matches anything except '/'
  590. regexString += '[^/]*';
  591. }
  592. }
  593. //Hope everything works.
  594. }
  595. // If the pattern ends with '**', allow anything at the end
  596. if (pattern.endsWith('**')) {
  597. regexString += '.*';
  598. } else if (!pattern.endsWith('*')) {
  599. regexString += '$';
  600. }
  601. return new RegExp(regexString);
  602. };
  603.  
  604. try {
  605. const regex = patternToRegex(urlPattern);
  606. return regex.test(currentUrl);
  607. } catch (e) {
  608. console.error('Invalid URL pattern:', e);
  609. return false;
  610. }
  611. }
  612.  
  613. function showCurrentPageNotes() {
  614. const notes = getAllNotes();
  615. const currentUrl = window.location.href;
  616. let matchingNotes = [];
  617.  
  618. // Collect all matching notes
  619. for (const urlPattern in notes) {
  620. if (doesUrlMatchPattern(urlPattern, currentUrl)) {
  621. matchingNotes.push({
  622. pattern: urlPattern,
  623. notes: notes[urlPattern]
  624. });
  625. }
  626. }
  627.  
  628. const container = document.createElement('div');
  629. container.innerHTML = `
  630. <h3 class="modal-title">Notes for Current Page</h3>
  631. <div class="url-text">${currentUrl}</div>
  632. `;
  633.  
  634. if (matchingNotes.length === 0) {
  635. container.innerHTML += '<p style="color: #6b7280;">No matching notes found for this page</p>';
  636. } else {
  637. matchingNotes.forEach(({pattern, notes: patternNotes}) => {
  638. const patternDiv = document.createElement('div');
  639. patternDiv.innerHTML = `<div class="url-text">Pattern: ${pattern}</div>`;
  640.  
  641. patternNotes.forEach((note, index) => {
  642. const noteDiv = document.createElement('div');
  643. noteDiv.className = 'notes-list-item';
  644. noteDiv.innerHTML = `
  645. <span style="flex-grow: 1; display: flex; align-items: center;">${note.title}</span>
  646. <button class="delete-note-button" title="Delete note">×</button>
  647. `;
  648.  
  649. noteDiv.onclick = (e) => {
  650. if (!e.target.classList.contains('delete-note-button')) {
  651. container.parentElement.parentElement.remove();
  652. showNoteContent(note, pattern, index);
  653. }
  654. };
  655.  
  656. noteDiv.querySelector('.delete-note-button').onclick = (e) => {
  657. e.stopPropagation();
  658. if (confirm('Are you sure you want to delete this note?')) {
  659. deleteNote(pattern, index);
  660. noteDiv.remove();
  661. if (patternNotes.length === 1) {
  662. patternDiv.remove();
  663. }
  664. }
  665. };
  666.  
  667. patternDiv.appendChild(noteDiv);
  668. });
  669.  
  670. container.appendChild(patternDiv);
  671. });
  672. }
  673.  
  674. // Add help button and dropdown
  675. const helpButton = document.createElement('button');
  676. helpButton.textContent = '?';
  677. helpButton.style.position = 'absolute';
  678. helpButton.style.top = '16px';
  679. helpButton.style.right = '56px';
  680. helpButton.style.width = '32px';
  681. helpButton.style.height = '32px';
  682. helpButton.style.borderRadius = '50%';
  683. helpButton.style.border = 'none';
  684. helpButton.style.background = isDarkMode ? '#374151' : '#e5e7eb';
  685. helpButton.style.color = isDarkMode ? '#f3f4f6' : '#4b5563';
  686. helpButton.style.fontSize = '18px';
  687. helpButton.style.cursor = 'pointer';
  688. helpButton.style.display = 'flex';
  689. helpButton.style.alignItems = 'center';
  690. helpButton.style.justifyContent = 'center';
  691. helpButton.title = 'URL Pattern Help';
  692.  
  693. const helpDropdown = document.createElement('div');
  694. helpDropdown.style.position = 'absolute';
  695. helpDropdown.style.top = '52px';
  696. helpDropdown.style.right = '56px';
  697. helpDropdown.style.background = isDarkMode ? '#1f2937' : '#ffffff';
  698. helpDropdown.style.border = `1px solid ${isDarkMode ? '#4b5563' : '#e5e7eb'}`;
  699. helpDropdown.style.borderRadius = '8px';
  700. helpDropdown.style.padding = '16px';
  701. helpDropdown.style.boxShadow = '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)';
  702. helpDropdown.style.zIndex = '10001';
  703. helpDropdown.style.display = 'none';
  704. helpDropdown.style.maxWidth = '300px';
  705. helpDropdown.style.color = isDarkMode ? '#f3f4f6' : '#4b5563';
  706. helpDropdown.innerHTML = `
  707. <strong>URL Pattern Examples:</strong><br>
  708. - https://domain.com/* (matches entire domain, one level deep)<br>
  709. - https://domain.com/** (matches entire domain, any number of levels)<br>
  710. - https://domain.com/specific/* (matches specific path and one level below)<br>
  711. - https://domain.com/specific/** (matches specific path and any levels below)<br>
  712. - https://domain.com/*/specific (matches specific ending, one level in between)<br>
  713. - https://domain.com/**/specific (matches specific ending, any number of levels in between)
  714. `;
  715.  
  716. let isDropdownOpen = false;
  717.  
  718. helpButton.onmouseenter = () => {
  719. if (!isDropdownOpen) {
  720. helpDropdown.style.display = 'block';
  721. }
  722. };
  723.  
  724. helpButton.onmouseleave = () => {
  725. if (!isDropdownOpen) {
  726. helpDropdown.style.display = 'none';
  727. }
  728. };
  729.  
  730. helpButton.onclick = () => {
  731. isDropdownOpen = !isDropdownOpen;
  732. helpDropdown.style.display = isDropdownOpen ? 'block' : 'none';
  733. };
  734.  
  735. document.addEventListener('click', (e) => {
  736. if (isDropdownOpen && e.target !== helpButton && !helpDropdown.contains(e.target)) {
  737. isDropdownOpen = false;
  738. helpDropdown.style.display = 'none';
  739. }
  740. });
  741.  
  742. container.appendChild(helpButton);
  743. container.appendChild(helpDropdown);
  744.  
  745. createModal(container);
  746. }
  747.  
  748. function showAllNotes() {
  749. const notes = getAllNotes();
  750. const container = document.createElement('div');
  751. container.innerHTML = '<h3 class="modal-title">All Notes</h3>';
  752.  
  753. if (Object.keys(notes).length === 0) {
  754. container.innerHTML += '<p style="color: #6b7280;">No notes found</p>';
  755. } else {
  756. for (const url in notes) {
  757. const urlDiv = document.createElement('div');
  758. urlDiv.innerHTML = `<div class="url-text">${url}</div>`;
  759.  
  760. notes[url].forEach((note, index) => {
  761. const noteDiv = document.createElement('div');
  762. noteDiv.className = 'notes-list-item';
  763. noteDiv.innerHTML = `
  764. <span style="flex-grow: 1; display: flex; align-items: center;">${note.title}</span>
  765. <button class="delete-note-button" title="Delete note">×</button>
  766. `;
  767.  
  768. noteDiv.onclick = (e) => {
  769. if (!e.target.classList.contains('delete-note-button')) {
  770. container.parentElement.parentElement.remove();
  771. showNoteContent(note, url, index);
  772. }
  773. };
  774.  
  775. noteDiv.querySelector('.delete-note-button').onclick = (e) => {
  776. e.stopPropagation();
  777. if (confirm('Are you sure you want to delete this note?')) {
  778. deleteNote(url, index);
  779. noteDiv.remove();
  780. if (notes[url].length === 1) {
  781. urlDiv.remove();
  782. }
  783. }
  784. };
  785.  
  786. urlDiv.appendChild(noteDiv);
  787. });
  788.  
  789. container.appendChild(urlDiv);
  790. }
  791. }
  792.  
  793. createModal(container);
  794. }
  795.  
  796. document.addEventListener('keydown', function(e) {
  797. if (matchShortcut(e, options.shortcuts.newNote)) {
  798. e.preventDefault();
  799. showNoteForm();
  800. }
  801. if (matchShortcut(e, options.shortcuts.currentPageNotes)) {
  802. e.preventDefault();
  803. showCurrentPageNotes();
  804. }
  805. if (matchShortcut(e, options.shortcuts.allNotes)) {
  806. e.preventDefault();
  807. showAllNotes();
  808. }
  809. });
  810.  
  811. function matchShortcut(e, shortcut) {
  812. return e.ctrlKey === shortcut.ctrlKey &&
  813. e.shiftKey === shortcut.shiftKey &&
  814. e.altKey === shortcut.altKey &&
  815. e.key.toLowerCase() === shortcut.key.toLowerCase();
  816. }
  817.  
  818. displayPinnedNotes();
  819.  
  820. // Register menu commands
  821. GM_registerMenuCommand('New Note', () => showNoteForm());
  822. GM_registerMenuCommand('View Notes (Current Page)', showCurrentPageNotes);
  823. GM_registerMenuCommand('View All Notes', showAllNotes);
  824. GM_registerMenuCommand('Options', showOptionsMenu);
  825.  
  826. })();