Path Notes

Add and save notes for different website paths

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
  1. // ==UserScript==
  2. // @name Path Notes
  3. // @namespace Violentmonkey Scripts
  4. // @version 2.0
  5. // @description Add and save notes for different website paths
  6. // @author maanimis
  7. // @match *://*/*
  8. // @grant GM_registerMenuCommand
  9. // @run-at document-end
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15. // Configuration
  16. const CONFIG = {
  17. notesPanelClass: 'path-notes-container',
  18. overlayClass: 'path-notes-overlay',
  19. textareaClass: 'path-notes-textarea',
  20. toggleBtnClass: 'path-notes-toggle',
  21. storageKey: 'pathNotes',
  22. animationDuration: 300, // ms
  23. debounceInterval: 500, // ms for autosave debounce
  24. };
  25. // DOM Elements (initialized later)
  26. let elements = {
  27. container: null,
  28. overlay: null,
  29. textarea: null,
  30. toggleBtn: null
  31. };
  32. // State management
  33. const state = {
  34. isVisible: false,
  35. currentPath: window.location.pathname + window.location.search,
  36. notes: {},
  37. saveTimeout: null
  38. };
  39. // Styles
  40. const styles = `
  41. .${CONFIG.overlayClass} {
  42. position: fixed;
  43. top: 0;
  44. left: 0;
  45. width: 100%;
  46. height: 100%;
  47. background-color: rgba(0, 0, 0, 0.5);
  48. backdrop-filter: blur(3px);
  49. z-index: 9999;
  50. opacity: 0;
  51. visibility: hidden;
  52. transition: opacity ${CONFIG.animationDuration}ms ease;
  53. }
  54. .${CONFIG.notesPanelClass} {
  55. position: fixed;
  56. top: 50%;
  57. left: 50%;
  58. transform: translate(-50%, -50%) scale(0.95);
  59. background-color: #ffffff;
  60. box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
  61. border-radius: 12px;
  62. padding: 20px;
  63. z-index: 10000;
  64. max-width: 500px;
  65. width: 90%;
  66. display: flex;
  67. flex-direction: column;
  68. font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  69. opacity: 0;
  70. visibility: hidden;
  71. transition: opacity ${CONFIG.animationDuration}ms ease,
  72. transform ${CONFIG.animationDuration}ms ease;
  73. }
  74. .${CONFIG.notesPanelClass}.visible {
  75. opacity: 1;
  76. visibility: visible;
  77. transform: translate(-50%, -50%) scale(1);
  78. }
  79. .${CONFIG.overlayClass}.visible {
  80. opacity: 1;
  81. visibility: visible;
  82. }
  83. .path-notes-header {
  84. display: flex;
  85. justify-content: space-between;
  86. align-items: center;
  87. margin-bottom: 15px;
  88. border-bottom: 1px solid #eee;
  89. padding-bottom: 10px;
  90. }
  91. .path-notes-title {
  92. margin: 0;
  93. color: #333;
  94. font-size: 18px;
  95. font-weight: 600;
  96. }
  97. .path-notes-content {
  98. display: flex;
  99. flex-direction: column;
  100. flex-grow: 1;
  101. }
  102. .path-notes-path {
  103. font-size: 14px;
  104. color: #666;
  105. margin-bottom: 10px;
  106. word-break: break-all;
  107. }
  108. .${CONFIG.textareaClass} {
  109. width: 100%;
  110. min-height: 180px;
  111. padding: 12px;
  112. border: 1px solid #ddd;
  113. border-radius: 8px;
  114. resize: vertical;
  115. font-family: inherit;
  116. font-size: 14px;
  117. line-height: 1.5;
  118. color: #333;
  119. transition: border-color 0.2s;
  120. }
  121. .${CONFIG.textareaClass}:focus {
  122. outline: none;
  123. border-color: #4d90fe;
  124. box-shadow: 0 0 0 2px rgba(77, 144, 254, 0.2);
  125. }
  126. .${CONFIG.toggleBtnClass} {
  127. position: fixed;
  128. bottom: 20px;
  129. right: 20px;
  130. width: 50px;
  131. height: 50px;
  132. border-radius: 50%;
  133. background-color: #4d90fe;
  134. color: white;
  135. display: flex;
  136. align-items: center;
  137. justify-content: center;
  138. font-size: 24px;
  139. cursor: pointer;
  140. box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
  141. z-index: 9999;
  142. transition: background-color 0.2s, transform 0.2s;
  143. }
  144. .${CONFIG.toggleBtnClass}:hover {
  145. background-color: #3a7be0;
  146. transform: scale(1.05);
  147. }
  148. @media (max-width: 600px) {
  149. .${CONFIG.notesPanelClass} {
  150. width: 85%;
  151. max-width: none;
  152. }
  153. .${CONFIG.textareaClass} {
  154. min-height: 140px;
  155. }
  156. }
  157. `;
  158. // Load notes from localStorage
  159. function loadNotes() {
  160. try {
  161. state.notes = JSON.parse(localStorage.getItem(CONFIG.storageKey)) || {};
  162. } catch (e) {
  163. state.notes = {};
  164. }
  165. return state.notes;
  166. }
  167. // Save notes to localStorage
  168. function saveNotes() {
  169. localStorage.setItem(CONFIG.storageKey, JSON.stringify(state.notes));
  170. }
  171. // Save current note with debounce
  172. function saveCurrentNote(text) {
  173. clearTimeout(state.saveTimeout);
  174. state.saveTimeout = setTimeout(() => {
  175. state.notes[state.currentPath] = text;
  176. saveNotes();
  177. }, CONFIG.debounceInterval);
  178. }
  179. // Show/hide the notes panel
  180. function toggleNotesPanel() {
  181. state.isVisible = !state.isVisible;
  182. if (state.isVisible) {
  183. elements.container.classList.add('visible');
  184. elements.overlay.classList.add('visible');
  185. elements.textarea.focus();
  186. } else {
  187. elements.container.classList.remove('visible');
  188. elements.overlay.classList.remove('visible');
  189. }
  190. }
  191. // Create UI elements
  192. function createUI() {
  193. // Add styles
  194. const styleElement = document.createElement('style');
  195. styleElement.textContent = styles;
  196. document.head.appendChild(styleElement);
  197. // Create overlay for background blur
  198. const overlay = document.createElement('div');
  199. overlay.className = CONFIG.overlayClass;
  200. document.body.appendChild(overlay);
  201. // Create notes container
  202. const container = document.createElement('div');
  203. container.className = CONFIG.notesPanelClass;
  204. // Load stored notes
  205. loadNotes();
  206. const currentNote = state.notes[state.currentPath] || '';
  207. // Create notes UI
  208. container.innerHTML = `
  209. <div class="path-notes-header">
  210. <h3 class="path-notes-title">Path Notes</h3>
  211. </div>
  212. <div class="path-notes-content">
  213. <div class="path-notes-path">Current path: ${state.currentPath}</div>
  214. <textarea class="${CONFIG.textareaClass}" placeholder="Add your notes for this page...">${currentNote}</textarea>
  215. </div>
  216. `;
  217. document.body.appendChild(container);
  218. // Create toggle button
  219. const toggleButton = document.createElement('div');
  220. toggleButton.className = CONFIG.toggleBtnClass;
  221. toggleButton.innerHTML = '📝';
  222. toggleButton.title = 'Toggle Path Notes';
  223. document.body.appendChild(toggleButton);
  224. // Store elements references
  225. elements.container = container;
  226. elements.overlay = overlay;
  227. elements.textarea = container.querySelector(`.${CONFIG.textareaClass}`);
  228. elements.toggleBtn = toggleButton;
  229. // Setup event listeners
  230. setupEventListeners();
  231. }
  232. // Setup event listeners
  233. function setupEventListeners() {
  234. // Toggle button click
  235. elements.toggleBtn.addEventListener('click', toggleNotesPanel);
  236. // Clicking overlay closes panel
  237. elements.overlay.addEventListener('click', () => {
  238. if (state.isVisible) {
  239. toggleNotesPanel();
  240. }
  241. });
  242. // Auto-save on typing
  243. elements.textarea.addEventListener('input', (e) => {
  244. saveCurrentNote(e.target.value);
  245. });
  246. }
  247. // Register menu command
  248. function registerMenuCommand() {
  249. if (typeof GM_registerMenuCommand !== 'undefined') {
  250. GM_registerMenuCommand('Open Path Notes', toggleNotesPanel);
  251. }
  252. }
  253. // Initialize the userscript
  254. function initialize() {
  255. createUI();
  256. registerMenuCommand();
  257. }
  258. // Start when the page is fully loaded
  259. window.addEventListener('load', initialize);
  260. })();