Waze Manage Bookmarks with Backup (API Version)

Displays a UI for managing bookmarks in the Waze editor using the Userscript API, with export/import features and paste buttons.

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください。
  1. // ==UserScript==
  2. // @name Waze Manage Bookmarks with Backup (API Version)
  3. // @namespace https://greatest.deepsurf.us/ja/users/735907-cauliflower-carrot
  4. // @version 2.6
  5. // @description Displays a UI for managing bookmarks in the Waze editor using the Userscript API, with export/import features and paste buttons.
  6. // @author aoi
  7. // @match https://www.waze.com/*/editor*
  8. // @grant none
  9. // ==/UserScript==
  10.  
  11. (function() {
  12. 'use strict';
  13.  
  14. const SCRIPT_ID = 'waze-bookmarks-manager';
  15. const defaultFontSize = '16px';
  16. let allowedUrls = JSON.parse(localStorage.getItem('allowedUrls')) || [];
  17.  
  18. // Wait for WME to be fully ready
  19. function waitForWmeReady() {
  20. if (W?.userscripts?.state.isReady) {
  21. initializeScript();
  22. } else {
  23. document.addEventListener('wme-ready', initializeScript, { once: true });
  24. }
  25. }
  26.  
  27. async function initializeScript() {
  28. // Register sidebar tab
  29. const { tabLabel, tabPane } = W.userscripts.registerSidebarTab(SCRIPT_ID);
  30.  
  31. // Set tab label
  32. tabLabel.innerText = 'BM';
  33. tabLabel.title = 'Bookmark Manager';
  34.  
  35. // Create tab content with paste buttons
  36. tabPane.innerHTML = `
  37. <div style="padding: 10px;">
  38. <h3 style="margin: 0 0 10px 0;">Bookmark Management</h3>
  39. <div style="display: flex; align-items: center; margin-bottom: 5px;">
  40. <input type="text" id="urlInput" placeholder="Enter URL" style="flex: 1; padding: 5px; box-sizing: border-box;">
  41. <button id="pasteUrlButton" style="margin-left: 5px; padding: 5px 10px; background-color: #9C27B0; color: white; border: none; border-radius: 3px;" title="Paste from clipboard">Paste</button>
  42. </div>
  43. <div style="display: flex; align-items: center; margin-bottom: 5px;">
  44. <input type="text" id="nameInput" placeholder="Enter Bookmark Name" style="flex: 1; padding: 5px; box-sizing: border-box;">
  45. <button id="pasteNameButton" style="margin-left: 5px; padding: 5px 10px; background-color: #9C27B0; color: white; border: none; border-radius: 3px;" title="Paste from clipboard">Paste</button>
  46. </div>
  47. <button id="addUrlButton" style="width: 100%; margin-top: 5px; padding: 5px; background-color: #4CAF50; color: white; border: none; border-radius: 5px;">Add Bookmark</button>
  48. <ul id="urlList" style="list-style: none; padding: 0; margin: 10px 0 0 0; max-height: 200px; overflow-y: auto;"></ul>
  49. <button id="clearCacheButton" style="width: 100%; margin-top: 15px; padding: 5px; background-color: #2196F3; color: white; border: none; border-radius: 5px;">Clear Cache</button>
  50. <button id="exportButton" style="width: 100%; margin-top: 10px; padding: 5px; background-color: #FF9800; color: white; border: none; border-radius: 5px;">Export Bookmarks</button>
  51. <input type="file" id="importButton" accept=".json" style="width: 100%; margin-top: 10px;" title="Import Bookmarks">
  52. </div>
  53. `;
  54.  
  55. // Wait for tab to be connected to DOM
  56. await W.userscripts.waitForElementConnected(tabPane);
  57.  
  58. // Update bookmark list
  59. updateUrlList();
  60.  
  61. // Set up event listeners
  62. setupEventListeners();
  63. }
  64.  
  65. // Function to update bookmark list
  66. function updateUrlList() {
  67. const urlList = document.getElementById('urlList');
  68. urlList.innerHTML = '';
  69. allowedUrls.forEach((item) => {
  70. const li = document.createElement('li');
  71. li.style.display = 'flex';
  72. li.style.justifyContent = 'space-between';
  73. li.style.alignItems = 'center';
  74. li.style.marginBottom = '5px';
  75. li.innerHTML = `<span style="flex: 1; font-size: ${defaultFontSize};"><a href="${item.url}" style="color: #2196F3;" class="bookmarkLink" data-url="${item.url}">${item.name || item.url}</a></span>`;
  76. const removeButton = document.createElement('button');
  77. removeButton.textContent = 'Remove';
  78. removeButton.style.marginLeft = '10px';
  79. removeButton.style.backgroundColor = '#F44336';
  80. removeButton.style.color = 'white';
  81. removeButton.style.border = 'none';
  82. removeButton.style.borderRadius = '3px';
  83. removeButton.style.padding = '2px 5px';
  84. removeButton.addEventListener('click', () => {
  85. allowedUrls = allowedUrls.filter(i => i.url !== item.url);
  86. localStorage.setItem('allowedUrls', JSON.stringify(allowedUrls));
  87. updateUrlList();
  88. });
  89. li.appendChild(removeButton);
  90. urlList.appendChild(li);
  91. });
  92. }
  93.  
  94. // Function to clear cache and navigate
  95. function clearCacheAndNavigate(url) {
  96. caches.keys().then(names => {
  97. return Promise.all(names.map(name => caches.delete(name)));
  98. }).then(() => {
  99. window.location.href = url;
  100. });
  101. }
  102.  
  103. // Function to paste from clipboard
  104. async function pasteFromClipboard(inputId) {
  105. try {
  106. const text = await navigator.clipboard.readText();
  107. document.getElementById(inputId).value = text;
  108. } catch (err) {
  109. console.error('Failed to paste from clipboard:', err);
  110. alert('Failed to paste from clipboard. Please ensure clipboard permissions are enabled.');
  111. }
  112. }
  113.  
  114. // Set up event listeners
  115. function setupEventListeners() {
  116. document.getElementById('addUrlButton').addEventListener('click', () => {
  117. const urlInput = document.getElementById('urlInput');
  118. const nameInput = document.getElementById('nameInput');
  119. const newUrl = urlInput.value.trim();
  120. const newName = nameInput.value.trim();
  121.  
  122. if (newUrl && !allowedUrls.some(item => item.url === newUrl)) {
  123. allowedUrls.push({ url: newUrl, name: newName });
  124. localStorage.setItem('allowedUrls', JSON.stringify(allowedUrls));
  125. urlInput.value = '';
  126. nameInput.value = '';
  127. updateUrlList();
  128. } else if (allowedUrls.some(item => item.url === newUrl)) {
  129. alert('This bookmark already exists.');
  130. } else {
  131. alert('Please enter a URL.');
  132. }
  133. });
  134.  
  135. document.getElementById('clearCacheButton').addEventListener('click', () => {
  136. clearCacheAndNavigate(window.location.href);
  137. });
  138.  
  139. document.getElementById('exportButton').addEventListener('click', () => {
  140. const dataStr = JSON.stringify(allowedUrls);
  141. const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
  142. const linkElement = document.createElement('a');
  143. linkElement.setAttribute('href', dataUri);
  144. linkElement.setAttribute('download', 'bookmarks.json');
  145. linkElement.click();
  146. });
  147.  
  148. document.getElementById('importButton').addEventListener('change', (event) => {
  149. const file = event.target.files[0];
  150. if (file) {
  151. const reader = new FileReader();
  152. reader.onload = (e) => {
  153. allowedUrls = JSON.parse(e.target.result);
  154. localStorage.setItem('allowedUrls', JSON.stringify(allowedUrls));
  155. updateUrlList();
  156. };
  157. reader.readAsText(file);
  158. }
  159. });
  160.  
  161. document.getElementById('pasteUrlButton').addEventListener('click', () => {
  162. pasteFromClipboard('urlInput');
  163. });
  164.  
  165. document.getElementById('pasteNameButton').addEventListener('click', () => {
  166. pasteFromClipboard('nameInput');
  167. });
  168.  
  169. document.addEventListener('click', (e) => {
  170. if (e.target.classList.contains('bookmarkLink')) {
  171. e.preventDefault();
  172. const targetUrl = e.target.getAttribute('data-url');
  173. clearCacheAndNavigate(targetUrl);
  174. }
  175. });
  176. }
  177.  
  178. // Start script initialization
  179. waitForWmeReady();
  180. })();