WME Edition Helper

Monitor editions count and shows a timer for next suggested save action

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name             WME Edition Helper
// @name:es          WME Ayudante de edición
// @description      Monitor editions count and shows a timer for next suggested save action
// @description:es   Monitorea el contador de ediciones y muestra un temporizador para la siguiente acción de guardado sugerida

// @author           EdwardNavarro
// @namespace        https://greasyfork.org/en/users/670818-edward-navarro
// @version          2022.11.22.01
// @license          GNU GPLv3

// @include          /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/
// @exclude          https://www.waze.com/user/*
// @exclude          https://www.waze.com/*/user/*
// @require          https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js
// @icon             https://www.edwardnavarro.com/cdn/wme/wme_eh_icon_32x32.svg
// @connect          www.waze.com
// @grant            GM_xmlhttpRequest
// @grant            GM_addElement

// @contributionURL  https://github.com/WazeDev/Thank-The-Authors
// ==/UserScript==

/* global W */
/* global toastr */
/* global $ */

/**
* ===============================================
*  This script is based on the following scripts:
*  - "Waze Edit Count Monitor" (by MapOMatic)
*  - "Waze Edit & UR Count Monitor" (by Crotalo)
* ===============================================
*/

// This function is injected into the page to allow it to run in the page's context.
function wmeEH_Injected() {
    'use strict';

    const addonVersion = '2021.10.28.03';

    let _toastr_settings = {
        timeBeforeSaving: 70,
        remindAtEditCount: 30,
        warnAtEditCount: 45,
        wasReminded: false,
        wasWarned: false
    };

    let debugLevel = 0;
    let userName = '';
    let lastTodayEditCount = 0;
    let lastYesterdayEditCount = 0;
    let lastDayBeforeEditCount = 0;
    let savesWithoutIncrease = 0;
    let showURs, showMPs;
    let totalSeconds = 0;
    let button_container, button_content_wrap, button_item_container, button_item_icon, button_item_link, button_item_content, progress_bar_wrap, progress_bar_fill, saved_timer, timer_interval;
    let tooltipText = '<b>Ediciones Diarias</b><br><small>(Clic para ver el perfil)<small>';

    // load default addon settings
    if (!localStorage.WMEEditionHelperScript) {
        let options = [null,_toastr_settings.timeBeforeSaving,_toastr_settings.remindAtEditCount,_toastr_settings.warnAtEditCount,false,false];
        localStorage.WMEEditionHelperScript = JSON.stringify(options);
    }

    function log(message, level, prefix = 'LOG', bgColor = 'darkslategrey', textColor = 'white') {
        if (message && level <= debugLevel) {
            console.log('%c%s%s', `background:${bgColor};color:${textColor};padding:5px 10px;`, `[${prefix}] WME Edition Helper >>`, message);
        }
    }

    function checkEditCount() {
        window.postMessage(JSON.stringify(['wmeEHGetCounts',userName]),'*');
        _toastr_settings.wasReminded = false;
        _toastr_settings.wasWarned = false;
        toastr.remove();
    }

    function getChangedObjectCount() {
        let count = W.changesLogController._changesLogViewModel.attributes.actionsCount;
        return count;
    }

    function pad(val) {
        let valString = val + "";
        if (valString.length < 2) {
            return "0" + valString;
        } else {
            return valString;
        }
    }

    function setTime() {
        ++totalSeconds;
        let hours = parseInt(totalSeconds / 60 / 60) % 60;
        $('#saved-timer').html(`${(hours > 0) ? `${hours}:` : ''}${pad(parseInt(totalSeconds / 60) % 60)}:${pad(totalSeconds % 60)}`);
    }

    function runTimer() {
        timer_interval = setInterval(setTime, 1000);
    }

    function updateEditCount(todayEditCount = 0, yesterdayEditCount = 0, dayBeforeEditCount = 0, noIncrement) {
        let textColor;
        let bgColor;
        let tooltipTextColor;

        // Add the counter div if it doesn't exist.
        if ($('#eh-button').length === 0) {
            button_container = $('<div>', { id: 'eh-button' });
            button_content_wrap = $('<div>', { class: 'toolbar-button toolbar-button-with-icon' });
            button_item_link = $('<a>', { href: 'https://www.waze.com/user/editor/' + userName.toLowerCase(), target: '_blank', style:'text-decoration: none;', 'data-original-title': tooltipText });
            button_item_container = $('<div>', { class: 'item-container' });
            button_item_icon = $('<div>', { class: 'toolbar-icon-eh w-icon w-icon-pencil' });
            button_item_content = $('<div>', { style: 'margin:5px 0 0 5px; line-height: 1;' });

            progress_bar_wrap = $('<div>', { style: 'width: 100%; height: 5px; background-color: #d7dadc; border:1px #fff solid; box-sizing: content-box;' });
            progress_bar_fill = $('<div>', { class: 'progress', style: 'width: 0%; height: 5px; animation-fill-mode: both; animation-name: progressBar; animation-duration:' + _toastr_settings.timeBeforeSaving + 's; animation-timing-function: ease-in-out;' });

            saved_timer = $('<div>', { id: 'saved-timer', style: 'font-size:8px; line-height:1; text-align:right; color:darkgray;' });

            button_container.append(button_content_wrap);
            button_content_wrap.append(button_item_link);
            button_item_link.append(button_item_container);
            button_item_container.append(button_item_icon);
            button_item_container.append(button_item_content);

            button_item_content.append(progress_bar_wrap);
            button_item_content.append(saved_timer);
            progress_bar_wrap.append(progress_bar_fill);

            $('#edit-buttons').prepend(button_container);

            button_item_link.tooltip({
                placement: 'auto top',
                delay: { show: 100, hide: 100 },
                html: true,
                template: '<div class="tooltip" role="tooltip" style="opacity:0.95"><div class="tooltip-arrow"></div><div class="my-tooltip-header" style="display:block;"><b></b></div><div class="my-tooltip-body tooltip-inner" style="display:block;"></div></div>'
            });
        }

        log(`EDIT COUNTS -> Today: ${todayEditCount}, Yesterday: ${yesterdayEditCount}, Day before: ${dayBeforeEditCount}`, 1, 'INFO', 'purple');

        if (lastTodayEditCount !== todayEditCount) {
            savesWithoutIncrease = 0;
        } else {
            if (!noIncrement) savesWithoutIncrease += 1;
        }

        switch (savesWithoutIncrease) {
            case 0:
            case 1:
                textColor = '#354148';
                bgColor = '';
                tooltipTextColor = 'white';
                break;
            case 2:
                textColor = '#354148';
                bgColor = 'yellow';
                tooltipTextColor = 'black';
                break;
            default:
                textColor = 'white';
                bgColor = 'red';
                tooltipTextColor = 'white';
        }

        button_container.css('background-color', bgColor);
        button_item_icon.css('color', textColor);
        button_item_content.css('color', textColor).html(`Ediciones: ${todayEditCount}`);
        button_item_content.append(progress_bar_wrap);
        button_item_content.append(saved_timer);

        let dayBeforeEditCountText = `<hr style="border:0 none; border-bottom:1px #999 solid; margin:5px 0;"/><div class="days-group"><div class="day-1"><h3>Hoy</h3><span>${todayEditCount}</span></div><div class="day-2"><h3>Ayer</h3><span>${yesterdayEditCount}</span></div><div class="day-3"><h3>Antier</h3><span>${dayBeforeEditCount}</span></div></div>`;
        let warningText = (savesWithoutIncrease > 0) ? `<div style="font-size:13px;border-radius:5px;padding:5px;margin-top:5px;color:${tooltipTextColor};background-color:${bgColor};"><b>${savesWithoutIncrease}</b> salvadas/guardadas consecutivas sin incremento en el contador.<br><span style="font-weight:bold;font-size:16px;">¿Estás estrangulado?<br>🤔👀<span></div>` : '';
        button_item_link.attr('data-original-title', tooltipText + dayBeforeEditCountText + warningText);

        lastTodayEditCount = todayEditCount;
        lastYesterdayEditCount = yesterdayEditCount;
        lastDayBeforeEditCount = dayBeforeEditCount;
        totalSeconds = 0;

        clearTimeout(timer_interval);
        runTimer();
    }

    function receiveMessage(event) {
        let msg;

        try {
            msg = JSON.parse(event.data);
        } catch (err) {
            // Do nothing
        }

        if (msg && msg[0] === 'wmeEHUpdateUi') {
            let todayEditCount = msg[1][0];
            let yesterdayEditCount = msg[1][1];
            let dayBeforeEditCount = msg[1][2];
            updateEditCount(todayEditCount, yesterdayEditCount, dayBeforeEditCount);
        }
    }

    function checkChangedObjectCount() {
        let objectEditCount = getChangedObjectCount();

        if (objectEditCount >= _toastr_settings.warnAtEditCount && !_toastr_settings.wasWarned) {
            toastr.remove();
            toastr.error('<span style="font-size:16px;">Has editado al menos <b>' + _toastr_settings.warnAtEditCount + '</b> objetos.</span><br><br> Deberías considerar guardar pronto. Si obtienes un error al guardar, necesitarás deshacer algunos cambios/acciones e intentar nuevamente.', 'Edition Helper:', {timeOut: 25000});
            _toastr_settings.wasWarned = true;
            //log('WARMED', 0, 'ALERT', 'tomato')
        } else if (objectEditCount >= _toastr_settings.remindAtEditCount && !_toastr_settings.wasReminded) {
            toastr.remove();
            toastr.warning('<span style="font-size:16px;">Has editado al menos <b>' + _toastr_settings.remindAtEditCount + '</b> objetos.</span><br><br> Deberías considerar guardar pronto.', 'Edition Helper:', {timeOut: 15000});
            _toastr_settings.wasReminded = true;
            //log('REMINDED', 0, 'ALERT', 'orange')
        } else if (objectEditCount < _toastr_settings.remindAtEditCount) {
            _toastr_settings.wasWarned = false;
            _toastr_settings.wasReminded = false;
            toastr.remove();
            //log('REMOVED', 0, 'ALERT', 'sienna')
        }
    }

    function errorHandler(callback) {
        try {
            callback();
        } catch (e) {
            console.error('%c%s%s', 'background:darkred;color:white;padding:5px 10px;', '[ERROR] WME Edition Helper >>', e);
        }
    }

    /* helper functions */
    function getElementsByClassName(classname, node) {
        if(!node) node = document.getElementsByTagName("body")[0];
        let a = [];
        let re = new RegExp('\\b' + classname + '\\b');
        let els = node.getElementsByTagName("*");
        for (let i=0, j=els.length; i<j; i++) {
            if (re.test(els[i].className)) a.push(els[i]);
        }
        return a;
    }

    function getId(node) {
        return document.getElementById(node);
    }

    function updateAddonSettings(event) {
        _toastr_settings.timeBeforeSaving = getId('_ehSavingWaitTime').value;
        _toastr_settings.remindAtEditCount = getId('_ehRememberAfter').value;
        _toastr_settings.warnAtEditCount = getId('_ehAlertAfter').value;
        showURs = getId('_ehShowURs').checked;
        showMPs = getId('_ehShowMPs').checked;

        $('.progress').css('animation-duration', `${getId('_ehSavingWaitTime').value}s`);
    }

    function init() {
        userName = W.loginManager.user.userName;
        window.addEventListener('message', receiveMessage);

        // restore saved settings
        if (localStorage.WMEEditionHelperScript) {
            let options = JSON.parse(localStorage.WMEEditionHelperScript);

            _toastr_settings.timeBeforeSaving = options[1];
            _toastr_settings.remindAtEditCount = options[2];
            _toastr_settings.warnAtEditCount = options[3];
            showURs = options[4];
            showMPs = options[5];
        }

        // check if sidebar is hidden
        let sidebar = getId('sidebar');
        if (sidebar.style.display == 'none') {
            log("Not logged in yet - will initialise at login", 0, 'WARN', 'orange');
            W.loginManager.events.register("login", null, init);
            return;
        }

        // check that user-info section is defined
        let userTabs = getId('user-info');
        if (userTabs === null) {
            log("Editor not initialised yet - trying again in a bit...", 0, 'WARN', 'orange');
            setTimeout(init, 789);
            return;
        }

        // add styleshets and styles to head section
        $('head').append(
            $('<link/>', {
                rel: 'stylesheet',
                type: 'text/css',
                href: 'https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.css'
            }),
            $('<style type="text/css">'
              + '#toast-container {position: absolute;} '
              + '#toast-container > div {opacity: 0.95;} '
              + '.toast-top-center {top: 30px;} '
              + '#edit-buttons #eh-button { display:flex; } '
              + '.toolbar .toolbar-icon-eh { color: #484848; font-size: 24px; margin: 8px 0; position: relative; text-align: center; width: 24px; } '
              + '.progress { background-color: red; animation-fill-mode:both; } '
              + '@keyframes progressBar { 0% { width: 0; } 99% { background-color: red; } 100% { width: 100%; background-color: green; } } '
              + '.days-group { width:100%; display:flex; justify-content:space-between; align-item:center; } '
              + '.days-group div { width:30%; padding:5px; background-color:darkgray; color:white; display:flex; flex-direction:column; align-item:center; border-radius:5px; } '
              + '.days-group div h3 { font-size:12px; font-weight:bold; line-height:1; margin:5px 0; } '
              + '.days-group div span { font-size:14px; font-weight:bold; } '
              + '.days-group .day-1 { background-color:darkcyan; } '
              + '.days-group .day-2 { background-color:darkgreen; } '
              + '.days-group .day-3 { background-color:darkolivegreen; } '
              + '</style>')
        );

        // add js libraries and register events
        $.getScript('https://cdnjs.cloudflare.com/ajax/libs/toastr.js/2.1.4/toastr.min.js', function() {
            toastr.options = {
                target:'#map',
                showDuration: 300,
                hideDuration: 1000,
                timeOut: 10000,
                extendedTimeOut: 1000,
                positionClass: 'toast-top-right',
                closeOnHover: false,
                closeButton: true,
                newestOnTop: true,
                progressBar: true,
                showEasing: 'swing',
                hideEasing: 'linear',
                showMethod: 'fadeIn',
                hideMethod: 'fadeOut',
            };

            W.model.actionManager.events.register('afterclearactions', null, () => errorHandler(checkEditCount));
            W.model.actionManager.events.register('afteraction', null, () => errorHandler(checkChangedObjectCount));
            W.model.actionManager.events.register('afterundoaction', null, () => errorHandler(checkChangedObjectCount));

            // Update the edit count first time.
            checkEditCount();
            toastr.success("Edition Helper Inicializado!");
            log('Initialized!', 0, 'SUCCESS', 'green');
        });

        // add new box to left of the map
        let navTabs = getElementsByClassName('nav-tabs', userTabs)[0];
        let tabContent = getElementsByClassName('tab-content', userTabs)[0];
        let addon = document.createElement('section');
        addon.id = "edition-helper-addon";

          // advanced options
        let section = document.createElement('p');
        section.style.paddingTop = "0px";
        section.className = 'checkbox';
        section.id = 'advancedOptions';
        section.innerHTML = '<h4><span class="fa fa-pencil" title="Edition Helper"></span> Edition Helper</h4><div style="margin:5px 0 10px 0;"><b>Configuración</b></div>'
            + '<label for="_ehSavingWaitTime">Tiempo de espera para guardar</label><br>'
            + '<input type="number" min="1" max="3600" size="4" id="_ehSavingWaitTime" style="margin: 0 0 20px 0" /> segundos'
            + '<br>'
            + '<label for="_ehRememberAfter">Recomendar guardar despues de</label><br>'
            + '<input type="number" min="1" max="5000" size="4" id="_ehRememberAfter" style="margin: 0 0 20px 0" /> cambios'
            + '<br>'
            + '<label for="_ehAlertAfter">Alertar guardar despues de</label><br>'
            + '<input type="number" min="1" max="5000" size="4" id="_ehAlertAfter" style="margin: 0 0 20px 0" /> cambios'
            + '<br>'
            + '<label><input type="checkbox" id="_ehShowURs" /> Mostrar UR\'s gestionadas</label><br>'
            + '<label><input type="checkbox" id="_ehShowMPs" /> Mostrar MP\'s gestionadas</label><br>'
        ;
        addon.appendChild(section);

        // Addon legal and credits
        addon.innerHTML += '<hr style="border:0 none; border-bottom:1px #ccc solid;">'
            + '<small><b><a href="https://greasyfork.org/en/scripts/434355-wme-edition-helper" target="_blank"><u>'
            + 'WME Edition Helper</u></a></b> &nbsp; v' + addonVersion + '</small>';

        // Add tab button and panel content
        let newtab = document.createElement('li');
        newtab.innerHTML = '<a href="#sidepanel-edition-helper" data-toggle="tab"><span class="fa fa-pencil" title="Edition Helper"></span> EH</a>';
        navTabs.appendChild(newtab);

        addon.id = "sidepanel-edition-helper";
        addon.className = "tab-pane";
        tabContent.appendChild(addon);

        getId('_ehSavingWaitTime').onchange = updateAddonSettings;
        getId('_ehRememberAfter').onchange = updateAddonSettings;
        getId('_ehAlertAfter').onchange = updateAddonSettings;
        getId('_ehShowURs').onclick = updateAddonSettings;
        getId('_ehShowMPs').onclick = updateAddonSettings;

        // restore saved settings
        if (localStorage.WMEEditionHelperScript) {
            let options = JSON.parse(localStorage.WMEEditionHelperScript);

            getId('_ehSavingWaitTime').value = options[1];
            getId('_ehRememberAfter').value = options[2];
            getId('_ehAlertAfter').value = options[3];
            getId('_ehShowURs').checked = options[4];
            getId('_ehShowMPs').checked = options[5];
        }

        // overload the WME exit function
        const saveEditionHelperOptions = function() {
            if (localStorage) {
                let options = [];

                // preserve previous options which may get lost after logout
                if (localStorage.WMEEditionHelperScript) {
                    options = JSON.parse(localStorage.WMEEditionHelperScript);
                }

                options[1] = getId('_ehSavingWaitTime').value;
                options[2] = getId('_ehRememberAfter').value;
                options[3] = getId('_ehAlertAfter').value;
                options[4] = getId('_ehShowURs').checked;
                options[5] = getId('_ehShowMPs').checked;

                localStorage.WMEEditionHelperScript = JSON.stringify(options);
            }
        }
        window.addEventListener("beforeunload", saveEditionHelperOptions, false);
    }

    function bootstrap() {
        if (W &&
            W.loginManager &&
            W.loginManager.events &&
            W.loginManager.events.register &&
            W.map &&
            W.loginManager.user) {
            log('Initializing...', 0);
            init();
        } else {
            log('Bootstrap failed. Trying again...', 0, 'ERROR', 'darkred');
            window.setTimeout(function () {
                bootstrap();
            }, 1000);
        }
    }

    bootstrap();
}


