// ==UserScript==
// @name WME Enhanced Actions
// @namespace http://tampermonkey.net/
// @version 2.0
// @description Automate Waze editing tasks with shortcuts and streamlined actions.
// @author Astheron
// @match https://www.waze.com/*
// @grant none
// @license MIT
// ==/UserScript==
/**
* License and Credits:
*
* This script is licensed under the MIT License:
* Copyright (c) 2024 Astheron
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this script and associated documentation files (the "Script"), to deal
* in the Script without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Script, and to permit persons to whom the Script is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Script.
*
* THE SCRIPT IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SCRIPT OR THE USE OR OTHER DEALINGS IN THE
* SCRIPT.
*
* **Contact:** For more details or inquiries, you can contact the author, Astheron, on the Waze forum.
*/
(function() {
'use strict';
let autoSaveEnabled = JSON.parse(localStorage.getItem('autoSaveEnabled')) || false;
let saveKeyCombination = JSON.parse(localStorage.getItem('saveKeyCombination')) || [];
let isRecording = false;
function getEditorLanguage() {
const url = window.location.href;
if (url.includes('/es/')) {
return 'es';
} else if (url.includes('/ca/')) {
return 'ca';
}
return 'en';
}
const lang = getEditorLanguage();
const isSpanish = lang === 'es';
const isCatalan = lang === 'ca';
const translations = {
en: {
title: 'WME Enhanced Actions',
setSaveKey: 'Set Save Shortcut:',
saveHint: 'Use Ctrl, Alt, Shift with other keys.',
recording: 'Waiting for assignment...',
toggleRecording: 'Record',
toggleRecordingOn: 'Recording: On',
additionalFeatures: 'Features:',
featureList: '<ul><li>Press Enter to quickly confirm actions.</li><li>Auto-save edits with a custom shortcut.</li><li>Streamline your workflow with automated tools.</li></ul>',
about: 'About:',
description: 'This script helps Waze editors automate tasks, assign shortcuts, and improve efficiency during map editing.',
developedBy: 'Script developed by <a href="https://www.waze.com/user/editor/Astheron" target="_blank" style="color: #007BFF; text-decoration: none;">Astheron</a>. For questions, visit the forum: ',
forumLink: 'https://www.waze.com/discuss/t/script-wme-enhanced-actions/304400'
},
es: {
title: 'WME Enhanced Actions',
setSaveKey: 'Configurar Atajo de Guardado Inteligente:',
saveHint: 'Usa teclas o combinaciones con Ctrl, Alt, Shift y otras teclas.',
recording: 'Esperando asignación...',
toggleRecording: 'Grabar',
toggleRecordingOn: 'Grabación: Encendido',
additionalFeatures: 'Funciones:',
featureList: '<ul><li>Presiona Enter para confirmar acciones rápidamente.</li><li>Guardado automático con un atajo personalizado.</li><li>Optimiza tu flujo de trabajo con herramientas automáticas.</li></ul>',
about: 'Acerca de:',
description: 'Este script ayuda a los editores de Waze a automatizar tareas, asignar atajos y mejorar la eficiencia durante la edición de mapas.',
developedBy: 'Script programado por <a href="https://www.waze.com/user/editor/Astheron" target="_blank" style="color: #007BFF; text-decoration: none;">Astheron</a>. Para preguntas, visita el foro: ',
forumLink: 'https://www.waze.com/discuss/t/script-wme-enhanced-actions/304400'
},
ca: {
title: 'WME Enhanced Actions',
setSaveKey: 'Configurar drecera de desament:',
saveHint: 'Utilitza tecles o combinacions amb Ctrl, Alt, Shift i altres tecles.',
recording: 'Esperant assignació...',
toggleRecording: 'Enregistrar',
toggleRecordingOn: 'Enregistrament: Activat',
additionalFeatures: 'Funcions:',
featureList: '<ul><li>Prem Enter per confirmar accions ràpidament.</li><li>Desament automàtic amb una drecera personalitzada.</li><li>Optimitza el teu flux de treball amb eines automàtiques.</li></ul>',
about: 'Sobre:',
description: 'Aquest script ajuda els editors de Waze a automatitzar tasques, assignar dreceres i millorar l`eficiència durant l`edició de mapes.',
developedBy: 'Script desenvolupat per <a href="https://www.waze.com/user/editor/Astheron" target="_blank" style="color: #007BFF; text-decoration: none;">Astheron</a>. Per a preguntes, visita el fòrum: ',
forumLink: 'https://www.waze.com/discuss/t/script-wme-enhanced-actions/304400'
}
};
const t = isSpanish ? translations.es : isCatalan ? translations.ca : translations.en;
function initializeSidebarTab() {
const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("enhanced-actions");
tabLabel.innerText = t.title;
tabLabel.title = 'Configure Auto Save and Shortcuts';
tabPane.innerHTML = `
<div style="font-family: Arial, sans-serif; padding: 20px; border: 1px solid #ccc; border-radius: 10px; background: linear-gradient(to right, #f9f9f9, #ffffff); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);">
<h2 style="color: #007BFF; font-size: 2em; margin-bottom: 15px; text-align: center;">${t.title}</h2>
<div style="margin-bottom: 25px; text-align: center;">
<label for="saveKeyInput" style="font-weight: bold; font-size: 1.1em;">${t.setSaveKey}</label>
<input type="text" id="saveKeyInput" readonly placeholder="Recording..."
style="width: 270px; padding: 10px; margin-top: 10px; margin-bottom: 10px; border: 2px solid #007BFF; border-radius: 5px; box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1);">
<button id="toggleRecordingButton" style="margin: 20px; padding: 10px 15px; background-color: #465d9c; color: white; border: none; border-radius: 5px; cursor: pointer; box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2); font-weight: bold; text-align: center;">${t.toggleRecording}</button>
<p style="font-size: 0.9em; color: #555; margin: 20px 0;">${t.saveHint}</p>
</div>
<hr style="border: none; border-top: 1px solid #ddd; margin: 20px 0;">
<h3 style="color: #007BFF; font-size: 1.4em; text-align: center;">${t.additionalFeatures}</h3>
<div style="font-size: 1em; color: #444; margin: 20px;">
${t.featureList}
</div>
<hr style="border: none; border-top: 1px solid #ddd; margin: 20px 0;">
<h3 style="color: #007BFF; font-size: 1.4em; text-align: center;">${t.about}</h3>
<p style="font-size: 0.95em; color: #555; text-align: center; margin: 20px 0;">${t.description}</p>
<p style="font-size: 0.8em; color: #555; text-align: center; margin: 20px 0;">${t.developedBy}<a href="${t.forumLink}" target="_blank" style="color: #007BFF; text-decoration: none;">Waze forum</a>.</p>
</div>
`;
const saveKeyInput = tabPane.querySelector('#saveKeyInput');
const toggleRecordingButton = tabPane.querySelector('#toggleRecordingButton');
saveKeyInput.value = saveKeyCombination.join('+');
toggleRecordingButton.addEventListener('click', () => {
if (!isRecording) {
startRecordingKeyCombination(saveKeyInput, 'save');
saveKeyInput.value = t.recording;
toggleRecordingButton.textContent = t.toggleRecordingOn;
toggleRecordingButton.style.backgroundColor = '#dc3545';
isRecording = true;
} else {
stopRecordingKeyCombination(saveKeyInput, 'save');
toggleRecordingButton.textContent = t.toggleRecording;
toggleRecordingButton.style.backgroundColor = '#465d9c';
isRecording = false;
}
});
W.userscripts.waitForElementConnected(tabPane).then(() => {
console.log("Enhanced Actions tab connected.");
});
}
function startRecordingKeyCombination(inputField, type) {
let combination = [];
const keydownListener = (event) => {
if (!isRecording) return;
// Do not prevent default behavior for inputs, textareas, or wz-textarea elements
if (event.target.tagName === 'INPUT' || event.target.tagName === 'TEXTAREA' || event.target.closest('wz-textarea')) {
return;
}
event.preventDefault();
let key = event.key;
if (event.ctrlKey && !combination.includes('Ctrl')) {
combination.push('Ctrl');
}
if (event.altKey && !combination.includes('Alt')) {
combination.push('Alt');
}
if (event.shiftKey && !combination.includes('Shift')) {
combination.push('Shift');
}
if (!['Control', 'Shift', 'Alt', 'Meta'].includes(key)) {
combination.push(key);
}
inputField.value = combination.join('+');
};
document.addEventListener('keydown', keydownListener);
}
function stopRecordingKeyCombination(inputField, type) {
document.removeEventListener('keydown', startRecordingKeyCombination);
inputField.classList.remove('recording');
if (type === 'save') {
saveKeyCombination = inputField.value.split('+');
localStorage.setItem('saveKeyCombination', JSON.stringify(saveKeyCombination));
autoSaveEnabled = saveKeyCombination.length > 0;
localStorage.setItem('autoSaveEnabled', JSON.stringify(autoSaveEnabled));
}
}
function blurActiveInput() {
const activeElement = document.activeElement;
if (activeElement && (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA')) {
activeElement.blur();
}
}
function handleEnterPress(event) {
if (event.key === 'Enter' &&
!(document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA' || document.activeElement.closest('wz-textarea'))) {
event.preventDefault();
blurActiveInput();
let applyButton = document.querySelector('wz-button.save-button');
if (applyButton) {
setTimeout(() => {
applyButton.click();
}, 100);
} else {
console.log("Apply button not found.");
}
}
}
function handleSaveKeyPress(event) {
if (document.activeElement.tagName === 'INPUT' ||
document.activeElement.tagName === 'TEXTAREA' ||
document.activeElement.closest('wz-textarea') ||
document.activeElement.isContentEditable) {
return;
}
const pressedCombination = [];
if (event.ctrlKey) pressedCombination.push('Ctrl');
if (event.altKey) pressedCombination.push('Alt');
if (event.shiftKey) pressedCombination.push('Shift');
if (!['Control', 'Shift', 'Alt', 'Meta'].includes(event.key)) {
pressedCombination.push(event.key);
}
if (JSON.stringify(pressedCombination) === JSON.stringify(saveKeyCombination) && autoSaveEnabled) {
event.preventDefault();
blurActiveInput();
let saveButton = document.querySelector('wz-button#save-button');
if (saveButton && saveButton.getAttribute('disabled') !== "true") {
setTimeout(() => {
saveButton.click();
}, 100);
} else {
console.log("Save button not found or disabled.");
}
}
// Handle confirmation dialog "Guardar de todos modos"
let alarmingSaveButton = document.querySelector('wz-button.save[alarming="true"]');
if (alarmingSaveButton && JSON.stringify(pressedCombination) === JSON.stringify(saveKeyCombination)) {
event.preventDefault();
alarmingSaveButton.click();
}
}
function initializeMyUserscript() {
initializeSidebarTab();
document.addEventListener('keydown', handleEnterPress, true);
document.addEventListener('keydown', handleSaveKeyPress, true);
}
if (W?.userscripts?.state.isReady) {
initializeMyUserscript();
} else {
document.addEventListener("wme-ready", initializeMyUserscript, { once: true });
}
})();