PageSourceSearch

https://atlanticmanagement.com/wp-content/plugins/wp-google-maps/j…?ver=10.1.08+pro-10.0.07

js atlanticmanagement.com collected 2026-10-07 00:17:29 UTC 23,837 bytes, 623 lines download raw bytes

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.