// Code that is NOT injected into the page.
// Note that jQuery may or may not be available, so don't rely on it in this part of the script.
(function(){
    'use strict';

    function getEditorProfileFromSource(source) {
        let match = source.match(/gon.data=({.*?});gon.env=/i);
        return JSON.parse(match[1]);
    }

    function getEditCountFromProfile(profile) {
        let editingActivity = profile.editingActivity;
        return editingActivity[editingActivity.length-1];
    }

    function getEditCountByTypeFromProfile(profile, type) {
        let edits = profile.editsByType.find(edits => edits.key === type);
        return edits ? edits.value : -1;
    }

    function getEditCountByDayFromProfile(profile, day) {
        let editingActivity = profile.editingActivity;
        return editingActivity[editingActivity.length-day];
    }

    // Handle messages from the page.
    function receiveMessage(event) {
        let msg;

        try {
            msg = JSON.parse(event.data);
        }
        catch (err) {
            // Ignore errors
        }

        if (msg && msg[0] === 'wmeEHGetCounts') {
            let userName = msg[1];
            GM_xmlhttpRequest({
                method: 'GET',
                url: `https://www.waze.com/user/editor/${userName}`,
                onload: function(res) {
                    let profile = getEditorProfileFromSource(res.responseText);
                    window.postMessage(JSON.stringify(['wmeEHUpdateUi',[getEditCountFromProfile(profile), getEditCountByDayFromProfile(profile,2), getEditCountByDayFromProfile(profile,3)]]),'*');
                }
            });
        }
    }

    let wmeEH_Injected_script = GM_addElement('script', {
        textContent: "" + wmeEH_Injected.toString() + " \n wmeEH_Injected();"
    });

    // Listen for events coming from the page script.
    window.addEventListener('message', receiveMessage);
})();