// ==UserScript==
// @name WME POI Shortcuts
// @namespace https://greasyfork.org/users/45389
// @version 2025.08.10.011
// @description Various UI changes to make editing faster and easier.
// @author kid4rm90s
// @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
// @license GNU GPLv3
// @connect greasyfork.org
// @contributionURL https://github.com/WazeDev/Thank-The-Authors
// @grant GM_xmlhttpRequest
// @grant GM_addElement
// @require https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @require https://update.greasyfork.icu/scripts/509664/WME%20Utils%20-%20Bootstrap.js
// @require https://update.greasyfork.icu/scripts/523706/1569240/Link%20Enhancer.js
// @downloadURL none
// ==/UserScript==
/* global WazeWrap */
/* global bootstrap */
(function () {
('use strict');
const updateMessage = `
Temporarily sdk based shortcuts disabled and legacy shortcuts key support added`;
const scriptName = GM_info.script.name;
const scriptVersion = GM_info.script.version;
const downloadUrl = 'https://greasyfork.org/scripts/545278-wme-poi-shortcuts/code/wme-poi-shortcuts.user.js';
const forumURL = 'https://greasyfork.org/scripts/545278-wme-poi-shortcuts/feedback';
if (typeof unsafeWindow !== 'undefined' && unsafeWindow.SDK_INITIALIZED) {
unsafeWindow.SDK_INITIALIZED.then(initScript);
} else if (typeof window.SDK_INITIALIZED !== 'undefined') {
window.SDK_INITIALIZED.then(initScript);
} else {
console.error('WME SDK is not available. Script will not run.');
}
// Inject custom CSS for grayed out disabled options
injectCSSWithID('pieDisabledOptionStyle', `select[id^='pieItem'] option:disabled { color: #bbb !important; background: #000000ff !important; }`);
// --- GLE (Google Link Enhancer) Integration ---
// GLE settings and messages
let GLE = {
enabled: false,
showTempClosedPOIs: true,
enable() {
this.enabled = true;
ToggleExternalProvidersCSS(true);
},
disable() {
this.enabled = false;
ToggleExternalProvidersCSS(false);
},
closedPlace: 'Google indicates this place is permanently closed.\nVerify with other sources or your editor community before deleting.',
multiLinked: 'Linked more than once already. Please find and remove multiple links.',
linkedToThisPlace: 'Already linked to this place',
linkedNearby: 'Already linked to a nearby place',
linkedToXPlaces: 'This is linked to {0} places',
badLink: 'Invalid Google link. Please remove it.',
tooFar: 'The Google linked place is more than {0} meters from the Waze place. Please verify the link is correct.',
};
// Inject CSS helper
function injectCSSWithID(id, css) {
let style = document.getElementById(id);
if (!style) {
style = document.createElement('style');
style.id = id;
style.type = 'text/css';
style.appendChild(document.createTextNode(css));
document.head.appendChild(style);
}
}
// Toggle external providers CSS
function ToggleExternalProvidersCSS(truthiness) {
if (truthiness) injectCSSWithID('pieExternalProvidersTweaks', '#edit-panel .external-providers-view .select2-container {width:90%; margin-bottom:2px;}');
else {
var styles = document.getElementById('pieExternalProvidersTweaks');
if (styles) styles.parentNode.removeChild(styles);
}
}
// Add GLE controls to the sidebar UI
function buildGLEControls() {
return `
', { style: 'padding:4px 8px;font-size:10px;', id: 'piePlaceCat' + itemNumber });
$section.html(
[
`
Item ${itemNumber}`,
buildItemList(itemNumber),
`
${buildLockLevelDropdown(itemNumber)}
${buildGeometryTypeDropdown(itemNumber)}
`,
].join(' ')
);
return $section.html();
}
function buildAllItemOptions() {
let html = '';
for (let i = 1; i <= 10; i++) {
html += buildItemOption(i);
}
setTimeout(() => {
for (let i = 1; i <= 10; i++) {
loadPOIShortcutItem(i);
//legacy shortcuts key added from here
// Populate shortcut input with the actual shortcut key
const shortcutKey = i === 10 ? 'Ctrl+0' : `Ctrl+${i}`;
$(`#pieShortcut${i}`).val(shortcutKey);
// legacy shortcuts key added until above
// Save on change
$(`#pieItem${i},#pieLock${i},#pieGeom${i}`)
.off('change.wmepoi')
.on('change.wmepoi', function () {
savePOIShortcutItem(i);
// Prevent duplicate category selection
if (this.id.startsWith('pieItem')) {
const selectedCategories = [];
for (let j = 1; j <= 10; j++) {
const val = $(`#pieItem${j}`).val();
if (val) selectedCategories.push(val);
}
for (let j = 1; j <= 10; j++) {
$(`#pieItem${j} option`).prop('disabled', false).removeAttr('title');
}
for (let j = 1; j <= 10; j++) {
const currentVal = $(`#pieItem${j}`).val();
for (const cat of selectedCategories) {
if (cat !== currentVal) {
$(`#pieItem${j} option[value='${cat}']`).prop('disabled', true).attr('title', 'this category is already selected.');
}
}
}
}
});
}
// Initial duplicate prevention
const selectedCategories = [];
for (let j = 1; j <= 10; j++) {
const val = $(`#pieItem${j}`).val();
if (val) selectedCategories.push(val);
}
for (let j = 1; j <= 10; j++) {
$(`#pieItem${j} option`).prop('disabled', false).removeAttr('title');
}
for (let j = 1; j <= 10; j++) {
const currentVal = $(`#pieItem${j}`).val();
for (const cat of selectedCategories) {
if (cat !== currentVal) {
$(`#pieItem${j} option[value='${cat}']`).prop('disabled', true).attr('title', 'this category is already selected.');
}
}
}
}, 0);
return html;
}
/*
// --- wmeSDK Shortcuts Setup ---
// TODO: Re-enable when wmeSDK fixes shortcuts persistence after page refresh
/*
function setupShortcuts(wmeSDK) {
// Create 10 POI shortcut actions, one for each item
for (let i = 1; i <= 10; i++) {
// Assign shortcutKeys: C1-C9, C0 for 10
const shortcutKey = i === 10 ? 'C0' : `C${i}`;
const shortcutId = `create-poi-shortcut-${i}`;
// Remove previous shortcut if registered
if (wmeSDK.Shortcuts.isShortcutRegistered({ shortcutId })) {
wmeSDK.Shortcuts.deleteShortcut({ shortcutId });
}
// Check if shortcut keys are in use
if (wmeSDK.Shortcuts.areShortcutKeysInUse({ shortcutKeys: shortcutKey })) {
console.warn(`Shortcut keys ${shortcutKey} already in use, skipping registration for POI Shortcut #${i}`);
continue;
}
wmeSDK.Shortcuts.createShortcut({
callback: () => {
// Get selected values from the UI for this item
const cat = $(`#pieItem${i}`).val();
const lock = parseInt($(`#pieLock${i}`).val(), 10);
const geomType = $(`#pieGeom${i}`).val();
// Geometry: area = drawPolygon, point = drawPoint
let drawPromise = geomType === 'point' ? wmeSDK.Map.drawPoint() : wmeSDK.Map.drawPolygon();
drawPromise.then((geometry) => {
let newVenue = wmeSDK.DataModel.Venues.addVenue({
category: cat,
geometry: geometry,
});
wmeSDK.Editing.setSelection({
selection: {
ids: [newVenue.toString()],
objectType: 'venue',
},
});
// Only set lock if lock > 0 (lockRank 1-4)
if (!isNaN(lock) && lock > 0) {
wmeSDK.DataModel.Venues.updateVenue({
venueId: newVenue.toString(),
lockRank: lock,
});
}
// Nepal-specific logic for Gas Station
const topCountry = wmeSDK.DataModel.Countries.getTopCountry();
if (topCountry && (topCountry.name === 'Nepal' || topCountry.code === 'NP') && cat === 'GAS_STATION') {
wmeSDK.DataModel.Venues.updateVenue({
venueId: newVenue.toString(),
name: 'NOC',
brand: 'Nepal Oil Corporation',
});
}
});
},
description: `Create POI Shortcut #${i}`,
shortcutId,
shortcutKeys: shortcutKey,
});
}
// Shortcuts that click on WME's existing UI buttons for POI creation/modification
wmeSDK.Shortcuts.createShortcut({
callback: () => {
$("wz-icon[name='toll-booth']").parent().trigger('click');
},
description: 'Add Toll Booth',
shortcutId: 'add-toll-booth',
shortcutKeys: null,
});
wmeSDK.Shortcuts.createShortcut({
callback: () => {
$("wz-icon[name='railway-crossing']").parent().trigger('click');
},
description: 'Add Level Crossing',
shortcutId: 'add-level-crossing',
shortcutKeys: null,
});
wmeSDK.Shortcuts.createShortcut({
callback: () => {
$("wz-icon[name='school-zone']").parent().trigger('click');
},
description: 'Create School Zone',
shortcutId: 'create-school-zone',
shortcutKeys: null,
});
}
*/
/***********************************************legacy shortcuts below*********************************************** */
// --- Legacy Shortcuts Setup (Temporary until wmeSDK fixes shortcuts persistence) ---
function setupShortcuts(wmeSDK) {
// Legacy shortcuts configuration - maps shortcut numbers to keyboard combos
var shortcutsConfig = [
{
handler: 'WME-POI-Shortcuts_poi1',
title: 'POI Shortcut 1',
func: function (ev) {
createPOIFromShortcut(1, wmeSDK);
},
key: null,
arg: { slotNumber: 1 },
},
{
handler: 'WME-POI-Shortcuts_poi2',
title: 'POI Shortcut 2',
func: function (ev) {
createPOIFromShortcut(2, wmeSDK);
},
key: null,
arg: { slotNumber: 2 },
},
{
handler: 'WME-POI-Shortcuts_poi3',
title: 'POI Shortcut 3',
func: function (ev) {
createPOIFromShortcut(3, wmeSDK);
},
key: null,
arg: { slotNumber: 3 },
},
{
handler: 'WME-POI-Shortcuts_poi4',
title: 'POI Shortcut 4',
func: function (ev) {
createPOIFromShortcut(4, wmeSDK);
},
key: null,
arg: { slotNumber: 4 },
},
{
handler: 'WME-POI-Shortcuts_poi5',
title: 'POI Shortcut 5',
func: function (ev) {
createPOIFromShortcut(5, wmeSDK);
},
key: null,
arg: { slotNumber: 5 },
},
{
handler: 'WME-POI-Shortcuts_poi6',
title: 'POI Shortcut 6',
func: function (ev) {
createPOIFromShortcut(6, wmeSDK);
},
key: null,
arg: { slotNumber: 6 },
},
{
handler: 'WME-POI-Shortcuts_poi7',
title: 'POI Shortcut 7',
func: function (ev) {
createPOIFromShortcut(7, wmeSDK);
},
key: null,
arg: { slotNumber: 7 },
},
{
handler: 'WME-POI-Shortcuts_poi8',
title: 'POI Shortcut 8',
func: function (ev) {
createPOIFromShortcut(8, wmeSDK);
},
key: null,
arg: { slotNumber: 8 },
},
{
handler: 'WME-POI-Shortcuts_poi9',
title: 'POI Shortcut 9',
func: function (ev) {
createPOIFromShortcut(9, wmeSDK);
},
key: null,
arg: { slotNumber: 9 },
},
{
handler: 'WME-POI-Shortcuts_poi10',
title: 'POI Shortcut 10',
func: function (ev) {
createPOIFromShortcut(10, wmeSDK);
},
key: null,
arg: { slotNumber: 10 },
},
{
handler: 'WME-POI-Shortcuts_toll-booth',
title: 'Add Toll Booth',
func: function (ev) {
$("wz-icon[name='toll-booth']").parent().trigger('click');
},
key: -1, // No default key, user can set custom
arg: {},
},
{
handler: 'WME-POI-Shortcuts_level-crossing',
title: 'Add Level Crossing',
func: function (ev) {
$("wz-icon[name='railway-crossing']").parent().trigger('click');
},
key: -1, // No default key, user can set custom
arg: {},
},
{
handler: 'WME-POI-Shortcuts_school-zone',
title: 'Create School Zone',
func: function (ev) {
$("wz-icon[name='school-zone']").parent().trigger('click');
},
key: -1, // No default key, user can set custom
arg: {},
},
];
// Register legacy shortcuts
for (var i = 0; i < shortcutsConfig.length; ++i) {
WMEKSRegisterKeyboardShortcut('WME-POI-Shortcuts', 'WME POI Shortcuts', shortcutsConfig[i].handler, shortcutsConfig[i].title, shortcutsConfig[i].func, shortcutsConfig[i].key, shortcutsConfig[i].arg);
}
WMEKSLoadKeyboardShortcuts('WME-POI-Shortcuts');
window.addEventListener(
'beforeunload',
function () {
WMEKSSaveKeyboardShortcuts('WME-POI-Shortcuts');
},
false
);
}
// Function to create POI from shortcut slot
function createPOIFromShortcut(slotNumber, wmeSDK) {
try {
// Get selected values from the UI for this item
const cat = $(`#pieItem${slotNumber}`).val();
const lock = parseInt($(`#pieLock${slotNumber}`).val(), 10);
const geomType = $(`#pieGeom${slotNumber}`).val();
if (!cat || cat === '') {
console.warn(`POI Shortcut ${slotNumber}: No category selected`);
return;
}
// Geometry: area = drawPolygon, point = drawPoint
let drawPromise = geomType === 'point' ? wmeSDK.Map.drawPoint() : wmeSDK.Map.drawPolygon();
drawPromise.then((geometry) => {
let newVenue = wmeSDK.DataModel.Venues.addVenue({
category: cat,
geometry: geometry,
});
wmeSDK.Editing.setSelection({
selection: {
ids: [newVenue.toString()],
objectType: 'venue',
},
});
// Only set lock if lock > 0 (lockRank 1-4)
if (!isNaN(lock) && lock > 0) {
wmeSDK.DataModel.Venues.updateVenue({
venueId: newVenue.toString(),
lockRank: lock,
});
}
// Nepal-specific logic for Gas Station
const topCountry = wmeSDK.DataModel.Countries.getTopCountry();
if (topCountry && (topCountry.name === 'Nepal' || topCountry.code === 'NP') && cat === 'GAS_STATION') {
wmeSDK.DataModel.Venues.updateVenue({
venueId: newVenue.toString(),
name: 'NOC',
brand: 'Nepal Oil Corporation',
});
}
});
} catch (error) {
console.error(`Error creating POI from shortcut ${slotNumber}:`, error);
}
}
// --- Legacy Keyboard Shortcuts System (from WME Street to River PLUS) ---
function WMEKSRegisterKeyboardShortcut(scriptName, shortcutsHeader, newShortcut, shortcutDescription, functionToCall, shortcutKeysObj, arg) {
try {
I18n.translations[I18n.locale].keyboard_shortcuts.groups[scriptName].members.length;
} catch (c) {
(W.accelerators.Groups[scriptName] = []),
(W.accelerators.Groups[scriptName].members = []),
(I18n.translations[I18n.locale].keyboard_shortcuts.groups[scriptName] = []),
(I18n.translations[I18n.locale].keyboard_shortcuts.groups[scriptName].description = shortcutsHeader),
(I18n.translations[I18n.locale].keyboard_shortcuts.groups[scriptName].members = []);
}
if (functionToCall && 'function' == typeof functionToCall) {
(I18n.translations[I18n.locale].keyboard_shortcuts.groups[scriptName].members[newShortcut] = shortcutDescription),
W.accelerators.addAction(newShortcut, {
group: scriptName,
});
var i = '-1',
j = {};
(j[i] = newShortcut),
W.accelerators._registerShortcuts(j),
null !== shortcutKeysObj && ((j = {}), (j[shortcutKeysObj] = newShortcut), W.accelerators._registerShortcuts(j)),
W.accelerators.events.register(newShortcut, null, function () {
functionToCall(arg);
});
} else alert('The function ' + functionToCall + ' has not been declared');
}
function WMEKSLoadKeyboardShortcuts(scriptName) {
console.log('WMEKSLoadKeyboardShortcuts(' + scriptName + ')');
if (localStorage[scriptName + 'KBS'])
for (var shortcuts = JSON.parse(localStorage[scriptName + 'KBS']), i = 0; i < shortcuts.length; i++)
try {
W.accelerators._registerShortcuts(shortcuts[i]);
} catch (error) {
console.log(error);
}
}
function WMEKSSaveKeyboardShortcuts(scriptName) {
console.log('WMEKSSaveKeyboardShortcuts(' + scriptName + ')');
var shortcuts = [];
for (var actionName in W.accelerators.Actions) {
var shortcutString = '';
if (W.accelerators.Actions[actionName].group == scriptName) {
W.accelerators.Actions[actionName].shortcut
? (W.accelerators.Actions[actionName].shortcut.altKey === !0 && (shortcutString += 'A'),
W.accelerators.Actions[actionName].shortcut.shiftKey === !0 && (shortcutString += 'S'),
W.accelerators.Actions[actionName].shortcut.ctrlKey === !0 && (shortcutString += 'C'),
'' !== shortcutString && (shortcutString += '+'),
W.accelerators.Actions[actionName].shortcut.keyCode && (shortcutString += W.accelerators.Actions[actionName].shortcut.keyCode))
: (shortcutString = '-1');
var shortcutObj = {};
(shortcutObj[shortcutString] = W.accelerators.Actions[actionName].id), (shortcuts[shortcuts.length] = shortcutObj);
}
}
localStorage[scriptName + 'KBS'] = JSON.stringify(shortcuts);
}
/******************************************legacy shortcuts until here above************************************ */
function getGasStationCategoryKey() {
// Use I18n to get the correct category key for gas station
// Fallback to 'GAS_STATION' if not found
let locale = typeof I18n !== 'undefined' && I18n.currentLocale ? I18n.currentLocale() : 'en';
let categories = I18n?.translations?.[locale]?.venues?.categories || {};
// Find the key for 'Gas Station' or 'Petrol Station' in the current language
for (const key in categories) {
if (categories[key] === 'Gas Station' || categories[key] === 'Petrol Station') {
return key;
}
}
// Fallback to 'GAS_STATION'
return 'GAS_STATION';
}
function injectNOCButtonIfNepalGasStation(wmeSDK) {
// Only run if a venue is selected
const selection = wmeSDK.Editing.getSelection();
if (!selection || selection.objectType !== 'venue' || !selection.ids || selection.ids.length !== 1) return;
const venueId = selection.ids[0];
const venue = wmeSDK.DataModel.Venues.getById({ venueId });
const topCountry = wmeSDK.DataModel.Countries.getTopCountry();
const gasStationKey = getGasStationCategoryKey();
// Check if venue.categories (array) contains the gas station key
const isNepalGasStation = !!venue && !!topCountry && (topCountry.name === 'Nepal' || topCountry.code === 'NP') && Array.isArray(venue.categories) && venue.categories.includes(gasStationKey);
if (!isNepalGasStation) return;
// Wait for the categories-control element to exist
function tryInject() {
const $catControl = $('.categories-control');
if ($catControl.length === 0) {
setTimeout(tryInject, 150); // Retry after 150ms
return;
}
// Prevent duplicate button
if ($('.noc-gas-station-btn').length > 0) return;
// Inject button after categories-control
const buttonHtml = `
`;
$catControl.after(buttonHtml);
// Button click handler
$('.noc-gas-station-btn').on('click', function () {
// Read lockRank for GAS_STATION from localStorage config
let lockRank = null;
let config = {};
try {
config = JSON.parse(localStorage.getItem('wme-poi-shortcuts-config') || '{}');
} catch (e) {
config = {};
}
let foundConfig = false;
for (let i = 1; i <= 10; i++) {
if (config[i] && config[i].category === gasStationKey) {
lockRank = parseInt(config[i].lock, 10);
console.log(`[NOC Debug] Found gas station shortcut config: slot=${i}, lockRank=${lockRank}`);
foundConfig = true;
break;
}
}
if (!foundConfig || isNaN(lockRank)) {
console.log(`[NOC Debug] Using fallback lockRank. venue.lockRank=${venue.lockRank}`);
lockRank = venue.lockRank && !isNaN(venue.lockRank) ? venue.lockRank : 1;
}
console.log(`[NOC Debug] Final lockRank to be used: ${lockRank}`);
// Move current name to aliases if not 'NOC'
if (venue.name !== 'NOC') {
let aliases = Array.isArray(venue.aliases) ? venue.aliases.slice() : [];
if (venue.name && !aliases.includes(venue.name)) {
aliases.push(venue.name);
}
const updateObj = {
venueId: venueId,
name: 'NOC',
aliases: aliases,
};
if (venue.brand !== 'Nepal Oil Corporation') {
updateObj.brand = 'Nepal Oil Corporation';
console.log('[NOC Debug] Brand updated to Nepal Oil Corporation');
} else {
console.log('[NOC Debug] Brand already Nepal Oil Corporation, skipping brand update');
}
if (venue.lockRank !== lockRank && (!venue.isLocked || venue.isLocked === false)) {
updateObj.lockRank = lockRank;
console.log(`[NOC Debug] lockRank updated to ${lockRank}`);
} else {
console.log(`[NOC Debug] lockRank already ${venue.lockRank}, skipping lockRank update`);
}
try {
wmeSDK.DataModel.Venues.updateVenue(updateObj);
} catch (err) {
console.warn('[NOC Debug] Update failed:', err);
}
} else {
const updateObj = {
venueId: venueId,
};
if (venue.brand !== 'Nepal Oil Corporation') {
updateObj.brand = 'Nepal Oil Corporation';
console.log('[NOC Debug] Brand updated to Nepal Oil Corporation');
} else {
console.log('[NOC Debug] Brand already Nepal Oil Corporation, skipping brand update');
}
if (venue.lockRank !== lockRank && (!venue.isLocked || venue.isLocked === false)) {
updateObj.lockRank = lockRank;
console.log(`[NOC Debug] lockRank updated to ${lockRank}`);
} else {
console.log(`[NOC Debug] lockRank already ${venue.lockRank}, skipping lockRank update`);
}
try {
wmeSDK.DataModel.Venues.updateVenue(updateObj);
} catch (err) {
console.warn('[NOC Debug] Update failed:', err);
}
}
});
}
tryInject();
}
async function registerSidebarScriptTab(wmeSDK) {
// Register a script tab in the Scripts sidebar
try {
const { tabLabel, tabPane } = await wmeSDK.Sidebar.registerScriptTab();
// Add label/icon to the tab
tabLabel.innerHTML = '
⭐POI Shortcuts';
// Use buildAllItemOptions to show all 10 dropdowns with script info header
tabPane.innerHTML = `
${scriptName}
${scriptVersion}
${buildGLEControls()}
${buildAllItemOptions()}
`;
// Add event listeners for GLE controls
setTimeout(() => {
const cbEnableGLE = document.getElementById('_cbEnableGLE');
const cbGLEShowTempClosed = document.getElementById('_cbGLEShowTempClosed');
if (cbEnableGLE) {
cbEnableGLE.addEventListener('change', function () {
if (this.checked) {
GLE.enable();
} else {
GLE.disable();
// Force map refresh to remove lingering highlights
setTimeout(() => {
if (typeof W !== 'undefined' && W.map && W.map.getOLMap()) {
W.map.getOLMap().redraw();
}
}, 100);
}
cbGLEShowTempClosed.disabled = !this.checked;
});
}
if (cbGLEShowTempClosed) {
cbGLEShowTempClosed.addEventListener('change', function () {
GLE.showTempClosedPOIs = this.checked;
// Force map refresh when toggling temp closed highlights
setTimeout(() => {
if (typeof W !== 'undefined' && W.map && W.map.getOLMap()) {
W.map.getOLMap().redraw();
}
}, 100);
});
}
}, 0);
} catch (e) {
console.error('Failed to register POI Shortcuts script tab:', e);
}
}
function scriptupdatemonitor() {
if (WazeWrap?.Ready) {
bootstrap({ scriptUpdateMonitor: { downloadUrl } });
WazeWrap.Interface.ShowScriptUpdate(scriptName, scriptVersion, updateMessage, downloadUrl, forumURL);
} else {
setTimeout(scriptupdatemonitor, 250);
}
}
// Start the "scriptupdatemonitor"
scriptupdatemonitor();
console.log(`${scriptName} initialized.`);
/*Changelogs
2025.08.10.011
- Legacy shortcuts key support
*/
})();