Professional Website Notes Manager Backup

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

目前为 2025-02-14 提交的版本,查看 最新版本

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