PageSourceSearch

https://placecloud.io/wp-content/themes/placecloud3/js/custom.js?ver=1787234975

js placecloud.io collected 2026-10-04 11:44:23 UTC 44,453 bytes, 1,544 lines download raw bytes

1//uppercase first letter of string
2function jsUcfirst(string) {
3	return string.charAt(0).toUpperCase() + string.slice(1);
4}
5
6//Generate a hash from a string
7function hashCode(s) {
8  return s.split("").reduce(function(a, b) {
9	a = ((a << 5) - a) + b.charCodeAt(0);
10	return a & a;
11  }, 0);
12}
13
14// WordPress REST titles are HTML-rendered strings. Decode their entities before
15// assigning them to text-only browser properties such as document.title.
16function decodeHtmlText(value) {
17	var textarea = document.createElement("textarea");
18	textarea.innerHTML = value || "";
19	return textarea.value;
20}
21
22function setCookie(cname, cvalue, exdays) {
23  const d = new Date();
24  d.setTime(d.getTime() + (exdays*24*60*60*1000));
25  let expires = "expires="+ d.toUTCString();
26  document.cookie = cname + "=" + cvalue + ";" + expires + ";path=/";
27}
28
29function getCookie(cname) {
30  let name = cname + "=";
31  let decodedCookie = decodeURIComponent(document.cookie);
32  let ca = decodedCookie.split(';');
33  for(let i = 0; i <ca.length; i++) {
34	let c = ca[i];
35	while (c.charAt(0) == ' ') {
36	  c = c.substring(1);
37	}
38	if (c.indexOf(name) == 0) {
39	  return c.substring(name.length, c.length);
40	}
41  }
42  return "";
43}
44
45//https://phuoc.ng/collection/html-dom/check-if-an-element-is-visible-in-a-scrollable-container/
46function isVisible(ele, container) {
47	const { bottom, height, top } = ele.getBoundingClientRect();
48	const containerRect = container.getBoundingClientRect();
49	
50	return top <= containerRect.top ? containerRect.top - top <= height : bottom - containerRect.bottom <= height;
51};
52
53(function($) {
54
55	//Serialize object function
56	$.fn.serializeObject = function() {
57		var o = {};
58		var a = this.serializeArray();
59		$.each(a, function() {
60			if (o[this.name]) {
61				if (!o[this.name].push) {
62					o[this.name] = [o[this.name]];
63				}
64				o[this.name].push(this.value || '');
65			} else {
66				o[this.name] = this.value || '';
67			}
68		});
69		return o;
70	};
71
72	//Toggle the loader
73	toggle_loader = function(element_id) {
74		var in_progress = $("#multi-loader").attr("data-loading");
75		
76		var elements = in_progress.split(",");
77		var ipn = [];
78		for(var i = 0; i < elements.length; i++) {
79			if (elements[i] != "") {
80				ipn.push(elements[i]);
81			}
82		}
83		elements = ipn;
84		
85		if ($.inArray(element_id, elements) == -1) {
86			elements.push(element_id);
87		}
88		else {
89			var res = $.grep(elements, function( a ) {
90				return a !== element_id;
91			});
92			elements = res;
93		}
94		in_progress = elements.join(",");
95		$("#multi-loader").attr("data-loading", in_progress);
96		
97		if (in_progress == "") {
98			$("#multi-loader").hide();
99		}
100		else {
101			$("#multi-loader").show();
102		}
103	}
104
105	//Load the user's bookmarks
106	function ajax_call(action, parameters, target, template) {
107		parameters.action = action;
108		console.log(parameters);
109		
110		var id = Date.now();
111		toggle_loader("collections-" + id);
112		
113		$.post(localizations.ajaxUrl, parameters, function(response) {
114			$(target).find(".loading").remove();
115			
116			response = $.parseJSON(response);
117			response.localizations = localizations;
118			console.log("user bookmarks", response);
119			
120			$(target).html(window[template](response));
121			
122			toggle_loader("collections-" + id);
123		});
124	}
125	function trigger_ajax_call(action, parameters, target, template) {
126		if (typeof(window[template]) !== 'undefined') {
127			ajax_call(action, parameters, target, template);
128		}
129		else {
130			setTimeout(function() {
131				trigger_ajax_call(action, parameters, target, template);
132			}, 1000);
133		}
134	}
135	$(document).on("click", ".js-trigger_ajax_call", function() {
136		var button = $(this);
137		var can_fetch = true;
138		
139		if (button.attr("data-logged_in") == "1") {
140			if ($("body.logged-in").length) {
141			}
142			else {
143				can_fetch = false;
144			}
145		}
146		
147		var parameters = {};
148		$.each(button.data(), function(k, v) {
149			if (k.substring(0, 9) == "parameter") {
150				var p = k.substring(10);
151				parameters[p] = v;
152			}
153		});
154				
155		if (can_fetch) {
156			console.log(parameters);
157			trigger_ajax_call(button.attr("data-action"), parameters, button.attr("data-target"), button.attr("data-template"));
158		}
159	});
160	
161	//What needs to run after loading a placecast
162	placecast_callback = function(result) {
163		console.log("Entering the placecast callback", result);
164		
165		//Pick up covers, if any
166		find_covers();
167		
168		//Track the view directly in Matomo. Logged-in views are also recorded in
169		//WordPress so the recommendation logic can avoid repetitions.
170		track_viewpoint_event("View", result.results.id);
171		if (parseInt($("body").attr("data-user"), 10) > 0) {
172			log_event("view_placecast", {post_id: result.results.id});
173		}
174		
175		//Switch to streetview
176		$("#nav-street_view-tab").trigger("click");
177		
178		var sv = result.results.acf.streetview[0];
179		var loc = result.results.acf.location;
180	
181		//Set up the streetview
182		$("#placecast-streetview").attr("data-lat", sv.latitude);
183		$("#placecast-streetview").attr("data-lng", sv.longitude);
184		$("#placecast-streetview").attr("data-heading", sv.heading);
185		$("#placecast-streetview").attr("data-pitch", sv.pitch);
186		placecloud_reset_streetview();
187		
188		//Set up the map
189		$("#placecast-map").attr("data-lat", result.results.acf.location.lat);
190		$("#placecast-map").attr("data-lng", result.results.acf.location.lng);
191		$("#placecast-map").attr("data-accredited", "0");
192		$("#placecast-map").attr("data-voted", "0");
193		$("#placecast-map").attr("data-new", "0");
194		$("#placecast-map").attr("data-icon", result.results.icon);
195		if (result.results.author.accreditations.length > 0) {
196			$("#placecast-map").attr("data-accredited", "1");
197		}
198		if (result.results.voted) {
199			$("#placecast-map").attr("data-voted", "1");
200		}
201		if (result.results.is_new) {
202			$("#placecast-map").attr("data-new", "1");
203		}
204		placecloud_reset_map();
205		
206		//Set the page title using plain text, not the REST API's encoded HTML.
207		document.title = decodeHtmlText(result.results.title.rendered);
208		
209		//Set the redirect url in the login form
210		$("#loginModal input[name='redirect_to']").val('https://placecloud.io/?viewpoint=' + result.results.id);
211		
212		//Set the redirect url in the logout button
213		$("#sign-out-link").attr("href", $("#sign-out-link").attr("href") + "&redirect_to=" + encodeURI('https://placecloud.io/?viewpoint=' + result.results.id));
214	}
215	
216	//What needs to run after loading 'my collections'
217	//Storing the collections in a global variable, to be used elsewhere.
218	my_collections_callback = function(result){
219		console.log(result);
220		
221		var current_placecast = $("#random-placecast").attr("data-id");
222		
223		set_collections_for_placecast(current_placecast);
224	}
225	
226	//Given a set of collections, set these as the collections the placecast can be added to
227	function set_collections_for_placecast(placecast_id) {
228		console.log("Setting user collections for placecast " + placecast_id);
229	
230		var data = {
231			action: "placecloud_placecast_user_collections",
232			post_id: placecast_id
233		};
234	
235		//toggle_loader("user-collections-" + placecast_id);
236	
237		//This could be done with a standard rest api call
238		$.post(localizations.ajaxUrl, data, function(response) {
239			response = $.parseJSON(response);
240			console.log("Placecast user collections", response);
241		
242			if (response.success) {
243				$(".user-collections .user-collection").remove();
244				
245				var uc = {
246					user_collections: response.result
247				}
248				$(".user-collections").prepend(user_collections_template(uc));
249			}
250			else {
251				console.log("Problem");
252			}
253			
254			//toggle_loader("user-collections-" + placecast_id);
255		});
256	}
257	
258	//Load all handlebars
259	render_handlebars = function(element,template,r,callback) {
260		if (typeof(window[template]) !== 'undefined') {
261			element.find(".loading").removeClass("d-flex").addClass("d-none");
262			element.find(".loading-once").addClass("d-none");
263			
264			//Append requires data-page to also be set
265			var append = false;
266//console.log("1");
267			if (typeof (element.attr("data-append")) !== "undefined") {
268//console.log("2");
269				if (element.attr("data-append") == "1") {
270//console.log("3");
271					if (typeof (element.attr("data-page")) !== 'undefined') {
272//console.log("4");
273						if (element.attr("data-page") != "1") {
274//console.log("5");
275							append = true;
276						}
277					}
278				}
279			}
280			
281			if (append) {
282				element.find(".content").append(window[template](r)).show();
283			}
284			else {
285				element.find(".content").html(window[template](r)).show();
286			}
287			
288			//Increase the page number
289			if (typeof (element.attr("data-page")) !== 'undefined') {
290				element.attr("data-page", parseInt(element.attr("data-page")) + 1);
291			}
292		
293			//console.log(callback);
294			eval(callback);
295		}
296		else {
297			setTimeout(function() {
298				render_handlebars(element,template,r,callback);
299			}, 1000);
300		}
301	}
302	load_handlebars = function() {
303		$( ".load-handlebars" ).each(function() {
304			var element = $(this);
305			
306			if (element.attr("data-loading") == "0" && element.attr("data-loaded") == "0") {
307				var current_id = Date.now(); //For loading and stuff
308				
309				console.log("Element to load", element.attr("id"));
310				
311				element.attr("data-loading", "1");
312				//element.find(".content").html("").hide();
313				element.find(".loading").removeClass("d-none").addClass("d-flex");
314				
315				var template = element.attr("data-template");
316				var post_id = element.attr("data-id");
317				var post_type = element.attr('data-post_type');
318				
319				var callback = false;
320				if (typeof element.attr("data-callback") !== "undefined") {
321					callback = element.attr("data-callback");
322				}
323				
324				var post_id = "";
325				if (typeof(element.attr("data-id")) !== 'undefined') {
326					if (element.attr("data-id") != "") {
327						post_id = element.attr("data-id");
328					}
329				}
330				
331				var nonce = "";
332				if (typeof($("body").attr("data-nonce")) !== 'undefined') {
333					if ($("body").attr("data-nonce") != "") {
334						nonce = '&_wpnonce=' + $("body").attr("data-nonce");
335					}
336				}
337				
338				var fields = "";
339				if (typeof(element.attr("data-fields")) !== 'undefined') {
340					if (element.attr("data-fields") != "") {
341						//_embedded does not work without _links
342						fields = '&_fields=' + element.attr("data-fields") + ",_embedded,_links";
343					}
344				}
345				
346				var page = "";
347				if (typeof(element.attr("data-page")) !== 'undefined') {
348					page = "&page=" + element.attr("data-page");
349				}
350				
351				var search = "";
352				if (typeof(element.attr("data-search")) !== 'undefined') {
353					search = "&search=" + element.attr("data-search");
354					element.find("form").addClass("disabled");
355				}
356
357				toggle_loader(template + "-" + post_type + "-" + current_id);
358
359				element.attr("data-loading", "1");				
360				
361				var this_is_a_lookup = false; //Whether we have to go through the votes
362				
363				var filter = "";
364				//placecast specific filters: language, tag
365				if (post_type == "placecast" && post_id == "") {
366					var lang = $("html").attr("lang").substring(0,2);
367					console.log("Filtering placecast on lang", lang);
368					filter = "&filter[meta_key]=language&filter[meta_value]=" + lang;
369				
370					if (typeof(element.attr("data-filter_type")) !== "undefined") {
371						var typex = element.attr("data-filter_type");
372						var idx = element.attr("data-filter_id");
373						
374						if (typex == "collection") {
375							//We need to get votes to find the placecasts
376							this_is_a_lookup = true;
377							
378							var post_typex = "vote";
379							var include_embedsx = "_embed&";
380							var include_fieldsx = "_fields=_links,_embedded,acf,persona&";
381							//var include_fields = "";
382							var include_searchx = "";
383							var filterx = "";
384							if (typex == "collection" && idx > 0) {
385								filterx = "&filter[meta_query][0][key]=collection&filter[meta_query][0][value]=" + idx + "&filter[meta_query][0][compare]==";
386							}
387							// else {
388							// 	filter = "&author=" + $("body").attr("data-user") + "&filter[meta_query][0][key]=collection&filter[meta_query][0][compare]=NOT%20EXISTS";
389							// }
390							var noncex = '';
391							if (typeof($("body").attr("data-nonce")) !== "undefined") {
392								noncex = "&_wpnonce=" + $("body").attr("data-nonce");
393							}
394							var json_urlx = localizations.restUrl + post_typex + "?" + include_embedsx + include_fieldsx + include_searchx + "page=" + page + filterx +  noncex;
395							console.log("votes for list", json_urlx);
396							
397							var jqxhr = $.get( json_urlx, {}, function(responsex) {
398								console.log("hearts for list", responsex);
399							
400								var temp_placecast_ids = [];
401								for (var i = 0; i < responsex.length; i++) {
402									temp_placecast_ids.push(responsex[i].acf.post[0]);
403								}
404							
405								if (temp_placecast_ids.length) {
406									//We have placecast ids
407									console.log("placecast ids for list", temp_placecast_ids);
408									
409									//Terribly inefficient. Almost same code as below. Should be streamlined.
410									//We need to retrieve by placecast IDs, and not set the page number
411									//post_id is not set
412									var include = "&include=" + temp_placecast_ids.join(",");
413									
414									var json_url = localizations.restUrl + post_type + "/?_embed" + filter + include + nonce + fields + search;
415									console.log(json_url);
416										
417									var jqxhr = $.ajax( json_url )
418									.done(function(response) {
419										var treat_as_done = false;
420										
421										if (response) {
422											if (Array.isArray(response)) {
423												if (response.length == 0) {
424													treat_as_done = true;
425												}
426											}
427										}
428										else {
429											treat_as_done = true;
430										}
431									
432										if (treat_as_done) {
433											if (typeof(element.attr("data-page")) !== 'undefined') {
434												r = {
435													results: 		[],
436													localizations:	localizations
437												};
438												
439												element.attr("data-loaded_all", "1")
440												render_handlebars(element, "no_more_results_template", r, callback);
441											}
442									
443										}
444										else {
445											r = {
446												results: 		response,
447												localizations:	localizations
448											};
449											console.log("rest response", r);
450											
451											//console.log(element, template, r, callback);
452											render_handlebars(element, template, r, callback);
453											
454										}
455									
456									})
457									.fail(function() {
458										//button.hide();
459									
460										if (typeof(element.attr("data-page")) !== 'undefined') {
461											r = {
462												results: 		[],
463												localizations:	localizations
464											};
465											
466											element.attr("data-loaded_all", "1")
467											render_handlebars(element, "no_more_results_template", r, callback);
468										}
469										
470									})
471									.always(function() {
472										element.attr("data-loading", "0");
473										element.attr("data-loaded", "1");
474									
475										if (typeof(element.attr("data-search")) !== 'undefined') {
476											element.find("form").removeClass("disabled");
477										}
478										
479										if (element.hasClass("monitor-scroll")) {
480											trigger_handlebars_if_visible(element.find(".trigger").attr("id"), element.attr("id"));
481										}
482										
483										toggle_loader(template + "-" + post_type + "-" + current_id);					
484									});
485								}
486							})
487							.done(function() {
488							})
489							.fail(function() {
490								//We have reached the end of the vote lookup
491								
492								if (typeof(element.attr("data-page")) !== 'undefined') {
493									r = {
494										results: 		[],
495										localizations:	localizations
496									};
497									
498									element.attr("data-loaded_all", "1")
499									render_handlebars(element, "no_more_results_template", r, callback);
500									
501									element.attr("data-loading", "0");
502									element.attr("data-loaded", "1");
503									
504									if (typeof(element.attr("data-search")) !== 'undefined') {
505										element.find("form").removeClass("disabled");
506									}
507									
508									if (element.hasClass("monitor-scroll")) {
509										trigger_handlebars_if_visible(element.find(".trigger").attr("id"), element.attr("id"));
510									}
511									
512									toggle_loader(template + "-" + post_type + "-" + current_id);	
513								}
514							})
515							.always(function() {
516								//toggle_loader("map-" + modalId + "-" + type + "-" + id + "-" + paged);
517							});
518							
519						}
520						if (typex == "book") {
521							filter += '&filter[meta_query][0][key]=books_alt&filter[meta_query][0][value]=%22' + idx + '%22&filter[meta_query][0][compare]=LIKE';
522						}
523						else {
524							filter += "&" + typex + "=" + idx;
525						}
526					}
527				}
528				// if (post_type == "collection" && post_id == "") {
529				// 	var lang = $("html").attr("lang").substring(0,2);
530				// 	console.log("Filtering collection on lang", lang);
531				// 	filter = "&filter[meta_key]=language&filter[meta_value]=" + lang;
532				// }
533				
534				if (!this_is_a_lookup) {
535					var json_url = localizations.restUrl + post_type + "/" + post_id + "?_embed" + filter + nonce + fields + page + search;
536					console.log(json_url);
537						
538					var jqxhr = $.ajax( json_url )
539					.done(function(response) {
540						var treat_as_done = false;
541						
542						if (response) {
543							if (Array.isArray(response)) {
544								if (response.length == 0) {
545									treat_as_done = true;
546								}
547							}
548						}
549						else {
550							treat_as_done = true;
551						}
552	
553						if (treat_as_done) {
554							if (typeof(element.attr("data-page")) !== 'undefined') {
555								r = {
556									results: 		[],
557									localizations:	localizations
558								};
559								
560								element.attr("data-loaded_all", "1")
561								render_handlebars(element, "no_more_results_template", r, callback);
562							}
563	
564						}
565						else {
566							r = {
567								results: 		response,
568								localizations:	localizations
569							};
570							console.log("rest response", r);
571							
572							//console.log(element, template, r, callback);
573							render_handlebars(element, template, r, callback);
574							
575						}
576	
577					})
578					.fail(function() {
579						//button.hide();
580	
581						if (typeof(element.attr("data-page")) !== 'undefined') {
582							r = {
583								results: 		[],
584								localizations:	localizations
585							};
586							
587							element.attr("data-loaded_all", "1")
588							render_handlebars(element, "no_more_results_template", r, callback);
589						}
590						
591					})
592					.always(function() {
593						element.attr("data-loading", "0");
594						element.attr("data-loaded", "1");
595	
596						if (typeof(element.attr("data-search")) !== 'undefined') {
597							element.find("form").removeClass("disabled");
598						}
599						
600						if (element.hasClass("monitor-scroll")) {
601							trigger_handlebars_if_visible(element.find(".trigger").attr("id"), element.attr("id"));
602						}
603						
604						toggle_loader(template + "-" + post_type + "-" + current_id);					
605					});
606				}
607			}
608			
609		});
610	}
611	load_handlebars();
612	$(document).on("click", ".js-set_handlebars_filter", function() {
613		var button = $(this);
614		var type = button.attr("data-type");
615		var id = button.attr("data-id");
616		var target = button.attr("data-target");
617		var href = button.attr("href");
618		
619		$(target).attr("data-filter_type", "");
620		$(target).attr("data-filter_id", "");
621		$(target).attr("data-page", "1");
622		$(target).find(".content").html("");
623		$(target).attr("data-loaded_all", "0");
624		
625		populate_list(href, type, id);
626		
627		//Hack
628		if (type == "tag") {
629			type = "placecast_tag";
630		}
631		
632		$(target).attr("data-filter_type", type);
633		$(target).attr("data-filter_id", id);
634		
635	});
636	$(document).on("click", ".js-trigger_handlebars", function() {
637		var button = $(this);
638		var target = button.attr("data-target");
639		
640		$(target).attr("data-loaded", "0");
641
642		load_handlebars();
643		
644		return false;
645	});
646	$(document).on("click", ".js-reset_placecast", function() {
647		var button = $(this);
648		
649		$(".modal").modal("hide");
650		$(".offcanvas").offcanvas("hide");
651		
652		$("#random-placecast").attr("data-id", button.attr("data-id"));
653		$("#random-placecast").attr("data-loaded", "0");
654		
655		load_handlebars()
656		
657		return false;
658	});
659	
660	/* Copy to clipboard */
661	$(document).on("click", ".js-copy_to_clipboard", function() {
662		
663		var button = $(this);
664		var target = button.attr("data-target");
665		console.log($(target).text());
666		
667		navigator.clipboard.writeText($(target).text())
668		  .then(() => { console.log(`Copied!`) })
669		  .catch((error) => { console.log(`Copy failed! ${error}`) })
670		
671		button.closest(".copy-wrap").find(".copy-confirmation").show();
672		setTimeout(function() {
673			button.closest(".copy-wrap").find(".copy-confirmation").fadeOut("fast");
674		}, 2500);
675		
676		return false;
677	})
678	
679	//Toggle a heart, follow
680	$(document).on("click", ".js-toggle_object", function(e) {
681		var button = $(this);
682		var post_type = button.attr("data-post_type");
683		var object_id = button.attr("data-object");
684		
685		meta = {
686			'id': 			button.attr("data-id"), 
687			'type': 		button.attr("data-type"), 
688			'collection':	button.attr("data-collection")
689		}
690		
691		button.addClass("disabled");
692		toggle_loader("toggle-object-" + post_type + "-" + object_id);
693		
694		//Use the WP rest API
695		if (button.hasClass("toggled-on")) {
696			
697			$.ajax({
698				method: "DELETE",
699				url: localizations.restUrl + post_type + "/" + object_id,
700				beforeSend: function ( xhr ) {
701					xhr.setRequestHeader( 'X-WP-Nonce', $("body").attr("data-nonce") );
702				}
703				
704			})
705			.done(function() {
706				console.log( "success" );
707				button.removeClass("toggled-on");
708				button.attr("data-object", "0")
709				if (post_type === "vote") {
710					$(document).trigger("placecloud:vote-changed");
711				}
712				//button.find(".toggles").text(-1 + parseInt(button.find(".toggles").text()));
713			})
714			.fail(function() {
715				console.log( "error" );
716			})
717			.always(function() {
718				console.log( "complete" );
719				
720				if (typeof(button.attr("data-reload")) !== 'undefined') {
721					$(button.attr("data-reload")).trigger("click");
722				}
723				else {
724					button.removeClass("disabled");
725				}
726				
727				toggle_loader("toggle-object-" + post_type + "-" + object_id);;
728	
729			});
730			
731		}
732		else {
733			var ourPostData = {
734				"title"		: "User " + post_type + " through REST API",
735				"status"	: "publish",
736				"meta"		: meta
737			}
738			
739			console.log(ourPostData);
740			
741			var createPost = new XMLHttpRequest();
742			
743			createPost.open("POST", localizations.restUrl + post_type);
744			createPost.setRequestHeader('X-WP-Nonce', $("body").attr("data-nonce"));
745			createPost.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
746			createPost.send(JSON.stringify(ourPostData));
747			createPost.onreadystatechange = function() {
748				if(createPost.readyState == 4) {
749					if( createPost.status == 201 ) {
750						console.log("Used REST API to create " + post_type);
751
752						var response = $.parseJSON(createPost.responseText);
753						console.log(response);
754
755						button.addClass("toggled-on");
756						button.attr("data-object", response.id)
757						if (post_type === "vote") {
758							$(document).trigger("placecloud:vote-changed");
759						}
760						//button.find(".toggles").text(1 + parseInt(button.find(".toggles").text()));
761					} else {
762						console.log('Error - Try again.');
763					}
764					
765					if (typeof(button.attr("data-reload")) !== 'undefined') {
766						$(button.attr("data-reload")).trigger("click");
767					}
768					else {
769						button.removeClass("disabled");
770					}
771					
772					toggle_loader("toggle-object-" + post_type + "-" + object_id);;
773	
774				}
775			}
776		}
777		
778		return false;
779	});
780	
781	//Find book covers
782	//Not showing indication that it is loading
783	find_covers = function() {
784		console.log("Attempting to get book covers");
785		$('.bookCover[data-found="0"]').each(function(i, obj) {
786			var cover = $(this);
787			var isbn = cover.attr("data-isbn");
788			var isbn13 = cover.attr("data-isbn13");
789			
790			var toUse = false;
791			if (isbn) {
792				toUse = isbn;
793			}
794			if (isbn13) {
795				toUse = isbn13;
796			}
797			console.log("ISBN", toUse);
798			if (toUse) {
799				$.getJSON( "https://openlibrary.org/api/books?bibkeys=ISBN:" + toUse + "&format=json&jscmd=data", function( data ) {
800					console.log(data);
801					
802					if (typeof data["ISBN:" + toUse] !== 'undefined') {
803						if (typeof data["ISBN:" + toUse].cover !== "undefined") {
804							var newImage = false;
805							if (typeof data["ISBN:" + toUse].cover.medium !== "undefined") {
806								newImage = data["ISBN:" + toUse].cover.medium;
807							}
808							if (typeof data["ISBN:" + toUse].cover.large !== "undefined") {
809								newImage = data["ISBN:" + toUse].cover.large;
810							}
811							
812							if (newImage) {
813								console.log(newImage);
814								cover.attr("src", newImage);
815
816								console.log("Bookcover replaced for ", toUse);
817							}
818						}
819					}
820					
821				});			
822			}
823			
824			cover.attr("data-found", "1");		
825		});
826	}
827	
828	//Get a random placecast
829	//Only retrieves a placecast ID, then passes it to #random-placecast
830	$(document).on("click", ".js-random_placecast", function() {
831		var button = $(this);
832		var match_type = $(this).attr("data-match_type");
833		var match_id = $(this).attr("data-match_id");
834		var post_id = $(this).attr("data-post_id");
835				
836		var data = {
837			action: "placecloud_placecast_id",
838			match_type: match_type,
839			match_id: match_id,
840			post_id: post_id,
841			language: localizations.language
842		};
843		
844		//Close modals and menus, in case open
845		$(".modal").modal("hide");
846		$(".offcanvas").offcanvas("hide");
847		
848		toggle_loader("random-placecast-" + match_type + "-" + match_id + "-" + post_id);
849		$("#random-placecast .loading").removeClass("d-none").addClass("d-flex");
850		
851		console.log(data);
852		
853		$.post(localizations.ajaxUrl, data, function(response) {
854			response = $.parseJSON(response);
855			console.log("Random pick up", response);
856		
857			if (response.success) {
858				$("#random-placecast").attr("data-id", response.post_id);
859				$("#random-placecast").attr("data-loaded", "0");
860				load_handlebars();
861	
862			}
863			else {
864				console.log("Problem");
865			}
866			
867			//$("#random-placecast .loading").removeClass("d-flex").addClass("d-none")
868			toggle_loader("random-placecast-" + match_type + "-" + match_id + "-" + post_id);
869		});
870		
871		return false;
872	});
873	
874	/* Create a new collection. */
875	$(document).on("click", ".js-new_collection", function() {
876		var button = $(this);
877		var y = button.closest("form").find("input[type='text']").val();
878		var x = y.trim();
879		if (x != "") {
880			
881			var vals = { 
882				title: x, 
883				status: "publish",
884				language: button.closest("form").find("input[name='language']").val()
885			};
886			
887			console.log(vals);
888			
889			button.addClass("disabled");
890			toggle_loader("new-collection");
891			
892			var post = new wp.api.models.Collection( vals );
893			post.save().then(function(p) {
894				
895				console.log("New collection", p);
896
897				toggle_loader("new-collection");
898				button.removeClass("disabled");
899				button.closest("form").find("input[type='text']").val("");
900				button.closest(".modal").modal("hide");
901				$("#my-collections").attr("data-loaded", "0");
902				load_handlebars();
903				
904			});
905		}
906		
907		return false;
908		
909	});
910	
911	//Load a recording and start playback.
912	$(document).on("click", ".js-load_recording", function(event) {
913		event.preventDefault();
914		var button = $(this);
915		var post_id = button.attr("data-id");
916		
917		if ($(".audio[data-id='" + post_id + "']").attr("data-recording-loaded") == "0") {
918			var data = {
919				action: "placecloud_recording",
920				post_id: post_id,
921			};
922			
923			console.log(data);
924			button.addClass("disabled");
925			button.find(".not-loading").hide();
926			button.find(".loading").removeClass("d-none");
927			
928			$.post(localizations.ajaxUrl, data, function(response) {
929				response = $.parseJSON(response);
930				console.log(response);
931				
932				if (response.success && response.url) {
933					button.removeClass("disabled");
934					button.closest(".player-alt").find(".play-pause").removeClass("d-none");
935					button.remove();
936
937					$(".audio[data-id='" + post_id + "'] audio").attr("src", response.url);
938					$(".audio[data-id='" + post_id + "']").attr("data-init", "0");
939					$(".audio[data-id='" + post_id + "']").attr("data-recording-loaded", "1");
940
941					initAudio(post_id);
942					$(".audio[data-id='" + post_id + "'] .play-pause").trigger("click");
943				}
944				else {
945					button.removeClass("disabled");
946					button.find(".not-loading").show();
947					button.find(".loading").addClass("d-none");
948					console.log("Problem getting player details.");
949				}
950				
951			});
952		}
953	});
954	
955	//Play buttons
956	initAudio = function(post_id) {
957		var limited_to = "";
958		if (typeof post_id !== 'undefined') {
959			limited_to = '[data-id="' + post_id + '"]';
960		}
961		
962		$('.audio[data-init="0"]' + limited_to).each(function(i, obj) {
963			var this_audio = $(this);
964			//var this_audio_id = $(this).attr("id");
965			
966			//console.log(this_audio_id);
967			
968			var audio = $(obj).find('audio')[0];
969
970			function formatAudioPosition(seconds) {
971				seconds = isFinite(seconds) && seconds > 0 ? Math.floor(seconds) : 0;
972				var minutes = Math.floor(seconds / 60);
973				var remainingSeconds = seconds % 60;
974				return minutes + ":" + (remainingSeconds < 10 ? "0" : "") + remainingSeconds;
975			}
976
977			function updateAudioPosition(showPosition) {
978				this_audio.find(".audio-position")
979					.text(formatAudioPosition(audio.currentTime))
980					.toggleClass("d-none", !showPosition);
981				this_audio.find(".audio-duration").toggleClass("d-none", showPosition);
982			}
983			
984			$(obj).attr("data-playing", "0");
985			$(obj).attr("data-playtime", "0");
986			$(obj).attr("data-play-tracked", "0");
987			$(obj).attr("data-listen-tracked", "0");
988			
989			$(obj).find(".play-pause").on("click", function(){
990				$(".play-pause").removeClass("d-none").addClass("d-flex");
991				
992				//$(this).find(".play-pause-icon").toggle();
993				$(".play-pause-icon.loading").hide();
994	
995				if (audio.paused) {
996					if ($(obj).attr("data-play-tracked") !== "1") {
997						track_viewpoint_event("Play", $(obj).attr("data-id"));
998						$(obj).attr("data-play-tracked", "1");
999					}
1000					
1001					audio.play();
1002					updateAudioPosition(true);
1003					
1004					$(obj).attr("data-playing", "1");
1005					
1006					// if (!$(obj).attr("data-started") == "1") {
1007					// 	$(obj).closest(".card").find(".streetviewCta").slideDown();
1008					// }
1009					
1010					$(".play-pause").removeClass("btn-success").addClass("btn-warning");
1011					$(".play-pause-icon.play").hide();
1012					$(".play-pause-icon.pause").show();
1013						
1014					$(obj).attr("data-started", "1");		
1015	
1016					console.log("play");
1017				}
1018				else {
1019					console.log(this_audio);
1020					
1021					audio.pause();
1022					updateAudioPosition(false);
1023					
1024					setTimeout(function() {
1025						$(".play-pause").removeClass("btn-warning").addClass("btn-success");
1026						$(".play-pause-icon.play").show();
1027						$(".play-pause-icon.pause").hide();
1028
1029					}, 250);
1030	
1031					$(obj).attr("data-playing", "0");
1032	
1033					console.log("pause");
1034				}
1035				
1036				//return false;
1037			})
1038			audio.ontimeupdate = function(){
1039				updateAudioPosition(!audio.paused && !audio.ended);
1040				//$(".play-pause").removeClass("btn-success").addClass("btn-warning");
1041				this_audio.find(".play-pause-icon.loading").hide();
1042				//this_audio.find(".play-pause-icon.play").hide();
1043				//this_audio.find(".play-pause-icon.pause").show();
1044				
1045				this_audio.find(".js-jump").removeClass("disabled");
1046				this_audio.find(".js-restart").removeClass("disabled");
1047
1048				$(obj).find('.progress-classic').css('width', audio.currentTime / audio.duration * 100 + '%')
1049				$(obj).find('.progress .progress-bar').css('width', audio.currentTime / audio.duration * 100 + '%')
1050				$(obj).find('.progress').attr("aria-valuenow", audio.currentTime / audio.duration * 100);
1051				
1052				//A listen is one playback session that reaches 30 seconds.
1053				if (audio.currentTime >= 30 && $(obj).attr("data-listen-tracked") !== "1") {
1054					track_viewpoint_event("Listen", $(obj).attr("data-id"));
1055					$(obj).attr("data-listen-tracked", "1");
1056				}
1057				
1058				if (audio.currentTime / audio.duration > 0.99) {
1059					$(obj).find(".play-pause-icon.play").show();
1060					$(obj).find(".play-pause-icon.pause").hide();
1061					$(".play-pause").removeClass("btn-warning").addClass("btn-success");
1062				}
1063				
1064			}
1065
1066			audio.onended = function() {
1067				updateAudioPosition(false);
1068				$(obj).attr("data-playing", "0");
1069				$(obj).attr("data-play-tracked", "0");
1070				$(obj).attr("data-listen-tracked", "0");
1071			}
1072		
1073			$(obj).find(".js-restart").on("click", function(){
1074				var new_time = 0;
1075				var was_playing = !audio.paused;
1076
1077				$(obj).attr("data-play-tracked", "0");
1078				$(obj).attr("data-listen-tracked", "0");
1079				audio.currentTime = new_time;
1080				if (was_playing) {
1081					track_viewpoint_event("Play", $(obj).attr("data-id"));
1082					$(obj).attr("data-play-tracked", "1");
1083				}
1084				
1085				return false;
1086			});
1087			
1088			$(obj).find(".js-jump").on("click", function(){
1089				var jump = parseInt($(this).attr("data-time"));
1090				var new_time = audio.currentTime + jump;
1091				if (new_time < 0) {
1092					new_time = 0;
1093				}
1094				if (new_time > audio.duration) {
1095					new_time = audio_duration;
1096				}
1097				 
1098				audio.currentTime = new_time;
1099				
1100				//return false;
1101			});
1102		
1103			$(obj).attr("data-init", "1'");
1104			$(obj).removeClass("disabled");
1105		});
1106	}
1107	
1108	//Track a viewpoint event using the Matomo tracker already loaded in header.php.
1109	track_viewpoint_event = function(action, post_id) {
1110		window._paq = window._paq || [];
1111		window._paq.push(['trackEvent', 'Viewpoint', action, String(post_id)]);
1112	}
1113
1114	//Record application state that still belongs in WordPress.
1115	log_event = function(event_type, parameters) {
1116		var data = {
1117			action: "placecloud_log_event",
1118			event: event_type,
1119			parameters: parameters
1120		};
1121		
1122		console.log("Event to log", data);
1123		
1124		$.post(localizations.ajaxUrl, data, function(response) {
1125			response = $.parseJSON(response);
1126			console.log("Event logged", response);
1127		});
1128	}
1129	
1130	//Trigger the help offcanvas
1131	$(document).on("click", ".js-trigger_help a", function() {
1132		$(".offcanvas").offcanvas("hide");
1133		$("#offcanvasHelp").offcanvas("show");
1134		
1135		return false;
1136	});
1137	
1138	//Search places
1139	$(document).on("click", ".js-search_places", function() {
1140		var button = $(this);
1141		var search_term = button.closest(".input-group").find(".field").val();
1142		
1143		var data = {
1144			action:		"placecloud_search_places",
1145			search: 	search_term
1146		}
1147		
1148		console.log(data);	
1149		
1150		$("#places-search-loading").show();
1151		$("#places-search-results").html("").hide();
1152		
1153		$.post(localizations.ajaxUrl, data, function(response) {
1154			response = $.parseJSON(response);
1155			console.log("search results", response);
1156			
1157			$("#places-search-loading").hide();
1158
1159			if (response.success) {
1160				response.localizations = localizations;
1161				
1162				$("#places-search-results").html(place_search_results_template(response)).show();
1163		
1164			}
1165			else {
1166				console.log("Problem getting search results.");
1167			}
1168		
1169		});
1170		
1171		return false;
1172	});
1173	
1174	//On the homepage, remove the logo
1175	if ($("body.home").length) {
1176		setTimeout(function() {
1177			$(".navbar-brand img").fadeOut();
1178		}, 30000);
1179	}
1180	
1181	//Trigger a modal, if relevant values were passed via the URL
1182	trigger = function(id) {
1183		console.log("triggering", id);
1184		var link = document.getElementById('trigger-modal');
1185		link.click();
1186	}
1187	if ($("#trigger-modal").length) {
1188		setTimeout(function() {
1189			trigger("#trigger-modal");
1190		}, 1000);
1191	}
1192	
1193	//Trash a collection
1194	$(document).on("click", ".js-delete_collection", function() {
1195		var button = $(this);
1196		var redirect = false;
1197	
1198		if (confirm(localizations.Sure)) {
1199	
1200			var data = {
1201				action: "placecloud_trash_collection",
1202				collectionId: button.attr("data-id"),
1203			};
1204	
1205			console.log(data);
1206	
1207			button.addClass("disabled");
1208			button.closest("tr").addClass("disabled");
1209			button.closest("tr").find(".btn").addClass("disabled");
1210			
1211			$.post(localizations.ajaxUrl, data, function(response) {
1212				response = $.parseJSON(response);
1213				console.log(response);
1214	
1215				if (response.success) {
1216					console.log("collection deleted");
1217					
1218					if (typeof button.attr("data-redirect") !== 'undefined') {
1219						redirect = button.attr("data-redirect");
1220					}
1221					
1222					if (redirect) {
1223						window.location.replace(redirect);
1224					}
1225					else {
1226						button.closest("tr").slideUp("fast", function() {
1227							button.closest("tr").remove();
1228						});
1229					}
1230				}
1231				else {
1232					alert(localizations.Problem);
1233					
1234					button.removeClass("disabled");
1235					button.closest("tr").removeClass("disabled");
1236					button.closest("tr").find(".btn").removeClass("disabled");
1237
1238				}
1239			});
1240			
1241		}
1242		
1243		return false;
1244	});
1245	
1246	//Fill collection edit form
1247	$(document).on("click", ".js-edit_collection", function() {
1248		var button = $(this);
1249		var id = button.attr("data-id");
1250		
1251		$("#collectionInputId").val(id);
1252		$("#collectionInputTitle").val(button.closest("tr").find(".title").text());
1253		$("#collectionInputDescription").val(button.closest("tr").find(".description").text());
1254		
1255		$("#collectionRoute").prop("checked", false);
1256		if ($("tr[data-id='" + id + "']").attr("data-route") == '1') {
1257			$("#collectionRoute").prop("checked", true);
1258		}
1259		
1260		return false;
1261	});
1262	
1263	//Submit collection edit form
1264	$(document).on("click", ".js-submit_collection", function() {
1265		var button = $(this);
1266		var values = button.closest("form").serializeArray();
1267		
1268		var data = {
1269			action: "placecloud_submit_collection",
1270			values: values,
1271		};
1272		
1273		console.log(data);
1274		
1275		button.closest("form").addClass("disabled");
1276		button.closest("form").find(".btn").addClass("disabled");
1277		
1278		$.post(localizations.ajaxUrl, data, function(response) {
1279			response = $.parseJSON(response);
1280			console.log(response);
1281		
1282			if (response.success) {
1283				console.log("collection updated");
1284				
1285				$(".btn-close").trigger("click");
1286				
1287				$("tr[data-id='" + response.v.collectionInputId + "']").find(".title").text(response.v.collectionInputTitle);
1288				$("tr[data-id='" + response.v.collectionInputId + "']").find(".description").text(response.v.collectionInputDescription.replace('\\', ''));
1289				
1290				$("tr[data-id='" + response.v.collectionInputId + "']").attr("data-route", "0");
1291				if (response.v.collectionRoute == "1") {
1292					$("tr[data-id='" + response.v.collectionInputId + "']").attr("data-route", "1");
1293				}
1294			}
1295			else {
1296				alert(localizations.Problem);
1297			}
1298			
1299			button.closest("form").removeClass("disabled");
1300			button.closest("form").find(".btn").removeClass("disabled");
1301		});
1302		
1303		return false;
1304	});
1305	
1306	//Prettify acf-input fields
1307	$( ".acf-input input[type='url']" ).addClass("form-control");
1308	
1309	//Process a placecaster request
1310	$(document).on("click", ".js-process_placecaster_request", function() {
1311		var button = $(this);
1312		
1313		var data = {
1314			action: "placecloud_process_placecaster_request",
1315			userId: button.attr("data-user"),
1316			process: button.attr("data-process")
1317		};
1318	
1319		console.log(data);
1320	
1321		button.closest("table").find("tr[data-user='" + button.attr("data-user") + "']").addClass("disabled");
1322	
1323		$.post(localizations.ajaxUrl, data, function(response) {
1324			response = $.parseJSON(response);
1325			console.log(response);
1326	
1327			button.closest("table").find("tr[data-user='" + button.attr("data-user") + "']").removeClass("disabled");
1328	
1329			if (response.success) {
1330				button.closest("tr").find("td.status").text(button.attr("data-process"));
1331				button.closest("td").find(".btn").remove();
1332	
1333				
1334				//button.closest("table").find("tr[data-user='" + button.attr("data-user") + "']").slideUp();
1335				//location.reload();
1336			}
1337			else {
1338				alert(pc_localizations.Problem);
1339				
1340				
1341					
1342			}
1343		});
1344	});
1345	
1346	$(document).on("click", ".js-vet_user", function() {
1347		var button = $(this);
1348		var user_id = button.attr("data-id");
1349		
1350		var data = {
1351			action: "placecloud_vet_user",
1352			user_id: user_id,
1353		};
1354		
1355		toggle_loader("toggle-vet");
1356		button.addClass("disabled");
1357		
1358		console.log(data);
1359		
1360		$.post(localizations.ajaxUrl, data, function(response) {
1361			response = $.parseJSON(response);
1362			console.log(response);
1363			
1364			button.removeClass("disabled");
1365			
1366			if (response.success) {
1367				button.remove();
1368			}
1369			else {
1370			}
1371			
1372			toggle_loader("toggle-vet");
1373		});
1374	});
1375	
1376	//If a file input changes, update the submit button
1377	$(document).on("change", "input[type='file']", function() {
1378		var input_field = $(this);
1379		
1380		if (input_field.val() != "") {
1381			console.log("Removing disabled from submit button");
1382			input_field.closest("form").find("[type='button']").prop("disabled", false);
1383		}
1384	});
1385	
1386	//Check for cookie acceptance
1387	function checkCookieAcceptance() {
1388		let accepted = getCookie("accept_cookies");
1389		if (accepted != "") {
1390		} else {
1391			$("#offcanvasCookie").offcanvas("show");
1392		}
1393		
1394	}
1395	setTimeout(function() {
1396		checkCookieAcceptance();
1397	}, 2000);
1398	$(document).on("click", ".js-accept_cookies", function() {
1399		$("#offcanvasCookie").offcanvas("hide");
1400		
1401		setCookie("accept_cookies", "1", 365);
1402		console.log("accepting cookies");
1403		
1404		return false;
1405	});
1406	function menuHelpHasBeenSeen() {
1407		try {
1408			return window.localStorage.getItem("placecloud_menu_help_seen") === "1";
1409		}
1410		catch (error) {
1411			return false;
1412		}
1413	}
1414
1415	function rememberMenuHelp() {
1416		try {
1417			window.localStorage.setItem("placecloud_menu_help_seen", "1");
1418		}
1419		catch (error) {
1420			// The callout will still dismiss for this page if storage is unavailable.
1421		}
1422	}
1423
1424	function dismissMenuHelp() {
1425		var help = $("#main-menu-help");
1426		rememberMenuHelp();
1427		help.removeClass("show");
1428		window.setTimeout(function() {
1429			help.addClass("d-none");
1430		}, 150);
1431	}
1432
1433	window.setTimeout(function() {
1434		var help = $("#main-menu-help");
1435		if (help.length && !menuHelpHasBeenSeen()) {
1436			help.removeClass("d-none");
1437			window.requestAnimationFrame(function() {
1438				help.addClass("show");
1439			});
1440		}
1441	}, 1200);
1442
1443	$(document).on("click", ".js-dismiss-menu-help", function() {
1444		dismissMenuHelp();
1445		return false;
1446	});
1447
1448	$(document).on("click", "#main-menu-toggle", function() {
1449		dismissMenuHelp();
1450	});
1451	
1452	//in-page login form
1453	$("#loginModal input.input").addClass("form-control");
1454	$("#loginModal input.button").addClass("btn btn-success w-100 text-white");
1455
1456	trigger_handlebars_if_visible = function(trigger_id, element_id) {
1457		
1458		var do_the_check = true;
1459		if (typeof ($("#" + element_id).attr("data-loaded_all")) !== "undefined") {
1460			if ($("#" + element_id).attr("data-loaded_all") == "1") {
1461				do_the_check = false;
1462			}
1463		}
1464		
1465		if (do_the_check) {
1466			//console.log(trigger_id, element_id);
1467			var el = document.getElementById(trigger_id);
1468			var cont = document.getElementById(element_id);
1469			
1470			if (isVisible(el, cont)) {
1471				
1472				if ($("#" + element_id).attr("data-loading") == "0") {
1473					$("#" + element_id).attr("data-loaded", "0");
1474					load_handlebars();
1475				}
1476			}
1477			else {
1478				//console.log("not visible", trigger_id);
1479			}
1480		}
1481
1482	}	
1483	
1484	//monitor scrolls. These are also load-handlebars, and presumably have paging and a trigger
1485	$( ".monitor-scroll" ).each(function( index ) {
1486		var element = $(this);
1487		var element_id = element.attr("id");
1488		var trigger = element.attr("data-trigger");
1489		var trigger_id = $(trigger).attr("id");
1490		
1491		//console.log("detecting scroll", trigger_id)
1492		element.scroll(function() {
1493			trigger_handlebars_if_visible(trigger_id, element_id);
1494		});
1495	});
1496	
1497	//search viewpoints
1498	$(document).on("click", ".js-search_placecasts", function() {
1499		var search_term = $(this).closest(".input-group").find(".search-field").val();
1500		
1501		console.log(search_term);
1502		
1503		$("#new-placecasts").attr("data-search", search_term.trim());
1504		$("#new-placecasts").attr("data-page", "1");
1505		$("#new-placecasts").attr("data-loaded", "0");
1506		$("#new-placecasts").attr("data-loaded_all", "0");
1507		load_handlebars();
1508		
1509		return false;
1510	});
1511	
1512	//Detect viewpoint search box change
1513	$('[data-id="placecast-search"]').on('input', function() {
1514		var current_search = $('[data-id="placecast-search"]').val().trim().toLowerCase();
1515		var most_recent_search = $("#new-placecasts").attr("data-search").trim().toLowerCase();
1516		
1517		if (current_search != most_recent_search) {
1518			if (current_search == "") {
1519				$("#new-placecasts").attr("data-search", "");
1520				$("#new-placecasts").attr("data-page", "1");
1521				$("#new-placecasts").attr("data-loaded", "0");
1522				$("#new-placecasts").attr("data-loaded_all", "0");
1523				load_handlebars();
1524			}
1525		}
1526	});
1527	
1528	//Toggle language in list
1529	$(document).on("click", ".js-language_list_toggle", function() {
1530		var button = $(this);
1531		var language = button.attr("data-language");
1532		
1533		console.log("language", language);
1534		$("body").toggleClass("hide-language-" + language);
1535		
1536	});
1537	$(document).on("click", ".js-placecast_micro_override", function() {
1538		var button = $(this);
1539		button.closest(".placecast-mini").toggleClass("placecast-micro-override");
1540		
1541		return false;
1542	});
1543	
1544})(jQuery);

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.