您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Adds some extra WME functionality related to Google place links.
当前为
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.icu/scripts/39208/1526036/WME%20Utils%20-%20Google%20Link%20Enhancer.js
// ==UserScript== // @name WME Utils - Google Link Enhancer // @namespace WazeDev // @version 2025.01.24.000 // @description Adds some extra WME functionality related to Google place links. // @author MapOMatic, WazeDev group // @include /^https:\/\/(www|beta)\.waze\.com\/(?!user\/)(.{2,6}\/)?editor\/?.*$/ // @license GNU GPLv3 // ==/UserScript== /* global OpenLayers */ /* global W */ // /* eslint-disable */ /* eslint-disable no-unused-vars */ class GoogleLinkEnhancer { #DISABLE_CLOSED_PLACES = false; // Set to TRUE if the feature needs to be temporarily disabled, e.g. during the COVID-19 pandemic. #EXT_PROV_ELEM_QUERY = 'wz-list-item.external-provider'; #EXT_PROV_ELEM_EDIT_QUERY = 'wz-list-item.external-provider-edit'; #EXT_PROV_ELEM_CONTENT_QUERY = 'div.external-provider-content'; #LINK_CACHE_NAME = 'gle_link_cache'; #LINK_CACHE_CLEAN_INTERVAL_MIN = 1; // Interval to remove old links and save new ones. #LINK_CACHE_LIFESPAN_HR = 6; // Remove old links when they exceed this time limit. #linkCache; #enabled = false; #disableApiUntil = null; // When a serious API error occurs (OVER_QUERY_LIMIT, REQUEST_DENIED), set this to a time in the future. #mapLayer = null; #distanceLimit = 400; // Default distance (meters) when Waze place is flagged for being too far from Google place. // Area place is calculated as #distanceLimit + <distance between centroid and furthest node> #showTempClosedPOIs = true; #linkObserver; #modeObserver; #lzString; #cacheCleanIntervalID; #originalHeadAppendChildMethod; strings = { permClosedPlace: 'Google indicates this place is permanently closed.\nVerify with other sources or your editor community before deleting.', tempClosedPlace: 'Google indicates this place is temporarily closed.', multiLinked: 'Linked more than once already. Please find and remove multiple links.', linkedToThisPlace: 'Already linked to this place', linkedNearby: 'Already linked to a nearby place', linkedToXPlaces: 'This is linked to {0} places', badLink: 'Invalid Google link. Please remove it.', tooFar: 'The Google linked place is more than {0} meters from the Waze place. Please verify the link is correct.' }; /* eslint-enable no-unused-vars */ constructor() { this.#initLZString(); const STORED_CACHE = localStorage.getItem(this.#LINK_CACHE_NAME); try { this.#linkCache = STORED_CACHE ? $.parseJSON(this.#lzString.decompressFromUTF16(STORED_CACHE)) : {}; } catch (ex) { if (ex.name === 'SyntaxError') { // In case the cache is corrupted and can't be read. this.#linkCache = {}; console.warn('GoogleLinkEnhancer:', 'An error occurred while loading the stored cache. A new cache was created.'); } else { throw ex; } } if (this.#linkCache === null || this.#linkCache.length === 0) this.#linkCache = {}; this.#initLayer(); // NOTE: Arrow functions are necessary for calling methods on object instances. // This could be made more efficient by only processing the relevant places. W.model.events.register('mergeend', null, () => { this.#processPlaces(); }); W.model.venues.on('objectschanged', () => { this.#processPlaces(); }); W.model.venues.on('objectsremoved', () => { this.#processPlaces(); }); W.model.venues.on('objectsadded', () => { this.#processPlaces(); }); // Watch for ext provider elements being added to the DOM, and add hover events. this.#linkObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { for (let idx = 0; idx < mutation.addedNodes.length; idx++) { const nd = mutation.addedNodes[idx]; if (nd.nodeType === Node.ELEMENT_NODE) { const $el = $(nd); const $subel = $el.find(this.#EXT_PROV_ELEM_QUERY); if ($el.is(this.#EXT_PROV_ELEM_QUERY)) { this.#formatLinkElements(); } else if ($subel.length) { this.#formatLinkElements(); } } } }); }); // Watch the side panel for addition of the sidebar-layout div, which indicates a mode change. this.#modeObserver = new MutationObserver(mutations => { mutations.forEach(mutation => { for (let idx = 0; idx < mutation.addedNodes.length; idx++) { const nd = mutation.addedNodes[idx]; if (nd.nodeType === Node.ELEMENT_NODE && $(nd).is('.sidebar-layout')) { this.#observeLinks(); break; } } }); }); // This is a special event that will be triggered when DOM elements are destroyed. /* eslint-disable wrap-iife, func-names, object-shorthand */ (function($) { $.event.special.destroyed = { remove: function(o) { if (o.handler && o.type !== 'destroyed') { o.handler(); } } }; })(jQuery); /* eslint-enable wrap-iife, func-names, object-shorthand */ // In case a place is already selected on load. const selObjects = W.selectionManager.getSelectedDataModelObjects(); if (selObjects.length && selObjects[0].type === 'venue') { this.#formatLinkElements(); } } #initLayer() { this.#mapLayer = new OpenLayers.Layer.Vector('Google Link Enhancements.', { uniqueName: '___GoogleLinkEnhancements', displayInLayerSwitcher: true, styleMap: new OpenLayers.StyleMap({ default: { strokeColor: '${strokeColor}', strokeWidth: '${strokeWidth}', strokeDashstyle: '${strokeDashstyle}', pointRadius: '15', fillOpacity: '0' } }) }); this.#mapLayer.setOpacity(0.8); W.map.addLayer(this.#mapLayer); } enable() { if (!this.#enabled) { this.#modeObserver.observe($('.edit-area #sidebarContent')[0], { childList: true, subtree: false }); this.#observeLinks(); // Watch for JSONP callbacks. JSONP is used for the autocomplete results when searching for Google links. this.#addJsonpInterceptor(); // Note: Using on() allows passing "this" as a variable, so it can be used in the handler function. $(window).on('unload', null, this, GoogleLinkEnhancer.#onWindowUnload); W.model.venues.on('objectschanged', this.#formatLinkElements, this); this.#processPlaces(); this.#cleanAndSaveLinkCache(); this.#cacheCleanIntervalID = setInterval(() => this.#cleanAndSaveLinkCache(), 1000 * 60 * this.#LINK_CACHE_CLEAN_INTERVAL_MIN); this.#enabled = true; } } disable() { if (this.#enabled) { this.#modeObserver.disconnect(); this.#linkObserver.disconnect(); this.#removeJsonpInterceptor(); $(window).off('unload', null, this, GoogleLinkEnhancer.#onWindowUnload); W.model.venues.off('objectschanged', this.#formatLinkElements, this); if (this.#cacheCleanIntervalID) clearInterval(this.#cacheCleanIntervalID); this.#cleanAndSaveLinkCache(); this.#enabled = false; } } // The distance (in meters) before flagging a Waze place that is too far from the linked Google place. // Area places use distanceLimit, plus the distance from the centroid of the AP to its furthest node. get distanceLimit() { return this.#distanceLimit; } set distanceLimit(value) { this.#distanceLimit = value; this.#processPlaces(); } get showTempClosedPOIs() { return this.#showTempClosedPOIs; } set showTempClosedPOIs(value) { this.#showTempClosedPOIs = value; this.#processPlaces(); } static #onWindowUnload(evt) { evt.data.#cleanAndSaveLinkCache(); } #cleanAndSaveLinkCache() { if (!this.#linkCache) return; const now = new Date(); Object.keys(this.#linkCache).forEach(id => { const link = this.#linkCache[id]; // Bug fix: if (link.location) { link.loc = link.location; delete link.location; } // Delete link if older than X hours. if (!link.ts || (now - new Date(link.ts)) > this.#LINK_CACHE_LIFESPAN_HR * 3600 * 1000) { delete this.#linkCache[id]; } }); localStorage.setItem(this.#LINK_CACHE_NAME, this.#lzString.compressToUTF16(JSON.stringify(this.#linkCache))); // console.log('link cache count: ' + Object.keys(this.#linkCache).length, this.#linkCache); } // Borrowed from WazeWrap static #distanceBetweenPoints(point1, point2) { const line = new OpenLayers.Geometry.LineString([point1, point2]); const length = line.getGeodesicLength(W.map.getProjectionObject()); return length; // multiply by 3.28084 to convert to feet } #isLinkTooFar(link, venue) { if (link.loc) { const linkPt = new OpenLayers.Geometry.Point(link.loc.lng, link.loc.lat); linkPt.transform(W.Config.map.projection.remote, W.map.getProjectionObject()); let venuePt; let distanceLim = this.distanceLimit; if (venue.isPoint()) { venuePt = venue.geometry.getCentroid(); } else { const bounds = venue.geometry.getBounds(); const center = bounds.getCenterLonLat(); venuePt = new OpenLayers.Geometry.Point(center.lon, center.lat); const topRightPt = new OpenLayers.Geometry.Point(bounds.right, bounds.top); distanceLim += GoogleLinkEnhancer.#distanceBetweenPoints(venuePt, topRightPt); } const distance = GoogleLinkEnhancer.#distanceBetweenPoints(linkPt, venuePt); return distance > distanceLim; } return false; } // eslint-disable-next-line class-methods-use-this #processPlaces() { // Disabled until we can find a fix. try { if (this.#enabled) { const that = this; // Get a list of already-linked id's const existingLinks = GoogleLinkEnhancer.#getExistingLinks(); this.#mapLayer.removeAllFeatures(); const drawnLinks = []; W.model.venues.getObjectArray().forEach(venue => { const promises = []; venue.attributes.externalProviderIDs.forEach(provID => { const id = provID.attributes.uuid; // Check for duplicate links const linkInfo = existingLinks[id]; if (linkInfo.count > 1) { const geometry = venue.isPoint() ? venue.geometry.getCentroid() : venue.geometry.clone(); const width = venue.isPoint() ? '4' : '12'; const color = '#fb8d00'; const features = [new OpenLayers.Feature.Vector(geometry, { strokeWidth: width, strokeColor: color })]; const lineStart = geometry.getCentroid(); linkInfo.venues.forEach(linkVenue => { if (linkVenue !== venue && !drawnLinks.some(dl => (dl[0] === venue && dl[1] === linkVenue) || (dl[0] === linkVenue && dl[1] === venue))) { features.push( new OpenLayers.Feature.Vector( new OpenLayers.Geometry.LineString([lineStart, linkVenue.geometry.getCentroid()]), { strokeWidth: 4, strokeColor: color, strokeDashstyle: '12 12' } ) ); drawnLinks.push([venue, linkVenue]); } }); that.#mapLayer.addFeatures(features); } // Get Google link info, and store results for processing. promises.push(that.#getLinkInfoAsync(id)); }); // Process all results of link lookups and add a highlight feature if needed. Promise.all(promises).then(results => { let strokeColor = null; let strokeDashStyle = 'solid'; if (!that.#DISABLE_CLOSED_PLACES && results.some(res => res.permclosed)) { if (/^(\[|\()?(permanently )?closed(\]|\)| -)/i.test(venue.attributes.name) || /(\(|- |\[)(permanently )?closed(\)|\])?$/i.test(venue.attributes.name)) { strokeDashStyle = venue.isPoint() ? '2 6' : '2 16'; } strokeColor = '#F00'; } else if (results.some(res => that.#isLinkTooFar(res, venue))) { strokeColor = '#0FF'; } else if (!that.#DISABLE_CLOSED_PLACES && that.#showTempClosedPOIs && results.some(res => res.tempclosed)) { if (/^(\[|\()?(temporarily )?closed(\]|\)| -)/i.test(venue.attributes.name) || /(\(|- |\[)(temporarily )?closed(\)|\])?$/i.test(venue.attributes.name)) { strokeDashStyle = venue.isPoint() ? '2 6' : '2 16'; } strokeColor = '#FD3'; } else if (results.some(res => res.notFound)) { strokeColor = '#F0F'; } if (strokeColor) { const style = { strokeWidth: venue.isPoint() ? '4' : '12', strokeColor, strokeDashStyle }; const geometry = venue.isPoint() ? venue.geometry.getCentroid() : venue.geometry.clone(); that.#mapLayer.addFeatures([new OpenLayers.Feature.Vector(geometry, style)]); } }); }); } } catch (ex) { console.error('PIE (Google Link Enhancer) error:', ex); } } #cacheLink(id, link) { link.ts = new Date(); this.#linkCache[id] = link; // console.log('link cache count: ' + Object.keys(this.#linkCache).length, this.#linkCache); } #getLinkInfoAsync(placeId) { return new Promise(resolve => { let link = this.#linkCache[placeId]; if (!link) { link = {}; this.#cacheLink(placeId, link); } resolve(link); }); } // _getLinkInfoAsync(id) { // const link = this.#linkCache[id]; // if (link) { // return Promise.resolve(link); // } // if (this.#disableApiUntil) { // if (Date.now() < this.#disableApiUntil) { // return Promise.resolve({ apiDisabled: true }); // } // this.#disableApiUntil = null; // } // return new Promise(resolve => { // let fullUrl = this._urlBase; // if (id.startsWith('q=') || id.startsWith('cid=')) { // fullUrl += id; // } else { // fullUrl += `place_id=${id}`; // } // $.getJSON(fullUrl).then(json => { // const res = {}; // if (json.status === 'OK') { // res.loc = json.result.geometry.location; // if (json.result.business_status === 'CLOSED_PERMANENTLY') { // res.permclosed = true; // } else if (json.result.business_status === 'CLOSED_TEMPORARILY') { // res.tempclosed = true; // } // this._cacheLink(id, res); // } else if (json.status === 'NOT_FOUND') { // res.notfound = true; // this._cacheLink(id, res); // } else if (this.#disableApiUntil) { // res.apiDisabled = true; // } else { // res.error = json.status; // res.errorMessage = json.error_message; // this.#disableApiUntil = Date.now() + 10 * 1000; // Disable api calls for 10 seconds. // console.error(`${GM_info.script.name}, Google Link Enhancer disabled for 10 seconds due to API error.`, res); // } // resolve(res); // }); // }); // } async #formatLinkElements(callCount = 0) { const $links = $('#edit-panel').find(this.#EXT_PROV_ELEM_QUERY); const selObjects = W.selectionManager.getSelectedDataModelObjects(); if (!$links.length) { // If links aren't available, continue calling this function for up to 3 seconds unless place has been deselected. if (callCount < 30 && selObjects.length && selObjects[0].type === 'venue') { setTimeout(() => this.#formatLinkElements(++callCount), 100); } } else { const existingLinks = GoogleLinkEnhancer.#getExistingLinks(); // fetch all links first const promises = []; const extProvElements = []; $links.each((ix, linkEl) => { const $linkEl = $(linkEl); extProvElements.push($linkEl); const id = GoogleLinkEnhancer.#getIdFromElement($linkEl); if (!id) return; promises.push(this.#getLinkInfoAsync(id)); }); await Promise.all(promises); extProvElements.forEach($extProvElem => { const id = GoogleLinkEnhancer.#getIdFromElement($extProvElem); if (!id) return; if (existingLinks[id] && existingLinks[id].count > 1 && existingLinks[id].isThisVenue) { setTimeout(() => { $extProvElem.find(this.#EXT_PROV_ELEM_CONTENT_QUERY).css({ backgroundColor: '#FFA500' }).attr({ title: this.strings.linkedToXPlaces.replace('{0}', existingLinks[id].count) }); }, 50); } }); } } static #getExistingLinks() { const existingLinks = {}; const thisVenue = W.selectionManager.getSelectedDataModelObjects()[0]; W.model.venues.getObjectArray().forEach(venue => { const isThisVenue = venue === thisVenue; const thisPlaceIDs = []; venue.attributes.externalProviderIDs.forEach(provID => { const id = provID.attributes.uuid; if (thisPlaceIDs.indexOf(id) === -1) { thisPlaceIDs.push(id); let link = existingLinks[id]; if (link) { link.count++; link.venues.push(venue); } else { link = { count: 1, venues: [venue] }; existingLinks[id] = link; if (provID.attributes.url != null) { const u = provID.attributes.url.replace('https://maps.google.com/?', ''); link.url = u; } } link.isThisVenue = link.isThisVenue || isThisVenue; } }); }); return existingLinks; } static #getOLMapExtent() { let extent = W.map.getExtent(); if (Array.isArray(extent)) { extent = new OpenLayers.Bounds(extent); extent.transform('EPSG:4326', 'EPSG:3857'); } return extent; } static #getIdFromElement($el) { const providerIndex = $el.parent().children().toArray().indexOf($el[0]); return W.selectionManager.getSelectedDataModelObjects()[0].getExternalProviderIDs()[providerIndex]?.attributes.uuid; } #observeLinks() { this.elem = document.querySelector('#edit-panel'); this.#linkObserver.observe(document.querySelector('#edit-panel'), { childList: true, subtree: true }); } // The JSONP interceptor is used to watch the head element for the addition of JSONP functions // that process Google link search results. Those functions are overridden by our own so we can // process the results before sending them on to the original function. #addJsonpInterceptor() { // The idea for this function was hatched here: // https://stackoverflow.com/questions/6803521/can-google-maps-places-autocomplete-api-be-used-via-ajax/9856786 // The head element, where the Google Autocomplete code will insert a tag // for a javascript file. const head = $('head')[0]; // The name of the method the Autocomplete code uses to insert the tag. const method = 'appendChild'; // The method we will be overriding. const originalMethod = head[method]; this.#originalHeadAppendChildMethod = originalMethod; const that = this; /* eslint-disable func-names, prefer-rest-params */ // Doesn't work as an arrow function (at least not without some modifications) head[method] = function() { // Check that the element is a javascript tag being inserted by Google. if (arguments[0] && arguments[0].src && arguments[0].src.match(/GetPredictions/)) { // Regex to extract the name of the callback method that the JSONP will call. const callbackMatchObject = (/callback=([^&]+)&|$/).exec(arguments[0].src); // Regex to extract the search term that was entered by the user. const searchTermMatchObject = (/\?1s([^&]+)&/).exec(arguments[0].src); // const searchTerm = unescape(searchTermMatchObject[1]); if (callbackMatchObject && searchTermMatchObject) { // The JSONP callback method is in the form "abc.def" and each time has a different random name. const names = callbackMatchObject[1].split('.'); // Store the original callback method. const originalCallback = names[0] && names[1] && window[names[0]] && window[names[0]][names[1]]; if (originalCallback) { const newCallback = function() { // Define your own JSONP callback if (arguments[0] && arguments[0].predictions) { // SUCCESS! // The autocomplete results const data = arguments[0]; // console.log('GLE: ' + JSON.stringify(data)); that._lastSearchResultPlaceIds = data.predictions.map(pred => pred.place_id); // Call the original callback so the WME dropdown can do its thing. originalCallback(data); } }; // Add copy of all the attributes of the old callback function to the new callback function. // This prevents the autocomplete functionality from throwing an error. Object.keys(originalCallback).forEach(key => { newCallback[key] = originalCallback[key]; }); window[names[0]][names[1]] = newCallback; // Override the JSONP callback } } } // Insert the element into the dom, regardless of whether it was being inserted by Google. return originalMethod.apply(this, arguments); }; /* eslint-enable func-names, prefer-rest-params */ } #removeJsonpInterceptor() { $('head')[0].appendChild = this.#originalHeadAppendChildMethod; } /* eslint-disable */ // Disabling eslint since this is copied code. #initLZString() { // LZ Compressor // Copyright (c) 2013 Pieroxy <[email protected]> // This work is free. You can redistribute it and/or modify it // under the terms of the WTFPL, Version 2 // LZ-based compression algorithm, version 1.4.4 this.#lzString = (function () { // private property const f = String.fromCharCode; const keyStrBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; const keyStrUriSafe = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$"; const baseReverseDic = {}; function getBaseValue(alphabet, character) { if (!baseReverseDic[alphabet]) { baseReverseDic[alphabet] = {}; for (let i = 0; i < alphabet.length; i++) { baseReverseDic[alphabet][alphabet.charAt(i)] = i; } } return baseReverseDic[alphabet][character]; } var LZString = { compressToBase64: function (input) { if (input === null) return ""; const res = LZString._compress(input, 6, function (a) { return keyStrBase64.charAt(a); }); switch (res.length % 4) { // To produce valid Base64 default: // When could this happen ? case 0: return res; case 1: return res + "==="; case 2: return res + "=="; case 3: return res + "="; } }, decompressFromBase64: function (input) { if (input === null) return ""; if (input === "") return null; return LZString._decompress(input.length, 32, function (index) { return getBaseValue(keyStrBase64, input.charAt(index)); }); }, compressToUTF16: function (input) { if (input === null) return ""; return LZString._compress(input, 15, function (a) { return f(a + 32); }) + " "; }, decompressFromUTF16: function (compressed) { if (compressed === null) return ""; if (compressed === "") return null; return LZString._decompress(compressed.length, 16384, function (index) { return compressed.charCodeAt(index) - 32; }); }, compress: function (uncompressed) { return LZString._compress(uncompressed, 16, function (a) { return f(a); }); }, _compress: function (uncompressed, bitsPerChar, getCharFromInt) { if (uncompressed === null) return ""; let i, value, context_dictionary = {}, context_dictionaryToCreate = {}, context_c = "", context_wc = "", context_w = "", context_enlargeIn = 2, // Compensate for the first entry which should not count context_dictSize = 3, context_numBits = 2, context_data = [], context_data_val = 0, context_data_position = 0, ii; for (ii = 0; ii < uncompressed.length; ii += 1) { context_c = uncompressed.charAt(ii); if (!Object.prototype.hasOwnProperty.call(context_dictionary, context_c)) { context_dictionary[context_c] = context_dictSize++; context_dictionaryToCreate[context_c] = true; } context_wc = context_w + context_c; if (Object.prototype.hasOwnProperty.call(context_dictionary, context_wc)) { context_w = context_wc; } else { if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) { if (context_w.charCodeAt(0) < 256) { for (i = 0; i < context_numBits; i++) { context_data_val = (context_data_val << 1); if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } } value = context_w.charCodeAt(0); for (i = 0; i < 8; i++) { context_data_val = (context_data_val << 1) | (value & 1); if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } else { value = 1; for (i = 0; i < context_numBits; i++) { context_data_val = (context_data_val << 1) | value; if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = 0; } value = context_w.charCodeAt(0); for (i = 0; i < 16; i++) { context_data_val = (context_data_val << 1) | (value & 1); if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } context_enlargeIn--; if (context_enlargeIn === 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } delete context_dictionaryToCreate[context_w]; } else { value = context_dictionary[context_w]; for (i = 0; i < context_numBits; i++) { context_data_val = (context_data_val << 1) | (value & 1); if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } context_enlargeIn--; if (context_enlargeIn === 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } // Add wc to the dictionary. context_dictionary[context_wc] = context_dictSize++; context_w = String(context_c); } } // Output the code for w. if (context_w !== "") { if (Object.prototype.hasOwnProperty.call(context_dictionaryToCreate, context_w)) { if (context_w.charCodeAt(0) < 256) { for (i = 0; i < context_numBits; i++) { context_data_val = (context_data_val << 1); if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } } value = context_w.charCodeAt(0); for (i = 0; i < 8; i++) { context_data_val = (context_data_val << 1) | (value & 1); if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } else { value = 1; for (i = 0; i < context_numBits; i++) { context_data_val = (context_data_val << 1) | value; if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = 0; } value = context_w.charCodeAt(0); for (i = 0; i < 16; i++) { context_data_val = (context_data_val << 1) | (value & 1); if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } context_enlargeIn--; if (context_enlargeIn === 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } delete context_dictionaryToCreate[context_w]; } else { value = context_dictionary[context_w]; for (i = 0; i < context_numBits; i++) { context_data_val = (context_data_val << 1) | (value & 1); if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } } context_enlargeIn--; if (context_enlargeIn === 0) { context_enlargeIn = Math.pow(2, context_numBits); context_numBits++; } } // Mark the end of the stream value = 2; for (i = 0; i < context_numBits; i++) { context_data_val = (context_data_val << 1) | (value & 1); if (context_data_position === bitsPerChar - 1) { context_data_position = 0; context_data.push(getCharFromInt(context_data_val)); context_data_val = 0; } else { context_data_position++; } value = value >> 1; } // Flush the last char while (true) { context_data_val = (context_data_val << 1); if (context_data_position === bitsPerChar - 1) { context_data.push(getCharFromInt(context_data_val)); break; } else context_data_position++; } return context_data.join(''); }, decompress: function (compressed) { if (compressed === null) return ""; if (compressed === "") return null; return LZString._decompress(compressed.length, 32768, function (index) { return compressed.charCodeAt(index); }); }, _decompress: function (length, resetValue, getNextValue) { let dictionary = [], next, enlargeIn = 4, dictSize = 4, numBits = 3, entry = "", result = [], i, w, bits, resb, maxpower, power, c, data = { val: getNextValue(0), position: resetValue, index: 1 }; for (i = 0; i < 3; i += 1) { dictionary[i] = i; } bits = 0; maxpower = Math.pow(2, 2); power = 1; while (power !== maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position === 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb > 0 ? 1 : 0) * power; power <<= 1; } switch (next = bits) { case 0: bits = 0; maxpower = Math.pow(2, 8); power = 1; while (power !== maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position === 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb > 0 ? 1 : 0) * power; power <<= 1; } c = f(bits); break; case 1: bits = 0; maxpower = Math.pow(2, 16); power = 1; while (power !== maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position === 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb > 0 ? 1 : 0) * power; power <<= 1; } c = f(bits); break; case 2: return ""; } dictionary[3] = c; w = c; result.push(c); while (true) { if (data.index > length) { return ""; } bits = 0; maxpower = Math.pow(2, numBits); power = 1; while (power !== maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position === 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb > 0 ? 1 : 0) * power; power <<= 1; } switch (c = bits) { case 0: bits = 0; maxpower = Math.pow(2, 8); power = 1; while (power !== maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position === 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb > 0 ? 1 : 0) * power; power <<= 1; } dictionary[dictSize++] = f(bits); c = dictSize - 1; enlargeIn--; break; case 1: bits = 0; maxpower = Math.pow(2, 16); power = 1; while (power !== maxpower) { resb = data.val & data.position; data.position >>= 1; if (data.position === 0) { data.position = resetValue; data.val = getNextValue(data.index++); } bits |= (resb > 0 ? 1 : 0) * power; power <<= 1; } dictionary[dictSize++] = f(bits); c = dictSize - 1; enlargeIn--; break; case 2: return result.join(''); } if (enlargeIn === 0) { enlargeIn = Math.pow(2, numBits); numBits++; } if (dictionary[c]) { entry = dictionary[c]; } else { if (c === dictSize) { entry = w + w.charAt(0); } else { return null; } } result.push(entry); // Add w+entry[0] to the dictionary. dictionary[dictSize++] = w + entry.charAt(0); enlargeIn--; w = entry; if (enlargeIn === 0) { enlargeIn = Math.pow(2, numBits); numBits++; } } } }; return LZString; })(); } /* eslint-enable */ }