A base library for WME script writers
이 스크립트는 직접 설치하는 용도가 아닙니다. 다른 스크립트에서 메타 지시문 // @require https://update.greasyfork.org/scripts/24851/1815214/WazeWrap.js을(를) 사용하여 포함하는 라이브러리입니다.
// ==UserScript==
// @name WazeWrap
// @namespace https://greasyfork.org/users/30701-justins83-waze
// @version 2026.05.03.01
// @description Self-contained WazeWrap with no external dependencies - includes toastr library and CSS
// @author JustinS83/MapOMatic/JS55CT
// @include https://beta.waze.com/*editor*
// @include https://www.waze.com/*editor*
// @exclude https://www.waze.com/*user/editor/*
// @grant none
// @note Scripts using ScriptUpdateMonitor must grant GM_xmlhttpRequest in their own header
// ==/UserScript==
/* global WazeWrap */
/* global $ */
/**
* ===== WAZEWRAP LOADING PATTERN =====
*
* ARCHITECTURE:
* This is a self-contained, single-file version of WazeWrap with all dependencies
* (toastr library, CSS) embedded inline. No external network requests are made.
*
* LOADING SEQUENCE:
* 1. Page load → IIFE starts immediately
* 2. CSS and toastr library code are defined (not executed yet)
* 3. bootstrapInit() polls for jQuery availability (up to 100 seconds)
* 4. Once $ exists → initBootloader() + initWazeWrap() are called
* 5. initializeToastr() → initializeToastrLibrary() (toastr UMD now executes with jQuery available)
* 6. WazeWrap.Ready = true (signals to other scripts that WazeWrap is ready)
*
* MULTI-SCRIPT SAFETY:
* - WazeWrap.Repo = 'self-contained' (set immediately, prevents duplicate init)
* - WazeWrap.Ready = false (during init), then true (when complete)
* - Other scripts can check WazeWrap.Ready before using WazeWrap.Alerts, etc.
* - Sandbox-aware: WazeWrap available in both window and unsafeWindow contexts
*
* DEFERRED TOASTR INITIALIZATION:
* - Toastr UMD pattern wrapped in initializeToastrLibrary() function
* - Not executed until jQuery is confirmed available
* - simpleDefine() loader passes jQuery explicitly to toastr factory
* - Prevents "Cannot read properties of undefined" errors
*
* KEY MODULES:
* - Alerts: Toast notifications (success, error, info, warning, debug, confirm, prompt)
* - Interface: Script update notifications dashboard
* - ScriptUpdateMonitor: Version checking for script updates
*/
var WazeWrap;
(function () {
'use strict';
// ===== TOASTR CSS =====
// Injected directly into page to avoid external requests
const TOASTR_CSS = `
.toast-title {
font-weight: 700;
}
.toast-message {
-ms-word-wrap: break-word;
word-wrap: break-word;
}
.toast-message a,
.toast-message label {
color: #fff;
}
.toast-message a:hover {
color: #ccc;
text-decoration: none;
}
.toast-debug {
width: 700px !important;
}
.toast-debug > .toast-message {
min-height: 400px;
height: 60vh;
overflow-y: auto;
}
.toast-close-button {
position: relative;
right: -0.3em;
top: -0.3em;
float: right;
font-size: 20px;
font-weight: 700;
color: #fff;
-webkit-text-shadow: 0 1px 0 #fff;
text-shadow: 0 1px 0 #fff;
opacity: 0.8;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=80);
filter: alpha(opacity=80);
line-height: 1;
}
.toast-close-button:focus,
.toast-close-button:hover {
color: #000;
text-decoration: none;
cursor: pointer;
opacity: 0.4;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
filter: alpha(opacity=40);
}
.rtl .toast-close-button {
left: -0.3em;
float: left;
right: 0.3em;
}
button.toast-close-button {
padding: 0;
cursor: pointer;
background: 0 0;
border: 0;
-webkit-appearance: none;
}
.toast-top-center {
top: 0;
right: 0;
width: 100%;
}
.toast-bottom-center {
bottom: 0;
right: 0;
width: 100%;
}
.toast-top-full-width {
top: 0;
right: 0;
width: 100%;
}
.toast-bottom-full-width {
bottom: 0;
right: 0;
width: 100%;
}
.toast-top-center-wide {
top: 32px;
right: 0;
width: 100%;
}
.toast-bottom-center-wide {
bottom: 0;
right: 0;
width: 100%;
}
.toast-top-left {
top: 12px;
left: 12px;
}
.toast-top-right {
top: 12px;
right: 12px;
}
.toast-bottom-right {
right: 12px;
bottom: 12px;
}
.toast-bottom-left {
bottom: 12px;
left: 12px;
}
.toast-container-wazedev {
position: absolute;
z-index: 999999;
pointer-events: none;
}
.toast-container-wazedev * {
-moz-box-sizing: border-box;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.toast-container-wazedev > div {
position: relative;
pointer-events: auto;
overflow: hidden;
margin: 0 0 6px;
padding: 15px 15px 15px 50px;
width: 300px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
background-position: 15px center;
background-repeat: no-repeat;
-moz-box-shadow: 0 0 12px #999;
-webkit-box-shadow: 0 0 12px #999;
box-shadow: 0 0 12px #999;
color: #fff;
opacity: 0.95;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=95);
filter: alpha(opacity=95);
}
.toast-container-wazedev > div.rtl {
direction: rtl;
padding: 15px 50px 15px 15px;
background-position: right 15px center;
}
.toast-container-wazedev > div:hover {
-moz-box-shadow: 0 0 12px #000;
-webkit-box-shadow: 0 0 12px #000;
box-shadow: 0 0 12px #000;
opacity: 1;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=100);
filter: alpha(opacity=100);
cursor: pointer;
}
.toast-container-wazedev > .toast-info {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=) !important;
}
.toast-container-wazedev > .toast-error {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=) !important;
}
.toast-container-wazedev > .toast-success {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==) !important;
}
.toast-container-wazedev > .toast-warning {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=) !important;
}
.toast-container-wazedev > .toast-prompt {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAAOwgAADsIBFShKgAAAABh0RVh0U29mdHdhcmUAcGFpbnQubmV0IDQuMS42/U4J6AAAARJJREFUSEu1k7EOhSAMRR2d3u7k/3+QfoHGqJtxcOP1NkAKViKKNzmpVLmlgJUx5lPUZEnUZERNNBfgnTbHoyYJbzpNEwVdy7IgJIvFCWfcElkahgHhVCg2zzZWBA9fpLS5ky/iCqC10oLndwXGcUSoeXvWdcUgkM0127bx2Aqtx1vZ2gPW1KRWzy3aaI7jQPgR8rzcXl96pApgMt7JFXNO/BvaN1JcoPQNkuICqS7eCIvmQy7ehb0gwX/ARezVeqV93xHYHMgCXIR43Im9rt4cSHNH9nnM84yAeYE5CAaE2gFW1nWd6fuewTMJho6TsUMOrraHbwMhv72NHMRbA+Pk6u4gBzC61XYOarIkarIcpvoDnRHBRUBBxjQAAAAASUVORK5CYII=) !important;
}
.toast-container-wazedev > .toast-confirm {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuNv1OCegAAAG+SURBVEhL7ZQ9S8NQFIYbq1VRFETBbwddXYWCo+Lm7iKik/4CFydRBBGlo4u7v0Khi9hBHNTFX2CHhhZSkzb1uelJ29Q0TRpHH3i5ufec857k5iaJsNRqtZRt2yOumPdJqDcw0DAaRtvo4BsYGyoWi/fkbHA9KCXhEfMl9IZM5r4QM9BjPp9fY6pJeXdInqbw3XEJQblcNmiyKeXBkD+Ock5lNB64qTGx8YcE9QKfpSAy1F6LlT/kJEk6qac3Ye1DraNZNFetVjOVSkWiTTgHFsOo2PlDQgodW5ZlqyIMdTTPZeNY5nK5AeJ3Ku7DpKR1hqT+Uqm0xV1+mqZ5xNxzQtScphnl5kP3BgoSNTSkRllyYK4+OLVVr1x74KnUEwe/6E5QrxmGsYLBJXK2rx2Wg19yJ6hVW7LKWHCcfCD+gqakJBrc+TLFX+LlgXWlJzTDNPzX7KKK2Ntbx60NTC2O5n6hUJhgGt3cBaOruqUXfng7DElJ6x0anNYtPVyg+OYKjPZoknXFl5zVdX1XwvHBdAGlW0XTRQnHB8NztSetsHYm4fhgdiO+Df66wa//zn8DD/itY3jYprSEA0gkfgDQ/HIWcSKxGQAAAABJRU5ErkJggg==) !important;
}
.toast-container-wazedev > .toast-debug {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAYdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjEuNv1OCegAAALKSURBVEhLnZbNa1NBFMWTaimKCq1Y0YqCuBTpRlTUhSBVkBZEbP0XrIpduNL/wL3iui5ERJBacaEQsSAEgwELIqHgokGIiUqb729/5+XmNa8vX/bAYWbuPefOvJl5Lwm0klarDcEjYr1eH4ZBS3WENOj3NX1wyFJ+lMvlSQTfqtXq90qlkqKNEpuytA/SownDn/KJilm6O1jZIJPMYP5BO6eVxuPxvaLyFJsjt0rueigU2u6YtoJcLneYIr8ptkxRB/S/wl/ZbPaQybaOYrE4Ts08T+ABE+SInzDZ1kGhl1bTB3ILJusPeHx7yfasUOhxPp+fYMXHRPqXiD2BKyZz4avBPo5R5Bbid7TrtJ8RjVtaN+ViqVR6Q+5VJpM5gP4gmufwRaFQuGIybeVxedFnaZfQ32ExY9qCPJ0C7XuC9+hH4R+EFxjPEi9oOwT686IN6+jKcBbtZeJr8Aueu7RvYRFmA8lk8iSiUVuIHnEnokVitUaZDRD6KNrQAzwLsVjMfclUkxt43oZekNwDS+Z10WkCVqpd2GH23uAQj2Lo+wmYoJJOp/eb3Q80A3CwSQ7vGq0PnSYQOPxJGrcGHHCKRyIRfRL0HUm30Lc9goqLNvRAHvM65OCXCQed1RPQlbrfJI/82nFtArluh/wBPmipc9t5gnbg3p8ynwfdJsBz1uy9QZFRWDGvi04TECvSDJu9MxDuZnsewU/0+76mxApszxK+h6ph5bxAF0SwCP/CZzDesG+g0wRoV80jb/sPIKc+JTEFpjWmHUGsm+Ci3QRooolEYpd5pmGN8zinsQt02xDqOxKm7/4O67eAeMapBDZPQH9dGpM3dyFMXHUa74HAjGcIChMWckFsBpatoDuBYuCqyVzow0dOT3HaQo03WVfThj6gv8HK1jA/pZ1nrEO9aWkPqBXUgmn/73c6lUrpA6i/NeKIhXsgEPgHYbgVlAQqkpAAAAAASUVORK5CYII=);
}
.toast-container-wazedev.toast-bottom-center > div,
.toast-container-wazedev.toast-top-center > div {
width: 300px;
margin-left: auto;
margin-right: auto;
}
.toast-container-wazedev.toast-bottom-full-width > div,
.toast-container-wazedev.toast-top-full-width > div {
width: 96%;
margin-left: auto;
margin-right: auto;
}
.toast-container-wazedev.toast-bottom-center-wide > div,
.toast-container-wazedev.toast-top-center-wide > div {
width: 500px;
margin-left: auto;
margin-right: auto;
}
.toast-wazedev {
background-color: #030303;
}
.toast-success {
background-color: #51a351;
}
.toast-error {
background-color: #bd362f;
}
.toast-info {
background-color: #2f96b4;
}
.toast-warning {
background-color: #f89406;
}
.toast-prompt {
background-color: #369 !important;
}
.toast-confirm,
.toast-debug {
background-color: #555;
}
.toast-progress {
position: absolute;
left: 0;
bottom: 0;
height: 4px;
background-color: #000;
opacity: 0.4;
-ms-filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=40);
filter: alpha(opacity=40);
}
.btn {
border: 0;
border-radius: 5px;
cursor: pointer;
font-family: "Waze Boing Medium", "Waze Boing HB", sans-serif;
font-size: 12px;
height: 28px;
line-height: 18px;
padding: 6px 20px;
transition: all 0.1s ease;
display: inline-block;
margin-bottom: 0;
font-weight: normal;
text-align: center;
white-space: nowrap;
vertical-align: middle;
touch-action: manipulation;
background-image: none;
-webkit-user-select: none;
user-select: none;
}
.btn:hover {
text-decoration: none;
}
.btn-primary {
color: #fff;
background-color: #3279b7;
border-color: #2d6da3;
}
.btn.btn-primary {
background-color: #78b0bf;
box-shadow: 0 2px 0 #559cae;
color: #fff;
}
.btn-danger {
color: #fff;
background-color: #d9534f;
border-color: #d4463a;
}
.btn.btn-danger {
background-color: #eb7171;
box-shadow: 0 2px 0 #e66262;
color: #fff;
}
.btn-danger:hover {
background-color: #d9534f;
}
.toast-ok-btn {
margin-right: 8px;
}
.toast-prompt-input {
width: 100%;
}
@media all and (max-width: 240px) {
.toast-container-wazedev > div {
padding: 8px 8px 8px 50px;
width: 11em;
}
.toast-container-wazedev > div.rtl {
padding: 8px 50px 8px 8px;
}
.toast-container-wazedev .toast-close-button {
right: -0.2em;
top: -0.2em;
}
.toast-container-wazedev .rtl .toast-close-button {
left: -0.2em;
right: 0.2em;
}
}
@media all and (min-width: 241px) and (max-width: 480px) {
.toast-container-wazedev > div {
padding: 8px 8px 8px 50px;
width: 18em;
}
.toast-container-wazedev > div.rtl {
padding: 8px 50px 8px 8px;
}
.toast-container-wazedev .toast-close-button {
right: -0.2em;
top: -0.2em;
}
.toast-container-wazedev .rtl .toast-close-button {
left: -0.2em;
right: 0.2em;
}
}
@media all and (min-width: 481px) and (max-width: 768px) {
.toast-container-wazedev > div {
padding: 15px 15px 15px 50px;
width: 25em;
}
.toast-container-wazedev > div.rtl {
padding: 15px 50px 15px 15px;
}
}
`;
// ===== TOASTR LIBRARY (Inline) =====
// Embedded directly without external dependencies
// Wrapped in function to ensure jQuery is available when toastr initializes
function initializeToastrLibrary() {
// Define a simple AMD-like loader that handles jQuery dependency
const simpleDefine = function(deps, factory) {
// deps is ['jquery'], get jQuery and call factory
const jQuery = typeof $ !== 'undefined' ? $ : window.jQuery;
if (!jQuery) {
console.error('WazeWrap: jQuery not available for toastr initialization');
return;
}
// Call factory and store result in window.wazedevtoastr
window.wazedevtoastr = factory(jQuery);
return window.wazedevtoastr;
};
(function (define) {
define(['jquery'], function ($) {
return (function () {
var $container;
var listener;
var toastId = 0;
var toastType = {
confirm: 'confirm',
debug: 'debug',
error: 'error',
info: 'info',
prompt: 'prompt',
success: 'success',
warning: 'warning'
};
var toastr = {
clear: clear,
confirm: confirm,
debug: debug,
remove: remove,
error: error,
getContainer: getContainer,
info: info,
options: {},
prompt: prompt,
subscribe: subscribe,
success: success,
version: '2.1.5',
warning: warning
};
var previousToast;
return toastr;
function error(message, title, optionsOverride) {
return notify({
type: toastType.error,
iconClass: getOptions().iconClasses.error,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function getContainer(options, create) {
if (!options) { options = getOptions(); }
$container = $('#' + options.containerId);
if ($container.length) {
return $container;
}
if (create) {
$container = createContainer(options);
}
return $container;
}
function info(message, title, optionsOverride) {
return notify({
type: toastType.info,
iconClass: getOptions().iconClasses.info,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function subscribe(callback) {
listener = callback;
}
function success(message, title, optionsOverride) {
return notify({
type: toastType.success,
iconClass: getOptions().iconClasses.success,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function warning(message, title, optionsOverride) {
return notify({
type: toastType.warning,
iconClass: getOptions().iconClasses.warning,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function prompt(message, title, optionsOverride) {
return notify({
type: toastType.prompt,
iconClass: getOptions().iconClasses.prompt,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function confirm(message, title, optionsOverride) {
return notify({
type: toastType.confirm,
iconClass: getOptions().iconClasses.confirm,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function debug(message, title, optionsOverride) {
console.groupCollapsed('%c' + title, 'background: #252525; color: #e94f64');
console.log(message);
console.groupEnd();
return notify({
type: toastType.debug,
iconClass: getOptions().iconClasses.debug,
message: message,
optionsOverride: optionsOverride,
title: title
});
}
function clear($toastElement, clearOptions) {
var options = getOptions();
if (!$container) { getContainer(options); }
if (!clearToast($toastElement, options, clearOptions)) {
clearContainer(options);
}
}
function remove($toastElement) {
var options = getOptions();
if (!$container) { getContainer(options); }
if ($toastElement && $(':focus', $toastElement).length === 0) {
removeToast($toastElement);
return;
}
if ($container.children().length) {
$container.remove();
}
}
// internal functions
function clearContainer (options) {
var toastsToClear = $container.children();
for (var i = toastsToClear.length - 1; i >= 0; i--) {
clearToast($(toastsToClear[i]), options);
}
}
function clearToast ($toastElement, options, clearOptions) {
var force = clearOptions && clearOptions.force ? clearOptions.force : false;
if ($toastElement && (force || $(':focus', $toastElement).length === 0)) {
$toastElement[options.hideMethod]({
duration: options.hideDuration,
easing: options.hideEasing,
complete: function () { removeToast($toastElement); }
});
return true;
}
return false;
}
function createContainer(options) {
$container = $('<div/>')
.attr('id', options.containerId)
.addClass(options.positionClass)
.addClass(options.containerClass);
$container.appendTo($(options.target));
return $container;
}
function getDefaults() {
return {
tapToDismiss: true, toastClass: 'toast-wazedev',
containerId: 'toast-container-wazedev', containerClass: 'toast-container-wazedev',
debug: false,
showMethod: 'fadeIn', //fadeIn, slideDown, and show are built into jQuery
showDuration: 300,
showEasing: 'swing', //swing and linear are built into jQuery
onShown: undefined,
hideMethod: 'fadeOut', hideDuration: 1000, hideEasing: 'swing',
onHidden: undefined,
closeMethod: false, closeDuration: false, closeEasing: false, closeOnHover: true,
extendedTimeOut: 1000,
iconClasses: {
confirm: 'toast-confirm',
debug: 'toast-debug',
error: 'toast-error',
info: 'toast-info',
prompt: 'toast-prompt',
success: 'toast-success',
warning: 'toast-warning'
},
iconClass: 'toast-info',
positionClass: 'toast-top-right',
timeOut: 5000, // Set timeOut and extendedTimeOut to 0 to make it sticky
titleClass: 'toast-title',
messageClass: 'toast-message',
escapeHtml: false,
target: 'body',
closeHtml: '<button type="button">×</button>',
closeClass: 'toast-close-button',
newestOnTop: true,
preventDuplicates: false,
progressBar: false,
progressClass: 'toast-progress',
rtl: false,
PromptDefaultInput: '',
ConfirmOkButtonText: 'Ok',
ConfirmCancelButtonText: 'Cancel'
};
}
function publish(args) {
if (!listener) { return; }
listener(args);
}
function notify(map) {
if (map.message.length === 0) { return; }
var options = getOptions();
var iconClass = map.iconClass || options.iconClass;
if (typeof (map.optionsOverride) !== 'undefined') {
options = $.extend(options, map.optionsOverride);
iconClass = map.optionsOverride.iconClass || iconClass;
}
// Force sticky behavior for prompt, confirm, and debug types
if (map.type === toastType.prompt || map.type === toastType.confirm) {
options.tapToDismiss = false;
options.timeOut = 0;
options.extendedTimeOut = 0;
options.closeButton = false;
}
if (map.type === toastType.debug) {
options.tapToDismiss = false;
options.timeOut = 0;
options.extendedTimeOut = 0;
options.closeButton = true;
}
if (shouldExit(options, map)) { return; }
toastId++;
$container = getContainer(options, true);
var intervalId = null;
var $toastElement = $('<div/>');
var $titleElement = $('<div/>');
var $messageElement = $('<div/>');
var $progressElement = $('<div/>');
var $closeElement = $(options.closeHtml);
var $promptContainer = $('<div/>');
var $promptOkButton = $('<button class="btn btn-primary toast-ok-btn">Ok</button>');
var $promptCancelButton = $('<button class="btn btn-danger">Cancel</button>');
var $promptInput = $('<input type="text" class="toast-prompt-input"/>');
var $confirmContainer = $('<div/>');
var $confirmOkButton = $('<button class="btn btn-primary toast-ok-btn"></button>');
var $confirmCancelButton = $('<button class="btn btn-danger"></button>');
$confirmOkButton.text(options.ConfirmOkButtonText);
$confirmCancelButton.text(options.ConfirmCancelButtonText);
var progressBar = {
intervalId: null,
hideEta: null,
maxHideTime: null
};
var response = {
toastId: toastId,
state: 'visible',
startTime: new Date(),
options: options,
map: map
};
personalizeToast();
displayToast();
handleEvents();
publish(response);
if (options.debug && console) {
console.log(response);
}
return $toastElement;
function escapeHtml(source) {
if (source == null) {
source = '';
}
return source
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/</g, '<')
.replace(/>/g, '>');
}
function personalizeToast() {
setIcon();
setTitle();
setMessage();
setPrompt();
setConfirm();
setCloseButton();
setProgressBar();
setRTL();
setSequence();
setAria();
}
function setAria() {
var ariaValue = '';
switch (map.iconClass) {
case 'toast-success':
case 'toast-info':
ariaValue = 'polite';
break;
default:
ariaValue = 'assertive';
}
$toastElement.attr('aria-live', ariaValue);
}
function handleEvents() {
if (options.closeOnHover) {
$toastElement.hover(stickAround, delayedHideToast);
}
if (!options.onclick && options.tapToDismiss) {
$toastElement.click(hideToast);
}
if (options.closeButton && $closeElement) {
$closeElement.click(function (event) {
if (event.stopPropagation) {
event.stopPropagation();
} else if (event.cancelBubble !== undefined && event.cancelBubble !== true) {
event.cancelBubble = true;
}
if (options.onCloseClick) {
options.onCloseClick(event);
}
hideToast(true);
});
}
if (options.onclick && options.type !== toastType.prompt) {
$toastElement.click(function (event) {
options.onclick(event);
hideToast();
});
}
if (map.type === toastType.prompt) {
$promptOkButton.click(function (event) {
if (options.promptOK) { options.promptOK(event, $promptInput.val()); }
hideToast(true);
});
$promptCancelButton.click(function (event) {
if (options.promptCancel) { options.promptCancel(event); }
hideToast(true);
});
}
if (map.type === toastType.confirm) {
$confirmOkButton.click(function (event) {
if (options.confirmOK) { options.confirmOK(event); }
hideToast(true);
});
$confirmCancelButton.click(function (event) {
if (options.confirmCancel) { options.confirmCancel(event); }
hideToast(true);
});
}
}
function displayToast() {
$toastElement.hide();
$toastElement[options.showMethod](
{duration: options.showDuration, easing: options.showEasing, complete: options.onShown}
);
if (options.timeOut > 0) {
intervalId = setTimeout(hideToast, options.timeOut);
progressBar.maxHideTime = parseFloat(options.timeOut);
progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
if (options.progressBar) {
progressBar.intervalId = setInterval(updateProgress, 10);
}
}
}
function setIcon() {
if (map.iconClass) {
$toastElement.addClass(options.toastClass).addClass(iconClass);
}
}
function setSequence() {
if (options.newestOnTop) {
$container.prepend($toastElement);
} else {
$container.append($toastElement);
}
}
function setTitle() {
if (map.title) {
var suffix = map.title;
if (options.escapeHtml) {
suffix = escapeHtml(map.title);
}
$titleElement.append(suffix).addClass(options.titleClass);
$toastElement.append($titleElement);
}
}
function setMessage() {
if (map.message) {
var suffix = map.message.replace(/\n/g, '<br/>');
if (options.escapeHtml) {
suffix = escapeHtml(map.message);
}
$messageElement.append(suffix).addClass(options.messageClass);
$toastElement.append($messageElement);
}
}
function setPrompt() {
if (map.type === toastType.prompt) {
$promptContainer.append($promptInput);
var $buttonRow = $('<div/>');
$buttonRow.append($promptOkButton);
$buttonRow.append($promptCancelButton);
$promptContainer.append($buttonRow);
$toastElement.append($promptContainer);
$promptInput.val(options.PromptDefaultInput);
}
}
function setConfirm() {
if (map.type === toastType.confirm) {
var $buttonRow = $('<div/>');
$buttonRow.append($confirmOkButton);
$buttonRow.append($confirmCancelButton);
$confirmContainer.append($buttonRow);
$toastElement.append($confirmContainer);
}
}
function setCloseButton() {
if (options.closeButton) {
$closeElement.addClass(options.closeClass).attr('role', 'button');
$toastElement.prepend($closeElement);
}
}
function setProgressBar() {
if (options.progressBar) {
$progressElement.addClass(options.progressClass);
$toastElement.prepend($progressElement);
}
}
function setRTL() {
if (options.rtl) {
$toastElement.addClass('rtl');
}
}
function shouldExit(options, map) {
if (options.preventDuplicates) {
if (map.message === previousToast) {
return true;
} else {
previousToast = map.message;
}
}
return false;
}
function hideToast(override) {
var method = override && options.closeMethod !== false ? options.closeMethod : options.hideMethod;
var duration = override && options.closeDuration !== false ?
options.closeDuration : options.hideDuration;
var easing = override && options.closeEasing !== false ? options.closeEasing : options.hideEasing;
if ($(':focus', $toastElement).length && !override) {
return;
}
clearTimeout(progressBar.intervalId);
return $toastElement[method]({
duration: duration,
easing: easing,
complete: function () {
removeToast($toastElement);
clearTimeout(intervalId);
if (options.onHidden && response.state !== 'hidden') {
options.onHidden();
}
response.state = 'hidden';
response.endTime = new Date();
publish(response);
}
});
}
function delayedHideToast() {
if (options.timeOut > 0 || options.extendedTimeOut > 0) {
intervalId = setTimeout(hideToast, options.extendedTimeOut);
progressBar.maxHideTime = parseFloat(options.extendedTimeOut);
progressBar.hideEta = new Date().getTime() + progressBar.maxHideTime;
}
}
function stickAround() {
clearTimeout(intervalId);
progressBar.hideEta = 0;
$toastElement.stop(true, true)[options.showMethod](
{duration: options.showDuration, easing: options.showEasing}
);
}
function updateProgress() {
var percentage = ((progressBar.hideEta - (new Date().getTime())) / progressBar.maxHideTime) * 100;
$progressElement.width(percentage + '%');
}
}
function getOptions() {
return $.extend({}, getDefaults(), toastr.options);
}
function removeToast($toastElement) {
if (!$container) { $container = getContainer(); }
if ($toastElement.is(':visible')) {
return;
}
$toastElement.remove();
$toastElement = null;
if ($container.children().length === 0) {
$container.remove();
previousToast = undefined;
}
}
})();
});
}(simpleDefine));
} // End initializeToastrLibrary()
// ===== WAZEWRAP LIBRARY (Inline) =====
// Main WazeWrap functionality with all utilities
let wwSettings;
function initializeToastr() {
// Initialize toastr library first (now that jQuery is available)
initializeToastrLibrary();
// Inject CSS
$('head').append($('<style type="text/css">' + TOASTR_CSS + '</style>'));
wazedevtoastr.options = {
target: '#map',
timeOut: 6000,
positionClass: 'toast-top-center-wide',
closeOnHover: false,
closeDuration: 0,
showDuration: 0,
closeButton: true,
progressBar: true,
};
}
function initializeScriptUpdateInterface() {
console.log('Creating script update interface');
injectCSS();
var $section = $('<div>', { style: 'padding:8px 16px', id: 'wmeWWScriptUpdates' });
$section.html(
[
'<div id="WWSU-Container" class="fa" style="position:fixed; top:20%; left:40%; z-index:1000; display:none;">',
'<div id="WWSU-Close" class="fa-close fa-lg"></div>',
'<div class="modal-heading">',
'<h2>Script Updates</h2>',
'<h4><span id="WWSU-updateCount">0</span> of your scripts have updates</h4>',
'</div>',
'<div class="WWSU-updates-wrapper">',
'<div id="WWSU-script-list">',
'</div>',
'<div id="WWSU-script-update-info">',
'</div></div></div>',
].join(' '),
);
$('#WazeMap').append($section.html());
$('#WWSU-Close').click(function () {
$('#WWSU-Container').hide();
});
$(document).on('click', '.WWSU-script-item', function () {
$('.WWSU-script-item').removeClass('WWSU-active');
$(this).addClass('WWSU-active');
});
}
function injectCSS() {
let css = [
'#WWSU-Container { position:relative; background-color:#fbfbfb; width:650px; height:375px; border-radius:8px; padding:20px; box-shadow: 0 22px 84px 0 rgba(87, 99, 125, 0.5); border:1px solid #ededed; }',
'#WWSU-Close { color:#000000; background-color:#ffffff; border:1px solid #ececec; border-radius:10px; height:25px; width:25px; position: absolute; right:14px; top:10px; cursor:pointer; padding: 5px 0px 0px 5px;}',
'#WWSU-Container .modal-heading,.WWSU-updates-wrapper { font-family: "Helvetica Neue", Helvetica, "Open Sans", sans-serif; } ',
'.WWSU-updates-wrapper { height:350px; }',
'#WWSU-script-list { float:left; width:175px; height:100%; padding-right:6px; margin-right:10px; overflow-y: auto; overflow-x: hidden; height:300px; }',
'.WWSU-script-item { text-decoration: none; min-height:40px; display:flex; text-align: center; justify-content: center; align-items: center; margin:3px 3px 10px 3px; background-color:white; border-radius:8px; box-shadow: rgba(0, 0, 0, 0.4) 0px 1px 1px 0.25px; transition:all 200ms ease-in-out; cursor:pointer;}',
'.WWSU-script-item:hover { text-decoration: none; }',
'.WWSU-active { transform: translate3d(5px, 0px, 0px); box-shadow: rgba(0, 0, 0, 0.4) 0px 3px 7px 0px; }',
'#WWSU-script-update-info { width:auto; background-color:white; height:275px; overflow-y:auto; border-radius:8px; box-shadow: rgba(0, 0, 0, 0.09) 0px 6px 7px 0.09px; padding:15px; position:relative;}',
'#WWSU-script-update-info div { display: none;}',
'#WWSU-script-update-info div:target { display: block; }',
].join(' ');
$('<style type="text/css">' + css + '</style>').appendTo('head');
}
function Interface() {
this.ShowScriptUpdate = function (scriptName, version, updateHTML, greasyforkLink = '', forumLink = '') {
let settings;
function loadSettings() {
var loadedSettings = $.parseJSON(localStorage.getItem('WWScriptUpdate'));
var defaultSettings = {
ScriptUpdateHistory: {},
};
settings = loadedSettings ? loadedSettings : defaultSettings;
for (var prop in defaultSettings) {
if (!settings.hasOwnProperty(prop)) settings[prop] = defaultSettings[prop];
}
}
function saveSettings() {
if (localStorage) {
var localsettings = {
ScriptUpdateHistory: settings.ScriptUpdateHistory,
};
localStorage.setItem('WWScriptUpdate', JSON.stringify(localsettings));
}
}
loadSettings();
if (updateHTML && updateHTML.length > 0 && (typeof settings.ScriptUpdateHistory[scriptName] === 'undefined' || settings.ScriptUpdateHistory[scriptName] != version)) {
let currCount = $('.WWSU-script-item').length;
let divID = (scriptName + ('' + version)).toLowerCase().replace(/[^a-z-_0-9]/g, '');
$('#WWSU-script-list').append(`<a href="#${divID}" class="WWSU-script-item ${currCount === 0 ? 'WWSU-active' : ''}">${scriptName}</a>`);
$('#WWSU-updateCount').html(parseInt($('#WWSU-updateCount').html()) + 1);
let install = '',
forum = '';
if (greasyforkLink != '') install = `<a href="${greasyforkLink}" target="_blank">Greasyfork</a>`;
if (forumLink != '') forum = `<a href="${forumLink}" target="_blank">Forum</a>`;
let footer = '';
if (forumLink != '' || greasyforkLink != '') {
footer = `<span class="WWSUFooter" style="margin-bottom:2px; display:block;">${install}${greasyforkLink != '' && forumLink != '' ? ' | ' : ''}${forum}</span>`;
}
$('#WWSU-script-update-info').append(`<div id="${divID}"><span><h3>${version}</h3><br>${updateHTML}</span>${footer}</div>`);
$('#WWSU-Container').show();
if (currCount === 0) $('#WWSU-script-list').find('a')[0].click();
settings.ScriptUpdateHistory[scriptName] = version;
saveSettings();
}
};
}
function Alerts() {
this.success = function (scriptName, message) {
wazedevtoastr.success(message, scriptName);
};
this.info = function (scriptName, message, disableTimeout, disableClickToClose, timeOut) {
let options = {};
if (disableTimeout) options.timeOut = 0;
else if (timeOut) options.timeOut = timeOut;
if (disableClickToClose) options.tapToDismiss = false;
wazedevtoastr.info(message, scriptName, options);
};
this.warning = function (scriptName, message) {
wazedevtoastr.warning(message, scriptName);
};
this.error = function (scriptName, message) {
wazedevtoastr.error(message, scriptName);
};
this.debug = function (scriptName, message) {
wazedevtoastr.debug(message, scriptName);
};
this.prompt = function (scriptName, message, defaultText = '', okFunction, cancelFunction) {
wazedevtoastr.prompt(message, scriptName, { promptOK: okFunction, promptCancel: cancelFunction, PromptDefaultInput: defaultText });
};
this.confirm = function (scriptName, message, okFunction, cancelFunction, okBtnText = 'Ok', cancelBtnText = 'Cancel') {
wazedevtoastr.confirm(message, scriptName, { confirmOK: okFunction, confirmCancel: cancelFunction, ConfirmOkButtonText: okBtnText, ConfirmCancelButtonText: cancelBtnText });
};
this.ScriptUpdateMonitor = class {
#lastVersionChecked = '0';
#scriptName;
#currentVersion;
#downloadUrl;
#metaUrl;
#metaRegExp;
#GM_xmlhttpRequest;
#intervalChecker = null;
constructor(scriptName, currentVersion, downloadUrl, GM_xmlhttpRequest, metaUrl = null, metaRegExp = null) {
this.#scriptName = scriptName;
this.#currentVersion = currentVersion;
this.#downloadUrl = downloadUrl;
this.#GM_xmlhttpRequest = GM_xmlhttpRequest;
this.#metaUrl = metaUrl;
this.#metaRegExp = metaRegExp || /@version\s+(.+)/i;
this.#validateParameters();
}
start(intervalHours = 2, checkImmediately = true) {
if (intervalHours < 1) {
throw new Error('Parameter intervalHours must be at least 1');
}
if (!this.#intervalChecker) {
if (checkImmediately) this.#postAlertIfNewReleaseAvailable();
this.#intervalChecker = setInterval(() => this.#postAlertIfNewReleaseAvailable(), intervalHours * 60 * 60 * 1000);
}
}
stop() {
if (this.#intervalChecker) {
clearInterval(this.#intervalChecker);
this.#intervalChecker = null;
}
}
#validateParameters() {
if (this.#metaUrl) {
if (!this.#metaRegExp) {
throw new Error('metaRegExp must be defined if metaUrl is defined.');
}
if (!(this.#metaRegExp instanceof RegExp)) {
throw new Error('metaUrl must be a regular expression.');
}
} else {
if (!/\.user\.js$/.test(this.#downloadUrl)) {
throw new Error('Invalid downloadUrl paramenter. Must end with ".user.js" [', this.#downloadUrl, ']');
}
this.#metaUrl = this.#downloadUrl.replace(/\.user\.js$/, '.meta.js');
}
}
async #postAlertIfNewReleaseAvailable() {
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay));
let latestVersion;
try {
let tries = 1;
const maxTries = 3;
while (tries <= maxTries) {
latestVersion = await this.#fetchLatestReleaseVersion();
if (latestVersion === 503) {
if (tries < maxTries) {
console.log(`${this.#scriptName}: Checking for latest version again (retry #${tries})`);
await sleep(1000);
} else {
console.error(`${this.#scriptName}: Failed to check latest version #. Too many 503 status codes returned.`);
}
tries += 1;
} else if (latestVersion.status) {
console.error(`${this.#scriptName}: Error while checking for latest version.`, latestVersion);
return;
} else {
break;
}
}
} catch (ex) {
console.error(`${this.#scriptName}: Error while checking for latest version.`, ex);
return;
}
if (latestVersion > this.#currentVersion && latestVersion > (this.#lastVersionChecked || '0')) {
this.#lastVersionChecked = latestVersion;
this.#postNewVersionAlert(latestVersion);
}
}
#postNewVersionAlert(newVersion) {
const message = `<a href="${this.#downloadUrl}" target = "_blank">Version ${newVersion}</a> is available.<br>Update now to get the latest features and fixes.`;
WazeWrap.Alerts.info(this.#scriptName, message, true, false);
}
#fetchLatestReleaseVersion() {
const metaUrl = this.#metaUrl;
const metaRegExp = this.#metaRegExp;
return new Promise((resolve, reject) => {
this.#GM_xmlhttpRequest({
nocache: true,
revalidate: true,
url: metaUrl,
onload(res) {
if (res.status === 503) {
resolve(503);
} else if (res.status === 200) {
const versionMatch = res.responseText.match(metaRegExp);
if (versionMatch?.length !== 2) {
throw new Error(`Invalid RegExp expression (${metaRegExp}) or version # could not be found at this URL: ${metaUrl}`);
}
resolve(res.responseText.match(metaRegExp)[1]);
} else {
resolve(res);
}
},
onerror(res) {
reject(res);
},
});
});
}
};
}
// ===== INITIALIZATION FUNCTIONS =====
// All boot and initialization logic grouped together
function initBootloader() {
const sandboxed = typeof unsafeWindow !== 'undefined';
const pageWindow = sandboxed ? unsafeWindow : window;
// Duplicate loading prevention: Check if WazeWrap is already initializing or ready
if (pageWindow.WazeWrap && pageWindow.WazeWrap.Repo) {
console.log('WazeWrap already loading or initialized, skipping init');
WazeWrap = pageWindow.WazeWrap;
if (sandboxed) window.WazeWrap = pageWindow.WazeWrap;
return;
}
// Create WazeWrap object and expose to both contexts
if (!pageWindow.WazeWrap) pageWindow.WazeWrap = {};
WazeWrap = pageWindow.WazeWrap;
if (sandboxed) window.WazeWrap = pageWindow.WazeWrap;
// Set loading flag IMMEDIATELY (signals other loaders that WazeWrap is initializing)
// Repo = initialization flag (prevents duplicate init), Ready = completion flag
pageWindow.WazeWrap.Repo = 'self-contained';
pageWindow.WazeWrap.Ready = false;
}
async function initWazeWrap() {
console.log('Initializing WazeWrap (Self-Contained)...');
try {
initializeScriptUpdateInterface();
} catch (e) {
console.warn('Error initializing script update interface:', e);
}
try {
initializeToastr();
} catch (e) {
console.warn('Error loading toastr:', e);
}
WazeWrap.Alerts = new Alerts();
WazeWrap.Interface = new Interface();
// Signal full initialization complete - other scripts can now use WazeWrap
WazeWrap.Ready = true;
console.log('WazeWrap initialized successfully (Self-Contained)');
}
function bootstrapInit(tries = 1) {
if (typeof $ !== 'undefined') {
initBootloader();
initWazeWrap();
} else if (tries < 1000) {
setTimeout(function () {
bootstrapInit(tries++);
}, 100);
} else {
console.error('WazeWrap failed to load - jQuery not available after 100 seconds');
}
}
// ===== START BOOT SEQUENCE =====
bootstrapInit();
})();