Map-Making Shortcuts

use shortcut to help you mapping on map-making app

  1. // ==UserScript==
  2. // @name Map-Making Shortcuts
  3. // @namespace https://greasyfork.org/users/1179204
  4. // @description use shortcut to help you mapping on map-making app
  5. // @version 1.2.9
  6. // @license BSD
  7. // @author KaKa
  8. // @match *://map-making.app/maps/*
  9. // @icon https://www.svgrepo.com/show/521871/switch.svg
  10. // ==/UserScript==
  11. (function() {
  12. let editor,selections,currentIndex
  13. let map,mapListener,isApplied=false,customZoom=0.17,isCustom=false,isASV,isYSV
  14. let isDrawing = false;
  15. let startX, startY, endX, endY;
  16. let selectionBox;
  17. let isShift = false;
  18.  
  19. function getEditor() {
  20. map=unsafeWindow.map
  21. if(!map)getMap()
  22. editor = unsafeWindow.editor
  23. const activeSelections = editor.selections;
  24. const locations=unsafeWindow.locations
  25. selections = activeSelections.length > 0 ? activeSelections.flatMap(selection => selection.locations) : locations;
  26. }
  27.  
  28. function exportAsCsv(){
  29. if(!selections)getEditor()
  30. const csvContent = jsonToCSV(selections);
  31. downloadCSV(csvContent);
  32. }
  33.  
  34. function downloadCSV(csvContent, fileName = "output.csv") {
  35. const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
  36. const link = document.createElement('a');
  37. if (link.download !== undefined) {
  38. const url = URL.createObjectURL(blob);
  39. link.setAttribute('href', url);
  40. link.setAttribute('download', fileName);
  41. link.style.visibility = 'hidden';
  42. document.body.appendChild(link);
  43. link.click();
  44. document.body.removeChild(link);
  45. }
  46. }
  47.  
  48. function getTagsForRow(item, maxTags) {
  49. const tags = item.tags || [];
  50. return Array.from({ length: maxTags }, (_, index) => tags[index] || '');
  51. }
  52.  
  53. function getFormattedDate(dateStr) {
  54. if (!dateStr) return '';
  55. const date = new Date(dateStr);
  56. if (isNaN(date.getTime())) return '';
  57. const year = date.getFullYear();
  58. const month = String(date.getMonth() + 1).padStart(2, '0');
  59. return `${year}-${month}`;
  60. }
  61.  
  62. function getMaxTagCount(jsonData) {
  63. let maxTags = 0;
  64. jsonData.forEach(item => {
  65. if (item.tags && item.tags.length > maxTags) {
  66. maxTags = item.tags.length;
  67. }
  68. });
  69. return maxTags;
  70. }
  71.  
  72. function jsonToCSV(jsonData) {
  73. const maxTags = getMaxTagCount(jsonData);
  74. const tagHeaders = Array.from({ length: maxTags }, (_, i) => `tag${i + 1}`);
  75. const headers = ["lat", "lng", "panoId", "heading", "pitch", "zoom", "date", ...tagHeaders];
  76. const rows = jsonData.map(item => {
  77. const lat = item.location.lat|| '';
  78. const lng = item.location.lng|| '';
  79. const panoId = item.panoId|| '';
  80. const heading = item.heading|| '';
  81. const pitch = item.pitch|| '';
  82. const zoom = item.zoom || '';
  83. const date = getFormattedDate(item.panoDate)||'';
  84. const tags = getTagsForRow(item, maxTags);
  85.  
  86. return [
  87. lat,
  88. lng,
  89. panoId,
  90. heading,
  91. pitch,
  92. zoom,
  93. date,
  94. ...tags
  95. ];
  96. });
  97.  
  98. const csvContent = [headers, ...rows].map(row => row.join(",")).join("\n");
  99. return csvContent;
  100. }
  101.  
  102. function switchLoc(locs) {
  103. if(editor.currentLocation) editor.closeLocation(editor.currentLocation.updatedProps)
  104. if (!currentIndex) {
  105. currentIndex =1;
  106. } else {
  107. currentIndex +=1
  108. if (currentIndex>locs.length){
  109. currentIndex=1
  110. }
  111. }
  112. editor.openLocation(locs[currentIndex-1]);
  113. focusOnLoc(locs[currentIndex-1])
  114. if(isCustom)setTimeout(function(){setZoom(customZoom)},100)
  115. }
  116.  
  117. function rewindLoc(locs) {
  118. if(editor.currentLocation) editor.closeLocation(editor.currentLocation.updatedProps)
  119. if (!currentIndex) {
  120. currentIndex =1;
  121. }
  122. else {
  123. currentIndex -=1
  124. if (currentIndex<1) currentIndex=selections.length
  125. }
  126. editor.openLocation(locs[currentIndex-1]);
  127. focusOnLoc(locs[currentIndex-1])
  128. if(isCustom)setTimeout(function(){setZoom(customZoom)},100)
  129. }
  130.  
  131. function focusOnLoc(loc){
  132. map.setCenter(loc.location)
  133. map.setZoom(17)
  134. }
  135.  
  136. function deleteLoc(loc){
  137. editor.closeAndDeleteLocation(loc)
  138. }
  139.  
  140. function rewindSelections(loc){
  141. currentIndex=1
  142. editor.openLocation(selections[0])
  143. if(isCustom)setTimeout(function(){setZoom(customZoom)},100)
  144. }
  145.  
  146. function setZoom(z){
  147. if(z<0)z=0
  148. if(z>4)z=4
  149. const svControl=unsafeWindow.streetView
  150. svControl.setZoom(z)
  151.  
  152. }
  153.  
  154. function resetDefaultZoom(){
  155. if(mapListener) return
  156. mapListener=function(){
  157. if(isCustom){
  158. let intervalId=setInterval(function(){
  159. editor = unsafeWindow.editor
  160. if(editor.currentLocation){
  161. setZoom(customZoom)
  162. clearInterval(intervalId)}
  163. },50);
  164.  
  165. }
  166. }
  167. map.addListener('click', mapListener);
  168. }
  169.  
  170. function getTag(index){
  171.  
  172. const tags=unsafeWindow.editor.tags
  173. const result = Object.keys(tags).find(tag => tags[tag].order === index - 1)
  174. if(result){
  175. return result.trim()}
  176. }
  177.  
  178. function deleteTags() {
  179. let selections = unsafeWindow.editor.selections;
  180. while (selections.length > 0) {
  181. const item = selections[0];
  182. const tag = JSON.parse(item.key);
  183. const tagName = tag.tagName;
  184. const locations = item.locations;
  185.  
  186. editor.deleteTag(tagName, locations);
  187.  
  188. selections = unsafeWindow.editor.selections;
  189. }
  190. }
  191.  
  192. function customLayer(name,tileUrl,maxZoom,minZoom){
  193. return new google.maps.ImageMapType({
  194. getTileUrl: function(coord, zoom) {
  195. return tileUrl
  196. .replace('{z}', zoom)
  197. .replace('{x}', coord.x)
  198. .replace('{y}', coord.y);
  199. },
  200. tileSize: new google.maps.Size(256, 256),
  201. name: name,
  202. maxZoom:maxZoom,
  203. minZoom:minZoom||1
  204. });
  205. }
  206.  
  207. function classicMap(){
  208. 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`
  209. const tileLayer=customLayer('google_labels_reest',tileUrl,20)
  210. map.mapTypes.stack.layers[0]=tileLayer
  211. map.setMapTypeId('stack')
  212. }
  213. function resetGulf(){
  214. 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`
  215. 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`
  216. 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`
  217. 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`
  218. const tileLayer=customLayer('google_labels_reest',tileUrl,20)
  219. map.mapTypes.stack.layers[2]=tileLayer
  220. map.setMapTypeId('stack')
  221. }
  222. function setHW(){
  223. map.mapTypes.stack.layers.splice(2, 1)
  224. 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==`
  225. const tileLayer=customLayer('Petal_Maps',tileUrl,20)
  226. map.mapTypes.stack.layers[0]=tileLayer
  227. map.setMapTypeId('stack')
  228. }
  229. function setGD(){
  230. 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`
  231. const tileLayer=customLayer('GaoDe_Terrain',tileUrl,20)
  232. //map.mapTypes.stack.layers[0]=tileLayer
  233.  
  234. 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`
  235. const tileLayer_=customLayer('GaoDe_Border',tileUrl_,20)
  236. map.mapTypes.stack.layers[1]=tileLayer_
  237.  
  238. 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`
  239. const _tileLayer=customLayer('GaoDe_Labels',_tileUrl,20)
  240. //map.mapTypes.stack.layers[2]=_tileLayer
  241.  
  242. map.setMapTypeId('stack')
  243. }
  244.  
  245. function setYandex(){
  246. 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`
  247. 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`
  248. const svLayer=customLayer('Yandex_StreetView',svUrl,20,5)
  249. const baseLayer=customLayer('Yandex_Maps',baseUrl,20,1)
  250. map.mapTypes.stack.layers.splice(2, 0,svLayer)
  251. map.mapTypes.stack.layers.splice(2, 0,baseLayer)
  252. map.mapTypes.set("stack",map.mapTypes.stack.layers)
  253. map.setMapTypeId('stack')
  254. }
  255. function setApple(){
  256. const svUrl=`https://lookmap.eu.pythonanywhere.com/bluelines_raster_2x/{z}/{x}/{y}.png`
  257. const svLayer=customLayer('Apple_StreetView',svUrl,16)
  258. map.mapTypes.stack.layers.splice(2, 0,svLayer)
  259. map.setMapTypeId('stack')
  260.  
  261. }
  262. async function downloadTile(id,g) {
  263. try {
  264. 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`);
  265. const imageBlob = await response.blob();
  266. const img = new Image();
  267. img.onload = function() {
  268. const canvas = document.createElement('canvas');
  269. const ctx = canvas.getContext('2d');
  270. canvas.width = img.width;
  271. canvas.height = img.height;
  272. ctx.drawImage(img, 0, 0);
  273.  
  274. const dataUrl = canvas.toDataURL('image/jpeg');
  275. const link = document.createElement('a');
  276. link.href = dataUrl;
  277. link.download = id+'.jpg';
  278. link.click();
  279. };
  280. img.src = URL.createObjectURL(imageBlob);
  281. } catch (error) {
  282. console.error('Error:', error);
  283. }
  284. }
  285. function lon2tile(lng,zoom) {
  286. return (lng+180)/360*Math.pow(2,zoom);
  287. }
  288. function lat2tile(lat,zoom){
  289. return (1-Math.log(Math.tan(lat*Math.PI/180) + 1/Math.cos(lat*Math.PI/180))/Math.PI)/2 *Math.pow(2,zoom);
  290. }
  291.  
  292. function lonLatToEpsg3395Tile(lng, lat, zoom) {
  293.  
  294. return [lon2tile(lng,zoom), lat2tile(lat,zoom)];
  295.  
  296. }
  297. function getMap(){
  298. let element = document.getElementsByClassName("map-embed")[0]
  299. try{
  300. const keys = Object.keys(element)
  301. const key = keys.find(key => key.startsWith("__reactFiber$"))
  302. const props = element[key]
  303. map=props.pendingProps.children[1].props.children[1].props.map
  304. window.map=map
  305. }
  306. catch(error){
  307. console.error('Failed to get map')
  308. }
  309. }
  310. let isAnimating = false;
  311. let animationTimestamp = 0;
  312.  
  313. function animatePovNorth() {
  314. const viewer=unsafeWindow.streetView
  315. const startTime = Date.now();
  316. animationTimestamp = startTime;
  317.  
  318. const currentPov = viewer.getPov();
  319. const currentZoom = viewer.getZoom();
  320.  
  321. const isHeadingZero = currentPov.heading % 360 === 0 || isAnimating;
  322. const targetPov = {
  323. heading: 0,
  324. pitch: isHeadingZero ? -90 : 0,
  325. };
  326.  
  327. if (Math.abs(targetPov.heading - currentPov.heading) > 180) {
  328. targetPov.heading += 360;
  329. }
  330.  
  331. const transitionDuration = 4 * Math.max(Math.abs(targetPov.heading - currentPov.heading), 100);
  332. let previousTimestamp = null;
  333.  
  334. function updatePov(timestamp) {
  335. if (animationTimestamp !== startTime) return;
  336. if (!previousTimestamp) previousTimestamp = timestamp;
  337.  
  338. const progress = Math.min((timestamp - previousTimestamp) / transitionDuration, 1);
  339. const easing = 1 - Math.pow(1 - progress, 3);
  340.  
  341. const newHeading = currentPov.heading + (targetPov.heading - currentPov.heading) * easing;
  342. const newPitch = currentPov.pitch + (targetPov.pitch - currentPov.pitch) * easing;
  343.  
  344. viewer.setPov({ heading: newHeading, pitch: newPitch });
  345. viewer.setZoom(currentZoom + (0 - currentZoom) * easing);
  346.  
  347. if (progress < 1) {
  348. requestAnimationFrame(updatePov);
  349. } else {
  350. isAnimating = false;
  351. }
  352. }
  353.  
  354. isAnimating = true;
  355. requestAnimationFrame(updatePov);
  356. }
  357. let onKeyDown = async (e) => {
  358. if (e.target.tagName === 'INPUT' || e.target.isContentEditable||!isApplied) {
  359. return;
  360. }
  361. if(!editor)getEditor()
  362. if (e.shiftKey&&(e.key === 'k' || e.key === 'K')) {
  363. function delay(ms) {
  364. return new Promise(resolve => setTimeout(resolve, ms));
  365. }
  366.  
  367. async function fetchPanorama(panoId) {
  368. let service = new google.maps.StreetViewService();
  369. await delay(100);
  370. return await service.getPanorama({ pano: panoId });
  371. }
  372.  
  373. async function processLocations() {
  374. const startLoc = unsafeWindow.editor.currentLocation.location;
  375. let prevHeading = startLoc.heading;
  376. let metadata = await fetchPanorama(startLoc.panoId);
  377.  
  378. while (metadata.data.links.length == 2) {
  379. let nextLoc = metadata.data.links.find(loc => Math.abs(loc.heading - prevHeading) <= 90);
  380. if (nextLoc) {
  381. metadata = await fetchPanorama(nextLoc.pano);
  382. editor.addLocation({
  383. location: {
  384. lat: metadata.data.location.latLng.lat(),
  385. lng: metadata.data.location.latLng.lng()
  386. },
  387. panoId: metadata.data.location.pano,
  388. heading: nextLoc.heading,
  389. pitch: 0,
  390. zoom: 0,
  391. tags: [],
  392. flags: 1
  393. });
  394. prevHeading = nextLoc.heading;
  395. }
  396. else break
  397. }
  398. }
  399. processLocations();
  400. }
  401. if (e.key === 'q' || e.key === 'Q') {
  402. e.stopImmediatePropagation();
  403. switchLoc(selections)
  404. }
  405. else if (e.key === 'e' || e.key === 'E') {
  406. e.stopImmediatePropagation();
  407. rewindLoc(selections)
  408. }
  409. else if (e.key === 'c' || e.key === 'C') {
  410. e.stopImmediatePropagation();
  411. deleteLoc(selections[currentIndex-1])
  412. }
  413. else if (e.key === 'n' || e.key === 'N'&&!e.shiftKey) {
  414. e.stopImmediatePropagation();
  415. animatePovNorth()
  416. }
  417. else if (e.key === 'v' || e.key === 'V') {
  418. e.stopImmediatePropagation();
  419. editor.closeLocation(editor.currentLocation.updatedProps)
  420. }
  421. else if (e.key === 'r' || e.key === 'R') {
  422. e.stopImmediatePropagation();
  423. rewindSelections()
  424. };
  425. if ((e.shiftKey)&&(e.key === 'd' || e.key === 'D')) {
  426. exportAsCsv()
  427. }
  428. if ((e.shiftKey)&&(e.key === 'h' || e.key === 'H')) {
  429. setHW()
  430. }
  431. if ((e.shiftKey)&&(e.key === 'm' || e.key === 'M')) {
  432. resetGulf()
  433. }
  434. if ((e.shiftKey)&&(e.key === 'n' || e.key === 'N')) {
  435. classicMap()
  436. }
  437. if ((e.shiftKey)&&(e.key === 'b' || e.key === 'B')) {
  438. deleteTags()
  439. }
  440. /*if (e.key === 't' || e.key === 'T') {
  441. e.stopImmediatePropagation();
  442. getEditor()
  443. const panos=[]
  444. for (const loc of selections) {
  445. const panoId=loc.panoId
  446. var gen
  447. if (loc.tags.includes('Gen4')) gen='Gen4'
  448. if(panoId) panos.push({id:panoId,g:gen})
  449. }
  450. const downloadPromises = panos.map(pano => downloadTile(pano.id, pano.g));
  451. await Promise.all(downloadPromises);
  452.  
  453. };*/
  454.  
  455. if (e.key === 'g' || e.key === 'G') {
  456. e.stopImmediatePropagation();
  457. resetDefaultZoom()
  458. if(!isCustom) {
  459. var input = prompt('please enter a zoom value(0-4):');
  460.  
  461. var parsedValue = parseFloat(input);
  462.  
  463. if (isNaN(parsedValue)) {
  464. alert('The input is not a valid zoom! Please try again.');
  465. isCustom=false
  466. } else {
  467. customZoom=parsedValue
  468. isCustom=true
  469. resetDefaultZoom()
  470. alert('Custom zoom has been applied!');
  471. }
  472. }
  473. else {
  474. isCustom=false
  475. alert('Zoom customizing is cancelled!')}
  476. }
  477.  
  478. }
  479. document.addEventListener("keydown", onKeyDown);
  480.  
  481. var shortCutButton = document.createElement('button');
  482. shortCutButton.textContent = 'Shortcut Off';
  483. shortCutButton.style.position = 'absolute';
  484. shortCutButton.style.top = '8px';
  485. shortCutButton.style.right = '700px';
  486. shortCutButton.style.zIndex = '9999';
  487. shortCutButton.style.borderRadius = "18px";
  488. shortCutButton.style.padding = "5px 10px";
  489. shortCutButton.style.border = "none";
  490. shortCutButton.style.backgroundColor = "#4CAF50";
  491. shortCutButton.style.color = "white";
  492. shortCutButton.style.cursor = "pointer";
  493. shortCutButton.addEventListener('click', function(){
  494. if(isApplied){
  495. isApplied=false
  496. shortCutButton.style.border='none'
  497. shortCutButton.textContent = 'Shortcut Off';
  498. }
  499. else {isApplied=true
  500. shortCutButton.textContent = 'ShortCut On';
  501. shortCutButton.style.border='2px solid #fff'}
  502.  
  503. });
  504. document.body.appendChild(shortCutButton)
  505.  
  506. document.addEventListener('keydown', function(e) {
  507. if (e.shiftKey ) {
  508. isShift = true;
  509. }
  510. });
  511. document.addEventListener('keyup', function(e) {
  512. if (!e.shiftKey ) {
  513. isShift = false;
  514. }
  515. });
  516.  
  517. document.addEventListener('mousedown', function(e) {
  518. if (e.button === 0&&isShift) {
  519. isDrawing = true;
  520. startX = e.clientX;
  521. startY = e.clientY;
  522. document.body.style.userSelect = 'none'
  523. selectionBox = document.createElement('div');
  524. selectionBox.style.position = 'absolute';
  525. selectionBox.style.border = '2px solid rgba(0, 128, 255, 0.7)';
  526. selectionBox.style.backgroundColor = 'rgba(0, 128, 255, 0.2)';
  527. document.body.appendChild(selectionBox);
  528. }
  529. });
  530.  
  531. document.addEventListener('mousemove', function(e) {
  532. if (isDrawing) {
  533. endX = e.clientX;
  534. endY = e.clientY;
  535.  
  536. const width = Math.abs(endX - startX);
  537. const height = Math.abs(endY - startY);
  538. selectionBox.style.left = `${Math.min(startX, endX)}px`;
  539. selectionBox.style.top = `${Math.min(startY, endY)}px`;
  540. selectionBox.style.width = `${width}px`;
  541. selectionBox.style.height = `${height}px`;
  542. selectionBox.style.zIndex = '999999';
  543. }
  544. });
  545.  
  546. document.addEventListener('mouseup', function(e) {
  547. if (isDrawing) {
  548. isDrawing = false;
  549. const rect = selectionBox.getBoundingClientRect();
  550. document.body.removeChild(selectionBox);
  551. const elements = document.querySelectorAll('ul.tag-list');
  552. elements.forEach(element => {
  553. const childrens = element.querySelectorAll('li.tag.has-button');
  554. childrens.forEach(child => {
  555. const childRect = child.getBoundingClientRect();
  556. if (
  557. childRect.top >= rect.top &&
  558. childRect.left >= rect.left &&
  559. childRect.bottom <= rect.bottom &&
  560. childRect.right <= rect.right
  561. ) {
  562. child.click();
  563. document.body.style.userSelect = 'text';
  564. }
  565. });
  566. });
  567. }
  568. });
  569. })();