1/** 2 * @namespace WPGMZA 3 * @module AtlasMajorAutoSave 4 * @requires WPGMZA 5 * 6 * Auto-save for the map edit page under Atlas Major. Hijacks the 7 * #wpgmaps_options form submit, saves via REST (POST 8 * /wpgmza/v1/maps/{id}/settings), and additionally polls every 10s 9 * for a dirty form. On AJAX failure the handler shows an error state, 10 * counts down 3s, then falls back to a native form submit so the user 11 * never loses work. 12 * 13 * Shared UX: a pill (.am-save-pill) in the top bar, right of the 14 * shortcode pill, has states: idle (hidden), saving, saved, error. 15 * "Saved" persists once set (Google Docs-style). 16 */ 17jQuery(function($) { 18 19 /** 20 * Resolve a localized string from WPGMZA.localized_strings, with an 21 * English fallback for the rare case where the server-side 22 * localization array is missing or hasn't been refreshed. 23 */ 24 function L(key, fallback){ 25 return (WPGMZA.localized_strings && WPGMZA.localized_strings[key]) || fallback; 26 } 27 28 if(WPGMZA.currentPage != "map-edit"){ 29 return; 30 } 31 32 if(!document.querySelector('.wpgmza-atlas-major')){ 33 return; 34 } 35 36 WPGMZA.AtlasMajorAutoSave = function(){ 37 var self = this; 38 39 this.form = $('#wpgmaps_options'); 40 if(!this.form.length){ 41 return; 42 } 43 44 this.pill = $('.am-save-pill'); 45 this.pauseToggle = $('.am-autosave-pause-toggle'); 46 this.saveButtons = $('label[for="wpgmza_savemap"]'); 47 this.saveButtonOriginalText = this.saveButtons.first().text(); 48 this.pollIntervalMs = 10000; 49 50 /* Paused state â when paused, autosave skips all automatic 51 * triggers (change events, interval ticks). Manual Save Map 52 * clicks still flush so the user can intentionally commit when 53 * they want to. 54 * 55 * DEFAULT IS PAUSED â autosave is opt-in. The user toggles it 56 * on via the top-bar pause button; the choice is persisted to 57 * localStorage so it sticks across reloads. The previous 58 * behaviour (autosave-on by default) was too aggressive: an 59 * autosave-on-init flush could write empty values for any form 60 * field that wasn't serialised, clobbering the saved record. */ 61 this.paused = true; 62 try { 63 if(window.localStorage && localStorage.getItem('wpgmza_autosave_enabled') === '1'){ 64 this.paused = false; 65 } 66 } catch(e){ } 67 68 /* Instant visual flip on click. Bound outside the grace period 69 * so the user always gets feedback, even if they click Save 70 * before our submit hijack is wired up (in which case the form 71 * submits natively and the page reloads, restoring the button). */ 72 var self2 = this; 73 this.saveButtons.on('click', function(e){ 74 if($(this).hasClass('is-disabled')){ 75 e.preventDefault(); 76 e.stopImmediatePropagation(); 77 return false; 78 } 79 self2.setSaveButtonState('saving'); 80 }); 81 this.fallbackDelayMs = 3000; 82 this.changeDebounceMs = 1500; // coalesce rapid changes (toggle spam, slider drags) 83 this.gracePeriodMs = 3000; // don't save in the first N ms after page load 84 85 this.inFlight = false; 86 this.pendingFlush = false; // set true if a save is requested while one is in flight 87 this.fallbackTimer = null; 88 this.changeTimer = null; 89 this.ready = false; // flips true after grace period; nothing is bound before this 90 91 /* Grace period â on initial load a lot of other JS initialises, 92 * populates form fields, fires synthetic change events, and 93 * occasionally triggers form.submit() programmatically for 94 * internal wiring. We don't want our form-submit hijack, our 95 * change listener, or the interval to interfere with any of 96 * that bootstrap. Nothing binds or fires until the grace 97 * period elapses. */ 98 setTimeout(function(){ 99 self.lastHash = self.hashForm(); 100 self.ready = true; 101 self.attachListeners(); 102 }, this.gracePeriodMs); 103 104 /* Pause toggle in the top bar (right of the save pill). Wire it 105 * up immediately (no grace period needed â the toggle just flips 106 * a flag, it doesn't trigger saves). */ 107 this.attachPauseToggle(); 108 this.refreshPauseToggleUI(); 109 110 /* "Remember to save your map!" pill â show for 8 seconds on 111 * every pan / zoom (when autosave is off). Implemented from 112 * autosave's own listener with a sliding hide timer so we 113 * don't depend on jQuery .show()/.hide() inline-style state. */ 114 this.attachReminderPillTimer(); 115 }; 116 117 /** 118 * Watch the form's view-state inputs (map_start_lat / map_start_lng / 119 * map_start_zoom). Whenever any value differs from the last polled 120 * snapshot, flash the "Remember to save your map!" pill for 8 121 * seconds. Subsequent changes reset the timer. 122 * 123 * Polling is intentionally dumb: it doesn't depend on jQuery's 124 * .show()/.hide() state, the map's event dispatcher, or 125 * MutationObserver â all of which I tried and could not get to fire 126 * reliably on every pan after the first. The inputs are updated 127 * directly by the editor's onBoundsChanged via .val(), so they are 128 * the canonical "what is the map showing" signal. 4 polls/sec is 129 * trivial overhead. 130 * 131 * Skipped silently when autosave is enabled. 132 */ 133 WPGMZA.AtlasMajorAutoSave.prototype.attachReminderPillTimer = function(){ 134 var self = this; 135 136 var pillEl = document.getElementById('wpgmaps_save_reminder'); 137 var latEl = document.querySelector('input[name="map_start_lat"]'); 138 var lngEl = document.querySelector('input[name="map_start_lng"]'); 139 var zoomEl = document.querySelector('input[name="map_start_zoom"]'); 140 if(!pillEl || !latEl || !lngEl || !zoomEl){ 141 /* DOM not ready yet â retry shortly. */ 142 setTimeout(function(){ self.attachReminderPillTimer(); }, 250); 143 return; 144 } 145 146 var last = { 147 lat: latEl.value, 148 lng: lngEl.value, 149 zoom: zoomEl.value 150 }; 151 152 setInterval(function(){ 153 /* === 1. Detect lat/lng/zoom changes (pan/zoom via .val()) === */ 154 var cur = { 155 lat: latEl.value, 156 lng: lngEl.value, 157 zoom: zoomEl.value 158 }; 159 var panZoomChanged = (cur.lat !== last.lat || cur.lng !== last.lng || cur.zoom !== last.zoom); 160 if(panZoomChanged) last = cur; 161 162 /* === 2. Detect anything else via form hash === 163 * The save-pill `unsaved` flip-trigger lives in BOTH the 164 * change/input listener AND here as a safety net â some 165 * settings UIs in the editor mutate inputs via .val() 166 * without dispatching a change event (custom toggles, 167 * pickers, sliders, etc.), so the listener alone misses 168 * them. Hashing four times per second is cheap and 169 * catches everything the listener doesn't. Only runs 170 * once `lastHash` has been initialised by the grace- 171 * period callback â before that, every tick would look 172 * "changed" and the pill would falsely flip to unsaved 173 * before the user has done anything. */ 174 var hashChanged = false;
175 if(self.ready && typeof self.lastHash !== 'undefined'){ 176 var cur_hash = self.hashForm(); 177 if(cur_hash !== self.lastHash){ 178 hashChanged = true; 179 } 180 } 181 182 var anyChange = panZoomChanged || hashChanged; 183 if(!anyChange) return; 184 185 /* Pan/zoom updates lat/lng/zoom via .val() which doesn't 186 * fire a `change` event â and some custom settings UIs 187 * do the same. Flip the save-pill to "unsaved" here so 188 * the green "Saved" can't stay stale across user 189 * changes. Runs in BOTH paused and active states; the 190 * existing 10s polling in attachListeners() handles the 191 * actual flush when autosave is on. */ 192 self.setPillState('unsaved'); 193 194 if(!self.paused) return; // autosave on â reminder pill is suppressed by CSS anyway 195 196 if(!panZoomChanged) return; // only flash the "Remember!" pill on pan/zoom, not generic form edits 197 198 /* Autosave off â show the "Remember to save your map!" 199 * pill for 8 seconds. Subsequent changes reset the timer. 200 * 201 * Force-clear any inline display on the inner card too â 202 * there are dormant fadeOut() calls in 203 * atlas-major-live-preview.js (initSaveReminder) that 204 * historically set display:none on `.am-save-reminder`, 205 * and similar leftovers could exist for other classes. 206 * Resetting both elements every flash makes this 207 * resilient regardless of what else touched them. */ 208 pillEl.style.display = 'block'; 209 var innerCard = pillEl.firstElementChild; 210 if(innerCard) innerCard.style.display = ''; 211 212 clearTimeout(self._reminderPillTimer); 213 self._reminderPillTimer = setTimeout(function(){ 214 pillEl.style.display = 'none'; 215 }, 8000); 216 }, 250); 217 }; 218 219 /** 220 * Bind the top-bar pause toggle button. Clicking flips paused 221 * state, persists to localStorage, and updates the button's visual 222 * state. The flag is also read at construction so a paused state 223 * survives page reloads. 224 */ 225 WPGMZA.AtlasMajorAutoSave.prototype.attachPauseToggle = function(){ 226 var self = this; 227 if(!this.pauseToggle.length) return; 228 this.pauseToggle.on('click', function(e){ 229 e.preventDefault(); 230 self.setPaused(!self.paused); 231 }); 232 }; 233 234 /** 235 * Update the pause toggle button's visual state to reflect 236 * `this.paused`. Adds/removes a `is-paused` class plus an 237 * accessible label + tooltip. The button itself is styled in 238 * atlas-major.css. 239 */ 240 WPGMZA.AtlasMajorAutoSave.prototype.refreshPauseToggleUI = function(){ 241 if(!this.pauseToggle.length) return; 242 this.pauseToggle.toggleClass('is-paused', !!this.paused); 243 var label = this.paused 244 ? L('atlas_major_autosave_paused_label', 'Autosave off') 245 : L('atlas_major_autosave_active_label', 'Autosave on'); 246 var title = this.paused 247 ? L('atlas_major_autosave_resume_tip', 'Click to enable autosave') 248 : L('atlas_major_autosave_pause_tip', 'Click to disable autosave (manual Save Map still works)'); 249 this.pauseToggle.attr({ 'aria-label': label, 'title': title }); 250 this.pauseToggle.find('.am-autosave-pause-text').text(label); 251 252 /* Mirror the autosave state onto <body> so CSS can scope the 253 * legacy "Remember to save your map!" pill to "autosave is OFF". 254 * When autosave is enabled, hide the pill immediately (any 255 * pending 8s timer is also canceled so it can't re-show). */ 256 $('body').toggleClass('wpgmza-autosave-on', !this.paused); 257 if(!this.paused){ 258 clearTimeout(this._reminderPillTimer); 259 $('#wpgmaps_save_reminder').hide(); 260 } 261 }; 262 263 /** 264 * Public setter â flip the paused flag, persist it to localStorage, 265 * and refresh the UI. Safe to call before listeners are attached 266 * (grace period hasn't elapsed). 267 */ 268 WPGMZA.AtlasMajorAutoSave.prototype.setPaused = function(paused){ 269 this.paused = !!paused; 270 try { 271 /* `wpgmza_autosave_enabled` is the opt-in flag. Absence 272 * (the default) means autosave is paused; '1' means the 273 * user has explicitly enabled it. */ 274 if(this.paused) localStorage.removeItem('wpgmza_autosave_enabled'); 275 else localStorage.setItem('wpgmza_autosave_enabled', '1'); 276 } catch(e){ } 277 this.refreshPauseToggleUI(); 278 }; 279 280 /** 281 * Bind all the form / interval listeners. Only called AFTER the
282 * grace period so it can't interfere with any JS that runs 283 * during initial page bootstrap (marker list render, live 284 * preview init, etc.). 285 */ 286 WPGMZA.AtlasMajorAutoSave.prototype.attachListeners = function(){ 287 var self = this; 288 289 /* "Unsaved changes" pill state â fires on every change/input 290 * regardless of paused state. Without this, the pill could 291 * stay green "Saved" indefinitely after the user toggled 292 * autosave off and made edits, falsely implying the map was 293 * up to date. The pill flips to amber "Unsaved changes" as 294 * soon as anything in the form changes; flush() resets it 295 * to "Saved" on success. Cheap â just attribute + text 296 * mutations on one element. */ 297 this.form.on('change input', function(){ 298 self.setPillState('unsaved'); 299 }); 300 301 /* Change events â coalesced with a debounce so a rapid burst 302 * of changes (toggle spam, slider drag, typing in text fields 303 * that fire change on blur) results in one save rather than 304 * many. The "did anything change" decision is made inside 305 * flush() via the hash comparison. */ 306 this.form.on('change', function(e){ 307 if(self.paused) return; 308 clearTimeout(self.changeTimer); 309 self.changeTimer = setTimeout(function(){ 310 self.changeTimer = null; 311 if(self.paused) return; 312 if(!self.inFlight){ 313 self.flush(/* triggeredByUser */ false); 314 } 315 }, self.changeDebounceMs); 316 }); 317 318 /* Hijack form submit â catches both "Save Map" <label for=...> 319 * buttons (top bar + bottom action bar), any keyboard-Enter 320 * submit, and anything else that triggers the form.submit() 321 * event. One binding covers everything. */ 322 this.form.on('submit', function(e){ 323 e.preventDefault(); 324 self.flush(/* triggeredByUser */ true); 325 }); 326 327 /* 10s tick â hash the form and compare to lastHash. If they 328 * differ, something changed and we flush. This is the safety 329 * net that catches changes the `change` event misses: 330 * - programmatic .val() writes (map pan/zoom updating the 331 * hidden lat/lng/zoom inputs, sliders setting values via 332 * jQuery UI, etc. â these don't fire `change` by default) 333 * - toggle switches whose click handlers suppress / delay 334 * the change event 335 * Hashing is cheap (a few ms for a ~100-field form). */ 336 setInterval(function(){ 337 if(self.paused) return; 338 if(self.inFlight) return; 339 if(self.hashForm() !== self.lastHash){ 340 self.flush(/* triggeredByUser */ false); 341 } 342 }, this.pollIntervalMs); 343 }; 344 345 /** 346 * Find the current map's ID from the most reliable source 347 * available at call time. Falls through: form's hidden input â 348 * WPGMZA.mapEditPage.map â `?map_id=` query param. 349 */ 350 WPGMZA.AtlasMajorAutoSave.prototype.resolveMapId = function(){ 351 var val = parseInt(this.form.find('input[name="map_id"]').val() || 0, 10); 352 if(val) return val; 353 354 if(WPGMZA.mapEditPage && WPGMZA.mapEditPage.map && WPGMZA.mapEditPage.map.id){ 355 return parseInt(WPGMZA.mapEditPage.map.id, 10); 356 } 357 358 var m = window.location.search.match(/[?&]map_id=(\d+)/); 359 if(m) return parseInt(m[1], 10); 360 361 return 0; 362 }; 363 364 /** 365 * Build a compact hash of the form's serialised state. Good enough 366 * to detect whether the user has actually changed anything â 367 * we skip hitting the server if the 10s tick fires but nothing 368 * has actually changed since the last snapshot. 369 */ 370 WPGMZA.AtlasMajorAutoSave.prototype.hashForm = function(){ 371 var pairs = this.form.serializeArray(); 372 var str = JSON.stringify(pairs); 373 var hash = 0; 374 for(var i = 0; i < str.length; i++){ 375 hash = ((hash << 5) - hash) + str.charCodeAt(i); 376 hash |= 0; 377 } 378 return hash + '_' + str.length; 379 }; 380 381 /** 382 * Fire the save request. If a save is already in flight, queue a 383 * follow-up to run immediately after. Rapid user clicks on "Save 384 * Map" never double-submit. 385 */ 386 WPGMZA.AtlasMajorAutoSave.prototype.flush = function(triggeredByUser){ 387 var self = this; 388 389 if(this.inFlight){ 390 this.pendingFlush = true; 391 return; 392 } 393 394 /* Skip if nothing has changed, unless the user explicitly hit 395 * Save â then we always send so they get the UX reassurance
396 * of a "Saved" pill update. */ 397 var currentHash = this.hashForm(); 398 if(!triggeredByUser && currentHash === this.lastHash){ 399 return; 400 } 401 402 /* Resolve the map id lazily â the hidden `map_id` input is 403 * populated at runtime by other JS, so reading it in the 404 * constructor would often give an empty value. Resolve it 405 * per-save and fall back through: form input â WPGMZA 406 * mapEditPage.map â URL param â the map instance's id. */ 407 var mapId = this.resolveMapId(); 408 if(!mapId){ 409 /* Nothing we can do yet â the next tick will try again. */ 410 return; 411 } 412 413 this.inFlight = true; 414 this.setPillState('saving'); 415 /* Flip the Save Map button to "Savingâ¦" for ALL flushes â both 416 * user-triggered (clicking Save) and autosave (debounced after a 417 * settings change). Previously only user-triggered showed the 418 * button state change, which made autosaves invisible to the 419 * user except via the small "Saved" pill. Showing the button 420 * state for autosaves makes it obvious that work is being 421 * persisted. */ 422 this.setSaveButtonState('saving'); 423 424 /* Convert serialised form pairs into a plain object. Repeating 425 * keys (e.g. checkbox arrays) would collide â but map settings 426 * don't use those today, so a flat object is fine. */ 427 var pairs = this.form.serializeArray(); 428 var payload = {}; 429 for(var i = 0; i < pairs.length; i++){ 430 payload[pairs[i].name] = pairs[i].value; 431 } 432 433 WPGMZA.restAPI.call('/maps/' + mapId + '/settings/', { 434 method: 'POST', 435 data: payload, 436 success: function(response){ 437 self.inFlight = false; 438 self.lastHash = currentHash; 439 440 /* Cancel any countdown/fallback from a previous failure. */ 441 if(self.fallbackTimer){ 442 clearTimeout(self.fallbackTimer); 443 self.fallbackTimer = null; 444 } 445 446 self.setPillState('saved'); 447 self.setSaveButtonState('idle'); 448 449 /* Hide the legacy "Remember to save your map!" nag â 450 * the change has been persisted, the user no longer 451 * needs to remember. Cancel any pending 8s timer too. */ 452 clearTimeout(self._reminderPillTimer); 453 $('#wpgmaps_save_reminder').hide(); 454 455 /* If another save was queued while this one was in 456 * flight, flush it now. */ 457 if(self.pendingFlush){ 458 self.pendingFlush = false; 459 self.flush(/* triggeredByUser */ false); 460 } 461 462 /* One-shot reload signal: the live-preview modal sets 463 * this when the user confirms "Save & Reload" for a 464 * setting that needs the editor reconstructed (e.g. 465 * custom_tile_enabled â leaflet CRS is locked at map 466 * construction). Wait for any queued recursive flush 467 * to also complete (its success will land here too 468 * with inFlight=false) before reloading, so we don't 469 * abort a save mid-request. */ 470 if(self.reloadAfterSave && !self.inFlight){ 471 self.reloadAfterSave = false; 472 window.location.reload(); 473 } 474 }, 475 error: function(xhr, status, err){ 476 self.inFlight = false; 477 /* lastHash is NOT updated on failure, so the next tick 478 * sees the same diff and retries automatically. */ 479 self.setPillState('error'); 480 /* Leave the save button in the 'saving' state â the 481 * fallback will trigger a native form submit shortly, 482 * which reloads the page (button restores naturally). 483 * If we restored it here it would briefly look idle 484 * during the 3s countdown which is misleading. */ 485 self.scheduleFallback(); 486 } 487 }); 488 }; 489 490 /** 491 * Schedule a native form submit as a 3s countdown fallback. The 492 * native submit hits admin-post.php?action=wpgmza_save_map, which 493 * is the same server-side handler the form has always used â so 494 * data is never lost when AJAX is broken. 495 */ 496 WPGMZA.AtlasMajorAutoSave.prototype.scheduleFallback = function(){ 497 var self = this; 498 if(this.fallbackTimer) return; 499 500 var remaining = Math.floor(this.fallbackDelayMs / 1000); 501 var template = L('atlas_major_save_failed_retrying', 'Save failed â retrying in %ds'); 502 this.updatePillText('error', template.replace('%d', remaining)); 503 504 this.fallbackTimer = setInterval(function(){ 505 remaining--; 506 if(remaining <= 0){ 507 clearInterval(self.fallbackTimer); 508 self.fallbackTimer = null; 509 /* Trigger the native submit. `form.submit()` on the 510 * DOM element bypasses our jQuery `submit` handler so 511 * we don't re-enter the AJAX path. */ 512 self.form.get(0).submit(); 513 return; 514 } 515 self.updatePillText('error', template.replace('%d', remaining)); 516 }, 1000); 517 }; 518 519 /** 520 * Visually toggle the "Save Map" button (a <label for="wpgmza_savemap"> 521 * â there are two of them: top bar + bottom action bar) between 522 * idle and a disabled "Savingâ¦" state. The label is the user-facing 523 * control; the underlying submit input stays hidden. 524 */ 525 WPGMZA.AtlasMajorAutoSave.prototype.setSaveButtonState = function(state){ 526 if(!this.saveButtons.length) return; 527 if(state === 'saving'){ 528 /* Don't change the button text â replacing "Save Map" with 529 * "Savingâ¦" makes the button wider, which on tight action-bar 530 * layouts (top-bar with Get Shortcode + Preview + Save Map) 531 * causes the Save button to wrap onto a new line. The 532 * .wpgmza-saving CSS class provides all the visual feedback 533 * (dark red background + pulsing glow + spinner badge) without 534 * changing the button's content or width. */ 535 this.saveButtons 536 .addClass('wpgmza-saving is-disabled') 537 .attr('aria-disabled', 'true')
538 .css('pointer-events', 'none'); 539 } else { 540 /* Idle â remove visual saving cues. No need to restore text 541 * since saving state never changed it (see comment above). */ 542 this.saveButtons 543 .removeClass('wpgmza-saving is-disabled') 544 .removeAttr('aria-disabled') 545 .css('pointer-events', ''); 546 } 547 }; 548 549 WPGMZA.AtlasMajorAutoSave.prototype.setPillState = function(state){ 550 if(!this.pill.length) return; 551 552 /* Don't downgrade transient in-flight states. `saving` and 553 * `error` represent the current request lifecycle; allowing 554 * a `change` event mid-save to flip the pill back to 555 * `unsaved` would lie about what's actually in flight. The 556 * success / error callbacks in flush() always settle to 557 * `saved` or `error` after the request, so the pill returns 558 * to a correct state regardless. */ 559 if(state === 'unsaved'){ 560 var current = this.pill.attr('data-state'); 561 if(current === 'saving' || current === 'error') return; 562 } 563 564 this.pill.attr('data-state', state); 565 566 var text = ''; 567 switch(state){ 568 case 'saving': text = L('atlas_major_saving', 'Savingâ¦'); break; 569 case 'saved': text = L('atlas_major_saved', 'Saved'); break; 570 case 'unsaved': text = L('atlas_major_unsaved', 'Unsaved changes'); break; 571 case 'error': text = L('atlas_major_save_failed', 'Save failed'); break; 572 case 'idle': text = ''; break; 573 } 574 this._writePillText(text); 575 }; 576 577 WPGMZA.AtlasMajorAutoSave.prototype.updatePillText = function(state, text){ 578 if(!this.pill.length) return; 579 this.pill.attr('data-state', state); 580 this._writePillText(text); 581 }; 582 583 /* Safety check: only write to the pill text node when the value 584 * actually differs from what's already there. setPillState() and 585 * updatePillText() get called many times in rapid succession during 586 * a window-resize drag â the map's onElementResized re-fires bounds 587 * updates that cascade through the 4Hz polling loop and the form 588 * change/input listener, each calling setPillState. Repeatedly 589 * writing the same string to a text node triggers layout work and 590 * was observed to visibly flicker the pill (and occasionally render 591 * the English fallback for one frame between German writes when the 592 * localized_strings lookup raced an in-flight resize tick). Caching 593 * the last-written value on the instance is cheaper than reading 594 * .text() on every call. */ 595 WPGMZA.AtlasMajorAutoSave.prototype._writePillText = function(text){ 596 if(this._lastPillText === text) return; 597 this._lastPillText = text; 598 this.pill.find('.am-save-pill-text').text(text); 599 }; 600 601 /* Boot. 602 * 603 * Autosave is OFF BY DEFAULT â users opt in via the top-bar pause 604 * button. The localStorage flag `wpgmza_autosave_enabled` records 605 * the user's choice ('1' = enabled, absent/anything else = paused). 606 * Read on construction so the choice survives page reloads. 607 * 608 * To manually enable / disable from the console: 609 * localStorage.setItem('wpgmza_autosave_enabled', '1') 610 * localStorage.removeItem('wpgmza_autosave_enabled') 611 * 612 * A separate hard kill switch â window.WPGMZA_DISABLE_AUTOSAVE â is 613 * kept for hard-disabling autosave construction entirely (e.g. when 614 * developing in DevTools without wanting any save behaviour at all). 615 */ 616 $(document).ready(function(){ 617 if(window.WPGMZA_DISABLE_AUTOSAVE === true){ 618 return; 619 } 620 WPGMZA.atlasMajorAutoSave = new WPGMZA.AtlasMajorAutoSave(); 621 }); 622 623});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.