Map-Making Shortcuts

use shortcut to help you mapping on map-making app

目前為 2025-05-30 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Map-Making Shortcuts
// @namespace    https://greasyfork.org/users/1179204
// @description  use shortcut to help you mapping on map-making app
// @version      1.3.5
// @license      BSD
// @author       KaKa
// @match        *://map-making.app/maps/*
// @grant        GM_addStyle
// @icon         https://www.svgrepo.com/show/521871/switch.svg
// ==/UserScript==
(function(){
    /* ------------------------------------------------------------------------------- */
    /* ----- KEYBOARD SHORTCUTS (MUST Refresh PAGE FOR CHANGES TO TAKE EFFECT) -------- */
    /* ------------------------------------------------------------------------------- */


    const KEYBOARD_SHORTCUTS = {
        // Single key
        switchLoc: 'Q',

        rewindLoc: 'E',

        deleteLoc: 'C',

        closeLoc: 'V',

        copyLoc:'G',

        hideElement:'H',

        deSelectAll:'Z',

        pruneDuplicates:'P',


        // SHIFT key is need
        deleteTags:'B',

        resetGulf:'M',

        classicMap:'N',

        findLinkPanos:'K',

        exportAsCsv: 'D',
    };


    /* ############################################################################### */
    /* ##### DON'T MODIFY ANYTHING BELOW HERE UNLESS YOU KNOW WHAT YOU ARE DOING ##### */
    /* ############################################################################### */


    let selections,currentIndex
    let mapListener
    let isDrawing, isShift,isApplied,isASV,isYSV,isHidden
    let startX, startY, endX, endY
    let selectionBox
    let style
    let actionIndex=-1

    function exportAsCsv(locs){
        const csvContent = jsonToCSV(locs);
        downloadCSV(csvContent);
    }

    function downloadCSV(csvContent, fileName = "output.csv") {
        const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
        const link = document.createElement('a');
        if (link.download !== undefined) {
            const url = URL.createObjectURL(blob);
            link.setAttribute('href', url);
            link.setAttribute('download', fileName);
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }
    }

    function getTagsForRow(item, maxTags) {
        const tags = item.tags || [];
        return Array.from({ length: maxTags }, (_, index) => tags[index] || '');
    }

    function getFormattedDate(dateStr) {
        if (!dateStr) return '';
        const date = new Date(dateStr);
        if (isNaN(date.getTime())) return '';
        const year = date.getFullYear();
        const month = String(date.getMonth() + 1).padStart(2, '0');
        return `${year}-${month}`;
    }

    function getMaxTagCount(jsonData) {
        let maxTags = 0;
        jsonData.forEach(item => {
            if (item.tags && item.tags.length > maxTags) {
                maxTags = item.tags.length;
            }
        });
        return maxTags;
    }

    function jsonToCSV(jsonData) {
        const maxTags = getMaxTagCount(jsonData);
        const tagHeaders = Array.from({ length: maxTags }, (_, i) => `tag${i + 1}`);
        const headers = ["lat", "lng", "panoId", "heading", "pitch", "zoom", "date", ...tagHeaders];
        const rows = jsonData.map(item => {
            const lat = item.location.lat|| '';
            const lng = item.location.lng|| '';
            const panoId = item.panoId|| '';
            const heading = item.heading|| '';
            const pitch = item.pitch|| '';
            const zoom = item.zoom || '';
            const date = getFormattedDate(item.panoDate)||'';
            const tags = getTagsForRow(item, maxTags);

            return [
                lat,
                lng,
                panoId,
                heading,
                pitch,
                zoom,
                date,
                ...tags
            ];
        });

        const csvContent = [headers, ...rows].map(row => row.join(",")).join("\n");
        return csvContent;
    }

    function switchLoc(locs) {
        if(editor.currentLocation) editor.closeLocation(editor.currentLocation.updatedProps)
        if (!currentIndex) {
            currentIndex =1;
        } else {
            currentIndex +=1
            if (currentIndex>locs.length){
                currentIndex=1
            }
        }
        editor.openLocation(locs[currentIndex-1]);
        focusOnLoc(locs[currentIndex-1])
    }

    function rewindLoc(locs) {
        const activeSelections=editor.selections.length > 0 ? editor.selections.flatMap(selection => selection.locations) : locations
        if(editor.currentLocation) editor.closeLocation(editor.currentLocation.updatedProps)
        if (!currentIndex) {
            currentIndex =1;
        }
        else {
            currentIndex -=1
            if (currentIndex<1) currentIndex=activeSelections.length
        }
        editor.openLocation(locs[currentIndex-1]);
        focusOnLoc(locs[currentIndex-1])
    }

    function focusOnLoc(loc){
        map.setCenter(loc.location)
        map.setZoom(17)
    }

    function deleteLoc(loc){
        editor.closeAndDeleteLocation(loc)
    }

    function copyLoc(){
        editor.addLocation(editor.currentLocation.updatedProps)
    }


    function setZoom(z){
        if(z<0)z=0
        if(z>4)z=4
        const svControl=unsafeWindow.streetView
        svControl.setZoom(z)

    }

    function getTag(index){
        const sortedTags = Object.keys(editor.tags).sort((a, b) => {
            return editor.tags[a].order - editor.tags[b].order;
        });
        const currentTags= editor.currentLocation.updatedProps.tags
        while(currentTags.includes(sortedTags[index-1])){
            index++
        }
        return sortedTags[index-1]
    }

    async function tagLoc(index){
        if(editor.currentLocation){
            const tag=getTag(index)
            if(tag) await addTag(tag)
        }
    }


    async function addTag(tag){
        const isReview= document.querySelector('.review-header')
        const prevBtn = document.querySelector('[data-qa="review-prev"]');
        const nextBtn = document.querySelector('[data-qa="review-next"]');
        const currentLoc=editor.currentLocation.location
        const editLoc=editor.currentLocation.updatedProps
        if (isReview) {
            editor.currentLocation.updatedProps.tags.push(tag)
            setTimeout(() => {
                if (nextBtn) nextBtn.click();
            }, 100);
            setTimeout(() => {
                if (prevBtn)prevBtn.click()
            }, 200);
        }
        else{
            await editor.closeAndDeleteLocation(editor.currentLocation.location)
            editLoc.tags.push(tag)
            await editor.addAndOpenLocation(editLoc)
        }
    }

    function deleteTags() {
        let selections = editor.selections;
        while (selections.length > 0) {
            const item = selections[0];
            const tag = JSON.parse(item.key);
            const tagName = tag.tagName;
            const locations = item.locations;

            editor.deleteTag(tagName, locations);

            selections = editor.selections;
        }
    }

    function customLayer(name,tileUrl,maxZoom,minZoom){
        return new google.maps.ImageMapType({
            getTileUrl: function(coord, zoom) {
                return tileUrl
                    .replace('{z}', zoom)
                    .replace('{x}', coord.x)
                    .replace('{y}', coord.y);
            },
            tileSize: new google.maps.Size(256, 256),
            name: name,
            maxZoom:maxZoom,
            minZoom:minZoom||1
        });
    }

    function classicMap(){
        var tileUrl = `https://mapsresources-pa.googleapis.com/v1/tiles?map_id=61449c20e7fc278b&version=15797339025669136861&pb=!1m7!8m6!1m3!1i{z}!2i{x}!3i{y}!2i9!3x1!2m2!1e0!2sm!3m7!2sen!3sus!5e1105!12m1!1e3!12m1!1e2!4e0!5m5!1e0!8m2!1e1!1e1!8i47083502!6m6!1e12!2i2!11e0!39b0!44e0!50e0`
        const tileLayer=customLayer('google_labels_reest',tileUrl,20)
        map.mapTypes.stack.layers[0]=tileLayer
        map.setMapTypeId('stack')
    }

    function resetGulf(){
        var tileUrl = `https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.e%3Ag%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aon%2Cs.e%3Al%7Cp.v%3Aon%215m1%215f1.350000023841858`
        if(JSON.parse(localStorage.getItem('mapBoldCountryBorders')))tileUrl=`https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.t%3A17%7Cs.e%3Ag.s%7Cp.w%3A2%7Cp.c%3A%23000000%2Cs.e%3Ag%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aon%2Cs.e%3Al%7Cp.v%3Aon%215m1%215f1.350000023841858`
        if(JSON.parse(localStorage.getItem('mapBoldSubdivisionBorders')))tileUrl=`https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.t%3A18%7Cs.e%3Ag.s%7Cp.w%3A3%2Cs.e%3Al%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aoff%215m1%215f1.350000023841858`
        if(JSON.parse(localStorage.getItem('mapBoldSubdivisionBorders'))&&JSON.parse(localStorage.getItem('mapBoldCountryBorders')))tileUrl=`https://maps.googleapis.com/maps/vt?pb=%211m5%211m4%211i{z}%212i{x}%213i{y}%214i256%212m2%211e0%212sm%213m17%212sen%213smx%215e18%2112m4%211e68%212m2%211sset%212sRoadmap%2112m3%211e37%212m1%211ssmartmaps%2112m4%211e26%212m2%211sstyles%212ss.t%3A17%7Cs.e%3Ag.s%7Cp.w%3A2%7Cp.c%3A%23000000%2Cs.t%3A18%7Cs.e%3Ag.s%7Cp.w%3A3%2Cs.e%3Ag%7Cp.v%3Aoff%2Cs.t%3A1%7Cs.e%3Ag.s%7Cp.v%3Aon%2Cs.e%3Al%7Cp.v%3Aon%215m1%215f1.350000023841858`
        const tileLayer=customLayer('google_labels_reest',tileUrl,20)
        map.mapTypes.stack.layers[2]=tileLayer
        map.setMapTypeId('stack')
    }

    function setHW(){
        map.mapTypes.stack.layers.splice(2, 1)
        const tileUrl = `https://maprastertile-drcn.dbankcdn.cn/display-service/v1/online-render/getTile/24.12.10.10/{z}/{x}/{y}/?language=zh&p=46&scale=2&mapType=ROADMAP&presetStyleId=standard&pattern=JPG&key=DAEDANitav6P7Q0lWzCzKkLErbrJG4kS1u%2FCpEe5ZyxW5u0nSkb40bJ%2BYAugRN03fhf0BszLS1rCrzAogRHDZkxaMrloaHPQGO6LNg==`
        const tileLayer=customLayer('Petal_Maps',tileUrl,20)
        map.mapTypes.stack.layers[0]=tileLayer
        map.setMapTypeId('stack')
    }

    function setGD(){
        const tileUrl = `https://t2.tianditu.gov.cn/ter_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=ter&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=75f0434f240669f4a2df6359275146d2`
        const tileLayer=customLayer('GaoDe_Terrain',tileUrl,20)
        //map.mapTypes.stack.layers[0]=tileLayer

        const tileUrl_ = `https://t2.tianditu.gov.cn/tbo_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=tbo&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=75f0434f240669f4a2df6359275146d2`
        const tileLayer_=customLayer('GaoDe_Border',tileUrl_,20)
        map.mapTypes.stack.layers[1]=tileLayer_

        const _tileUrl = `https://t2.tianditu.gov.cn/cia_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cia&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILECOL={x}&TILEROW={y}&TILEMATRIX={z}&tk=75f0434f240669f4a2df6359275146d2`
        const _tileLayer=customLayer('GaoDe_Labels',_tileUrl,20)
        //map.mapTypes.stack.layers[2]=_tileLayer

        map.setMapTypeId('stack')
    }

    function setYandex(){
        const svUrl=`https://core-stv-renderer.maps.yandex.net/2.x/tiles?l=stv&x={x}&y={y}&z={z}&scale=1&v=2025.04.04.20.13-1_25.03.31-4-24330`
        const baseUrl=`https://core-renderer-tiles.maps.yandex.net/tiles?l=map&v=5.04.07-2~b:250311142430~ib:250404100358-24371&x={x}&y={y}&z={z}&scale=1&lang=en_US`
        const svLayer=customLayer('Yandex_StreetView',svUrl,20,5)
        const baseLayer=customLayer('Yandex_Maps',baseUrl,20,1)
        map.mapTypes.stack.layers.splice(2, 0,svLayer)
        map.mapTypes.stack.layers.splice(2, 0,baseLayer)
        map.mapTypes.set("stack",map.mapTypes.stack.layers)
        map.setMapTypeId('stack')
    }

    function setApple(){
        const svUrl=`https://lookmap.eu.pythonanywhere.com/bluelines_raster_2x/{z}/{x}/{y}.png`
        const svLayer=customLayer('Apple_StreetView',svUrl,16)
        map.mapTypes.stack.layers.splice(2, 0,svLayer)
        map.setMapTypeId('stack')

    }

    async function downloadTile(id,g) {
        try {
            const response = await fetch(`https://streetviewpixels-pa.googleapis.com/v1/tile?cb_client=apiv3&panoid=${id}&output=tile&x=${g==='Gen4'?18:16}&y=${g==='Gen4'?13:11}&zoom=5&nbt=1&fover=2`);
            const imageBlob = await response.blob();
            const img = new Image();
            img.onload = function() {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                canvas.width = img.width;
                canvas.height = img.height;
                ctx.drawImage(img, 0, 0);

                const dataUrl = canvas.toDataURL('image/jpeg');
                const link = document.createElement('a');
                link.href = dataUrl;
                link.download = id+'.jpg';
                link.click();
            };
            img.src = URL.createObjectURL(imageBlob);
        } catch (error) {
            console.error('Error:', error);
        }
    }

    function lon2tile(lng,zoom) {
        return (lng+180)/360*Math.pow(2,zoom);
    }

    function lat2tile(lat,zoom){
        return (1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom);
    }

    function lonLatToEpsg3395Tile(lng, lat, zoom) {
        return [lon2tile(lng,zoom), lat2tile(lat,zoom)];
    }

    function getMap(){
        let element = document.getElementsByClassName("map-embed")[0]
        try{
            const keys = Object.keys(element)
            const key = keys.find(key => key.startsWith("__reactFiber$"))
            const props = element[key]
            if(!map)window.map=props.pendingProps.children[1].props.children[1].props.map
        }
        catch(e){
            console.error('Failed to get map: '+e)
        }
    }

    function delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }


    async function fetchPanorama(service,panoId) {

        await delay(100);
        return await service.getPanorama({ pano: panoId });
    }

    async function findLinkPanos() {
        const startLoc = editor.currentLocation.updatedProps;
        let prevHeading = startLoc.heading;
        let service = new google.maps.StreetViewService();
        let metadata = await fetchPanorama(service,startLoc.panoId);
        while (metadata.data.links.length == 2) {
            let nextLoc = metadata.data.links.find(loc => Math.abs(loc.heading - prevHeading) <= 90);
            if (nextLoc) {
                metadata = await fetchPanorama(service,nextLoc.pano);
                editor.addLocation({
                    location: {
                        lat: metadata.data.location.latLng.lat(),
                        lng: metadata.data.location.latLng.lng()
                    },
                    panoId: metadata.data.location.pano,
                    heading: nextLoc.heading,
                    pitch: 0,
                    zoom: 0,
                    tags: [],
                    flags: 1
                });
                prevHeading = nextLoc.heading;
            }
            else break
        }
    }

    function toggleElementHidden() {
        if(!isHidden){
            style = GM_addStyle(`
    .embed-controls {display: none !important}
    .SLHIdE-sv-links-control {display: none !important}
    [class$="gmnoprint"], [class$="gm-style-cc"] {display: none !important}
  `);
            isHidden = true;
        }
        else{
            style.remove()
            isHidden=false;
        }
    }


    let onKeyDown =async (e) => {
        if (e.target.tagName === 'INPUT' || e.target.isContentEditable||!isApplied) {
            return;
        }

        const activeSelections=editor.selections.length > 0 ? editor.selections.flatMap(selection => selection.locations) : locations

        if (e.key >= '1' && e.key <= '9'){
            e.stopImmediatePropagation();
            const tagIndex=parseInt(e.key)
            await tagLoc(tagIndex)
        }

        if (!e.shiftKey&&!e.ctrlKey&&(e.key === KEYBOARD_SHORTCUTS.hideElement || e.key === KEYBOARD_SHORTCUTS.hideElement.toLowerCase())) {
            e.stopImmediatePropagation();
            toggleElementHidden()
        }

        else if (!e.shiftKey&&!e.ctrlKey&&(e.key === KEYBOARD_SHORTCUTS.deSelectAll || e.key === KEYBOARD_SHORTCUTS.deSelectAll.toLowerCase())) {
            e.stopImmediatePropagation();
            editor.resetSelections()
        }

        else if (!e.shiftKey&&!e.ctrlKey&&(e.key === KEYBOARD_SHORTCUTS.copyLoc || e.key === KEYBOARD_SHORTCUTS.copyLoc.toLowerCase())) {
            e.stopImmediatePropagation();
            copyLoc()
        }

        else if (e.key === KEYBOARD_SHORTCUTS.switchLoc || e.key === KEYBOARD_SHORTCUTS.switchLoc.toLowerCase()) {
            e.stopImmediatePropagation();
            switchLoc(activeSelections)
        }

        else if (e.key === KEYBOARD_SHORTCUTS.rewindLoc || e.key === KEYBOARD_SHORTCUTS.rewindLoc.toLowerCase()) {
            e.stopImmediatePropagation();
            rewindLoc(activeSelections)
        }

        else if (!e.shiftKey&&!e.ctrlKey&&(e.key === KEYBOARD_SHORTCUTS.deleteLoc || e.key === KEYBOARD_SHORTCUTS.deleteLoc.toLowerCase())) {
            e.stopImmediatePropagation();
            deleteLoc(activeSelections[currentIndex-1])
        }
        else if (!e.shiftKey&&!e.ctrlKey&&(e.key === KEYBOARD_SHORTCUTS.closeLoc || e.key === KEYBOARD_SHORTCUTS.closeLoc.toLowerCase())) {
            e.stopImmediatePropagation();
            editor.closeLocation(editor.currentLocation.updatedProps)
        }

        else if (e.key === KEYBOARD_SHORTCUTS.pruneDuplicates || e.key === KEYBOARD_SHORTCUTS.pruneDuplicates.toLowerCase()){
            const duplicates=editor.selections.flatMap(selection=>{if (selection.key.includes('dup') ) return selection})
            if (duplicates.length>0){
                duplicates.forEach((dup)=>{
                    editor.pruneDuplicates(dup)
                })
            }
        }

        if ((e.shiftKey)&&(e.key === KEYBOARD_SHORTCUTS.exportAsCsv || e.key === KEYBOARD_SHORTCUTS.exportAsCsv.toLowerCase())) {
            e.stopImmediatePropagation();
            exportAsCsv(activeSelections)
        }

        if ((e.shiftKey)&&(e.key === KEYBOARD_SHORTCUTS.resetGulf || e.key === KEYBOARD_SHORTCUTS.resetGulf.toLowerCase())) {
            e.stopImmediatePropagation();
            resetGulf()
        }

        if ((e.shiftKey)&&(e.key === KEYBOARD_SHORTCUTS.classicMap || e.key === KEYBOARD_SHORTCUTS.classicMap.toLowerCase())) {
            e.stopImmediatePropagation();
            classicMap()
        }

        if ((e.shiftKey)&&(e.key === KEYBOARD_SHORTCUTS.deleteTags || e.key === KEYBOARD_SHORTCUTS.deleteTags.toLowerCase())) {
            e.stopImmediatePropagation();
            deleteTags()
        }

        if (e.shiftKey&&(e.key === KEYBOARD_SHORTCUTS.findLinkPanos || e.key === KEYBOARD_SHORTCUTS.findLinkPanos.toLowerCase())) {
            e.stopImmediatePropagation();
            findLinkPanos();
        }


        if ((e.shiftKey)&&(e.key === 'h' || e.key === 'H')) {
            e.stopImmediatePropagation();
            setHW()
        }
        /*if (e.key === 't' || e.key === 'T') {
            e.stopImmediatePropagation();
            getEditor()
            const panos=[]
            for (const loc of selections) {
                const panoId=loc.panoId
                var gen
                if (loc.tags.includes('Gen4')) gen='Gen4'
                if(panoId) panos.push({id:panoId,g:gen})
            }
            const downloadPromises = panos.map(pano => downloadTile(pano.id, pano.g));
            await Promise.all(downloadPromises);

        };*/
    }
    document.addEventListener("keydown", onKeyDown);

    var shortCutButton = document.createElement('button');
    shortCutButton.textContent = 'Shortcut Off';
    shortCutButton.style.position = 'absolute';
    shortCutButton.style.top = '8px';
    shortCutButton.style.right = '700px';
    shortCutButton.style.zIndex = '9999';
    shortCutButton.style.borderRadius = "18px";
    shortCutButton.style.padding = "5px 10px";
    shortCutButton.style.border = "none";
    shortCutButton.style.backgroundColor = "#4CAF50";
    shortCutButton.style.color = "white";
    shortCutButton.style.cursor = "pointer";
    shortCutButton.addEventListener('click', function(){
        if(isApplied){
            isApplied=false
            shortCutButton.style.border='none'
            shortCutButton.textContent = 'Shortcut Off';
        }
        else {isApplied=true
              shortCutButton.textContent = 'ShortCut On';
              shortCutButton.style.border='2px solid #fff'}

    });
    document.body.appendChild(shortCutButton)

    document.addEventListener('keydown', function(e) {
        if (e.shiftKey ) {
            isShift = true;
        }
    });
    document.addEventListener('keyup', function(e) {
        if (!e.shiftKey ) {
            isShift = false;
        }
    });

    document.addEventListener('mousedown', function(e) {
        if (e.button === 0&&isShift) {
            isDrawing = true;
            startX = e.clientX;
            startY = e.clientY;
            document.body.style.userSelect = 'none'
            selectionBox = document.createElement('div');
            selectionBox.style.position = 'absolute';
            selectionBox.style.border = '2px solid rgba(0, 128, 255, 0.7)';
            selectionBox.style.backgroundColor = 'rgba(0, 128, 255, 0.2)';
            document.body.appendChild(selectionBox);
        }
    });

    document.addEventListener('mousemove', function(e) {
        if (isDrawing) {
            endX = e.clientX;
            endY = e.clientY;

            const width = Math.abs(endX - startX);
            const height = Math.abs(endY - startY);
            selectionBox.style.left = `${Math.min(startX, endX)}px`;
            selectionBox.style.top = `${Math.min(startY, endY)}px`;
            selectionBox.style.width = `${width}px`;
            selectionBox.style.height = `${height}px`;
            selectionBox.style.zIndex = '999999';
        }
    });

    document.addEventListener('mouseup', function(e) {
        if (isDrawing) {
            isDrawing = false;
            const rect = selectionBox.getBoundingClientRect();
            document.body.removeChild(selectionBox);
            const elements = document.querySelectorAll('ul.tag-list');
            elements.forEach(element => {
                const childrens = element.querySelectorAll('li.tag.has-button');
                childrens.forEach(child => {
                    const childRect = child.getBoundingClientRect();
                    if (
                        childRect.top >= rect.top &&
                        childRect.left >= rect.left &&
                        childRect.bottom <= rect.bottom &&
                        childRect.right <= rect.right
                    ) {
                        child.click();
                        document.body.style.userSelect = 'text';
                    }
                });
            });
        }
    });
})();