OpenStreetMap & OpenGeofiction Show Nodes

Shows all nearby nodes in query results and displays constituent nodes if a way is selected

// ==UserScript==
// @name        OpenStreetMap & OpenGeofiction Show Nodes
// @namespace   Violentmonkey Scripts
// @match       *://www.openstreetmap.org/*
// @match       *://www.opengeofiction.net/*
// @match       *://opengeofiction.net/*
// @version     1.1
// @author      CyrilSLi
// @description Shows all nearby nodes in query results and displays constituent nodes if a way is selected
// @license     MIT
// @grant       unsafeWindow
// @require     https://update.greasyfork.org/scripts/533461/1574689/Get%20OpenStreetMap%20Leaflet%20object.js
// ==/UserScript==

const featureStyle = {
    color: "#FF6200",
    weight: 4,
    opacity: 1,
    fillOpacity: 0.5,
    interactive: false
};
function showQueryNodes(elements) {
    document.getElementById("sidebar_content").insertAdjacentHTML("beforeend", `
        <div id="query-allnodes" class="query-results">
            <h3>All Nodes</h3>
            <div class="mx-n3">
                <ul class="query-results-list list-group list-group-flush" id="allnodes"></ul>
            </div>
        </div>
    `);
    document.getElementById("query-allnodes").style.display = "block";
    const allNodes = document.getElementById("allnodes");
    const nodeMarkers = {};
    elements.forEach((el) => {
        if (el.type === "node") {
            nodeMarkers[el.id] = L.circleMarker([el.lat, el.lon], featureStyle);
            allNodes.insertAdjacentHTML("beforeend", `<li class="list-group-item list-group-item-action">Node <a id="allnodes-${el.id}" class="stretched-link" href="/node/${el.id}">#${el.id}</a></li>`);
            const nodeEl = document.getElementById(`allnodes-${el.id}`);
            nodeEl.addEventListener("mouseenter", (ev) => {
                nodeMarkers[el.id].addTo(userscriptMap);
            });
            nodeEl.addEventListener("mouseleave", (ev) => {
                nodeMarkers[el.id].remove();
            });
            nodeEl.addEventListener("click", (ev) => {
                nodeMarkers[el.id].remove();
            });
        }
    });
    if (!allNodes.innerHTML) {
        allNodes.innerHTML = '<li class="list-group-item">No nodes found</li>';
    }
}
function showWayNodes(elements) {
    const nodeMarkers = [];
    elements.forEach((el) => {
        if (el.type === "node") {
            nodeMarkers.push(L.circleMarker([el.lat, el.lon], featureStyle).addTo(userscriptMap));
        }
    });
    const sidebar = document.getElementById("sidebar_content");
    const observer = new MutationObserver((muts) => {
        if (getComputedStyle(sidebar).display === "none" || !sidebar.getElementsByTagName("h2")[0].textContent.trim().toLowerCase().startsWith("way")) {
            nodeMarkers.forEach((el) => {
                el.remove();
            });
            observer.disconnect();
        }
    });
    observer.observe(document.getElementById("content"), {
        subtree: true,
        childList: true
    });
}

if (window.location.href.includes("openstreetmap")) {
    unsafeWindow.nativeFetch = unsafeWindow.fetch;
    unsafeWindow.fetch = async function(request, headers) {
        const res = await unsafeWindow.nativeFetch(request, headers);
        if (request.includes("query.openstreetmap.org/query-features") &&
            decodeURIComponent(headers.body.toString()).includes("node(around:")) {
            showQueryNodes((await res.clone().json()).elements);
        } else if (request.includes("/api/0.6/way/")) {
            showWayNodes((await res.clone().json()).elements);
        }
        return res;
    }
} else if (window.location.href.includes("opengeofiction")) {
    $(document).ajaxSuccess((ev, xhr, settings) => {
        if (settings.url.includes("overpass.opengeofiction.net/api/interpreter") &&
            decodeURIComponent(settings.data).includes("node(around:")) {
            showQueryNodes(xhr.responseJSON.elements);
        } else if (settings.url.includes("/api/0.6/way/")) {
            showWayNodes([...(xhr.responseXML).firstChild.getElementsByTagName("node")].map((el) => ({
                type: "node",
                lat: el.getAttribute("lat"),
                lon: el.getAttribute("lon")
            })));
        }
    });
}