// ==UserScript==
// @name WME Quick Zoom Button
// @name:en WME Quick Zoom Button
// @name:es Botón de Zoom Rápido WME
// @version 2025.07.21
// @description Fügt einen Button für temporären Zoom hinzu für bessere Chat sichtbarkeit und Auto-Sichtbarkeit
// @description:en Adds a button for temporary zoom for better chat visibility and auto-visibility
// @description:es Agrega un botón para zoom temporal para una mejor visibilidad del chat y auto-visibilidad
// @author Hiwi234
// @match https://www.waze.com/editor*
// @match https://www.waze.com/*/editor*
// @match https://beta.waze.com/editor*
// @match https://beta.waze.com/*/editor*
// @grant none
// @license MIT
// @namespace https://greasyfork.org/de/users/863740-horst-wittlich
// ==/UserScript==
(function() {
'use strict';
const STORAGE_KEY = {
AUTO: 'wme-quick-zoom-auto',
ZOOM: 'wme-quick-zoom-level',
VISIBILITY: 'wme-auto-visibility'
};
const translations = {
'de': {
buttonText: 'Quick Zoom',
buttonTooltip: 'Temporär auf Zoomstufe zoomen',
sliderLabel: 'Maximale Zoomstufe:',
autoLoadLabel: 'Automatisch beim Laden',
visibilityLabel: 'Immer sichtbar bleiben'
},
'en': {
buttonText: 'Quick Zoom',
buttonTooltip: 'Temporarily zoom to level',
sliderLabel: 'Maximum zoom level:',
autoLoadLabel: 'Automatic on load',
visibilityLabel: 'Always stay visible'
},
'es': {
buttonText: 'Zoom Rápido',
buttonTooltip: 'Zoom temporal al nivel',
sliderLabel: 'Nivel máximo de zoom:',
autoLoadLabel: 'Automático al cargar',
visibilityLabel: 'Permanecer siempre visible'
}
};
function getLanguage() {
const lang = navigator.language.split('-')[0];
return translations[lang] ? lang : 'en';
}
function getAutoZoomSetting() {
return localStorage.getItem(STORAGE_KEY.AUTO) === 'true';
}
function setAutoZoomSetting(value) {
localStorage.setItem(STORAGE_KEY.AUTO, value);
}
function getVisibilitySetting() {
return localStorage.getItem(STORAGE_KEY.VISIBILITY) === 'true';
}
function setVisibilitySetting(value) {
localStorage.setItem(STORAGE_KEY.VISIBILITY, value);
}
function getZoomLevel() {
return parseInt(localStorage.getItem(STORAGE_KEY.ZOOM) || '7');
}
function setZoomLevel(value) {
localStorage.setItem(STORAGE_KEY.ZOOM, value);
}
function ensureVisibility() {
if (!getVisibilitySetting()) return;
try {
// Suche nach dem Sichtbarkeits-Element im Online-Editoren Bereich
const visibilityLabel = document.querySelector('span.editor-visibility.label');
if (visibilityLabel) {
const labelText = visibilityLabel.textContent.toLowerCase().trim();
console.log('[WME Quick Zoom] Aktueller Sichtbarkeitsstatus:', labelText);
// Prüfe ob der Status "unsichtbar" oder "invisible" ist
if (labelText.includes('unsichtbar') || labelText.includes('invisible')) {
// Suche nach dem zugehörigen Button
const tooltipButton = visibilityLabel.closest('wz-list-item')?.querySelector('wz-button[color="clear-icon"]');
if (tooltipButton) {
console.log('[WME Quick Zoom] Unsichtbar erkannt, klicke Button...');
tooltipButton.click();
console.log('[WME Quick Zoom] Automatisch auf sichtbar gestellt');
return;
}
}
}
// Alternative Suche nach dem Button über verschiedene Selektoren
const alternativeSelectors = [
'wz-button[color="clear-icon"][size="md"]',
'button.wz-button.clear-icon.md.icon-only',
'wz-button button[type="button"]',
'.editor-visibility.label'
];
for (const selector of alternativeSelectors) {
const elements = document.querySelectorAll(selector);
for (const element of elements) {
// Prüfe verschiedene Text-Inhalte und Attribute
const elementText = element.textContent?.toLowerCase() || '';
const ariaLabel = element.getAttribute('aria-label')?.toLowerCase() || '';
const title = element.getAttribute('title')?.toLowerCase() || '';
const parentText = element.parentElement?.textContent?.toLowerCase() || '';
if (elementText.includes('unsichtbar') || elementText.includes('invisible') ||
ariaLabel.includes('unsichtbar') || ariaLabel.includes('invisible') ||
title.includes('unsichtbar') || title.includes('invisible') ||
parentText.includes('unsichtbar') || parentText.includes('invisible')) {
console.log('[WME Quick Zoom] Unsichtbar-Element gefunden, klicke...', selector);
element.click();
console.log('[WME Quick Zoom] Automatisch auf sichtbar gestellt');
return;
}
}
}
// Suche nach Icon-Namen
const iconElements = document.querySelectorAll('wz-icon, i[class*="icon"]');
for (const icon of iconElements) {
const iconName = icon.getAttribute('name') || icon.className;
if (iconName && (iconName.includes('invisible') || iconName.includes('unsichtbar'))) {
const clickableParent = icon.closest('button, wz-button, [clickable="true"]');
if (clickableParent) {
console.log('[WME Quick Zoom] Unsichtbar-Icon gefunden, klicke Parent...');
clickableParent.click();
console.log('[WME Quick Zoom] Automatisch auf sichtbar gestellt');
return;
}
}
}
} catch (error) {
console.warn('[WME Quick Zoom] Fehler beim Setzen der Sichtbarkeit:', error);
}
}
function startVisibilityMonitoring() {
if (!getVisibilitySetting()) return;
console.log('[WME Quick Zoom] Starte Sichtbarkeits-Überwachung...');
// Erweiterte DOM-Überwachung
const observer = new MutationObserver((mutations) => {
let shouldCheck = false;
mutations.forEach((mutation) => {
// Prüfe auf Änderungen in relevanten Bereichen
if (mutation.type === 'childList') {
const addedNodes = Array.from(mutation.addedNodes);
const removedNodes = Array.from(mutation.removedNodes);
const relevantChanges = [...addedNodes, ...removedNodes].some(node => {
if (node.nodeType === Node.ELEMENT_NODE) {
const element = node;
return element.classList && (element.classList.contains('online-editors-list') ||
element.classList.contains('editor-visibility')) ||
element.tagName === 'WZ-BUTTON' ||
(element.querySelector && element.querySelector('.editor-visibility, wz-button, .online-editors-list'));
}
return false;
});
if (relevantChanges) shouldCheck = true;
}
// Prüfe auf Attribut-Änderungen
if (mutation.type === 'attributes' &&
(mutation.attributeName === 'aria-label' ||
mutation.attributeName === 'title' ||
mutation.attributeName === 'class' ||
mutation.attributeName === 'name')) {
const target = mutation.target;
if (target.closest && (target.closest('.online-editors-list') ||
(target.classList && target.classList.contains('editor-visibility')) ||
target.tagName === 'WZ-BUTTON')) {
shouldCheck = true;
}
}
});
if (shouldCheck) {
setTimeout(ensureVisibility, 100); // Kurze Verzögerung für DOM-Updates
}
});
// Überwache das gesamte Online-Editoren Element
const onlineEditorsContainer = document.getElementById('online-editors');
if (onlineEditorsContainer) {
observer.observe(onlineEditorsContainer, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['aria-label', 'title', 'class', 'name']
});
} else {
// Fallback: Überwache den gesamten Body
observer.observe(document.body, {
childList: true,
subtree: true,
attributes: true,
attributeFilter: ['aria-label', 'title', 'class', 'name']
});
}
// Überprüfe alle 15 Sekunden
setInterval(ensureVisibility, 15000);
// Erste Überprüfung nach 3 Sekunden
setTimeout(ensureVisibility, 3000);
// Zusätzliche Überprüfung beim Fokus-Wechsel
window.addEventListener('focus', () => {
setTimeout(ensureVisibility, 1000);
});
}
async function performQuickZoom() {
const originalZoom = W.map.olMap.getZoom();
W.map.olMap.zoomTo(getZoomLevel());
return new Promise(resolve => {
setTimeout(() => {
W.map.olMap.zoomTo(originalZoom);
resolve();
}, 2000);
});
}
async function initializeQuickZoom() {
const i18n = translations[getLanguage()];
const { tabLabel, tabPane } = W.userscripts.registerSidebarTab("quick-zoom-script");
tabLabel.innerText = 'QZ';
tabLabel.title = i18n.buttonText;
const container = document.createElement('div');
container.style.display = 'flex';
container.style.flexDirection = 'column';
container.style.gap = '10px';
container.style.padding = '10px';
const sidebarButton = document.createElement('button');
sidebarButton.className = 'waze-btn waze-btn-small';
sidebarButton.innerText = i18n.buttonText;
sidebarButton.title = i18n.buttonTooltip;
const floatingButton = document.createElement('button');
floatingButton.innerText = 'QZ';
floatingButton.title = i18n.buttonTooltip;
floatingButton.style.cssText = `
position: fixed;
bottom: 20px;
left: 20px;
z-index: 1000;
background-color: white;
border: 1px solid #ccc;
padding: 8px 15px;
border-radius: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
cursor: pointer;
font-weight: bold;
transition: all 0.3s ease;
`;
floatingButton.addEventListener('mouseenter', () => {
floatingButton.style.backgroundColor = '#f0f0f0';
floatingButton.style.boxShadow = '0 4px 8px rgba(0,0,0,0.2)';
});
floatingButton.addEventListener('mouseleave', () => {
floatingButton.style.backgroundColor = 'white';
floatingButton.style.boxShadow = '0 2px 4px rgba(0,0,0,0.2)';
});
const sliderContainer = document.createElement('div');
sliderContainer.style.display = 'flex';
sliderContainer.style.flexDirection = 'column';
sliderContainer.style.gap = '5px';
const sliderLabel = document.createElement('label');
sliderLabel.textContent = i18n.sliderLabel;
sliderLabel.style.fontSize = '12px';
const sliderValue = document.createElement('span');
sliderValue.style.fontSize = '12px';
sliderValue.textContent = getZoomLevel();
const slider = document.createElement('input');
slider.type = 'range';
slider.min = '4';
slider.max = '12';
slider.value = getZoomLevel();
slider.style.width = '100%';
let isZooming = false;
const zoomHandler = async () => {
if (!isZooming) {
isZooming = true;
await performQuickZoom();
isZooming = false;
}
};
slider.addEventListener('input', (e) => {
const value = e.target.value;
sliderValue.textContent = value;
setZoomLevel(value);
sidebarButton.title = `${i18n.buttonTooltip} ${value}`;
floatingButton.title = `${i18n.buttonTooltip} ${value}`;
});
sidebarButton.addEventListener('click', zoomHandler);
floatingButton.addEventListener('click', zoomHandler);
const checkboxContainer = document.createElement('div');
checkboxContainer.style.display = 'flex';
checkboxContainer.style.alignItems = 'center';
checkboxContainer.style.gap = '5px';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.id = 'auto-quick-zoom';
checkbox.checked = getAutoZoomSetting();
const label = document.createElement('label');
label.htmlFor = 'auto-quick-zoom';
label.textContent = i18n.autoLoadLabel;
label.style.fontSize = '12px';
checkbox.addEventListener('change', (e) => {
setAutoZoomSetting(e.target.checked);
});
// Visibility Checkbox
const visibilityCheckboxContainer = document.createElement('div');
visibilityCheckboxContainer.style.display = 'flex';
visibilityCheckboxContainer.style.alignItems = 'center';
visibilityCheckboxContainer.style.gap = '5px';
const visibilityCheckbox = document.createElement('input');
visibilityCheckbox.type = 'checkbox';
visibilityCheckbox.id = 'auto-visibility';
visibilityCheckbox.checked = getVisibilitySetting();
const visibilityLabel = document.createElement('label');
visibilityLabel.htmlFor = 'auto-visibility';
visibilityLabel.textContent = i18n.visibilityLabel;
visibilityLabel.style.fontSize = '12px';
visibilityCheckbox.addEventListener('change', (e) => {
setVisibilitySetting(e.target.checked);
if (e.target.checked) {
startVisibilityMonitoring();
ensureVisibility();
}
});
sliderContainer.appendChild(sliderLabel);
sliderContainer.appendChild(slider);
sliderContainer.appendChild(sliderValue);
checkboxContainer.appendChild(checkbox);
checkboxContainer.appendChild(label);
visibilityCheckboxContainer.appendChild(visibilityCheckbox);
visibilityCheckboxContainer.appendChild(visibilityLabel);
container.appendChild(sidebarButton);
container.appendChild(sliderContainer);
container.appendChild(checkboxContainer);
container.appendChild(visibilityCheckboxContainer);
tabPane.appendChild(container);
document.body.appendChild(floatingButton);
await W.userscripts.waitForElementConnected(tabPane);
if (getAutoZoomSetting()) {
await performQuickZoom();
}
if (getVisibilitySetting()) {
startVisibilityMonitoring();
}
}
if (W?.userscripts?.state.isReady) {
initializeQuickZoom();
} else {
document.addEventListener("wme-ready", initializeQuickZoom, {
once: true,
});
}
})();