PageSourceSearch

https://piperka.net/piperka.js?v=40165719f20e6e83a9dcabab8ebbbbe2

js piperka.net collected 2026-10-08 23:34:00 UTC 33,193 bytes, 1,127 lines download raw bytes

1function getCid() {
2    var cidRegex = /[?&]cid=(\d+)/;
3    var cid = cidRegex.exec(document.location);
4    return cid ? cid[1] : null;
5}
6
7function readUnreadMode() {
8    const newin = document.getElementById('newin');
9    if (newin && newin.dataset.stickyShowHidden === "1")
10	return 1;
11    return /[?&]show_hidden=1/.exec(document.location) ? 1 : 2;
12}
13
14const getunread = readUnreadMode();
15var csrf_ham = /csrf_ham=([0-9a-z-]+)/.exec(document.cookie);
16csrf_ham = csrf_ham ? csrf_ham[1] : null;
17const cid = getCid();
18
19function getUpdatedComics() {
20    var res = [];
21    document.querySelectorAll('li').forEach(function(li, i) {
22	if (li.dataset.cid !== undefined) {
23	    res.push({cid: parseInt(li.dataset.cid),
24		      n: parseInt(li.dataset.unread),
25		      href: li.children[0].href
26		     });
27	}
28    });
29    return res;
30}
31
32(function( $ ){
33    $.fn.tagSelect = function() {
34	$(this).on('change', function(){
35	    $('.maincategory').hide();
36	    var id = 'cat-'+$(this).find(':selected').val();
37	    $('#'+id).show();
38	});
39	return this;
40    };
41
42    $.fn.updatedComics = function(a,b,c) {
43	var cids = {};
44	$(this).find('li').each(function(x,y) {
45	    var cid = $(this).data('cid');
46	    if (cid) {
47		cids[cid] = true;
48	    }
49	});
50	return cids;
51    }
52
53    return this;
54})( jQuery );
55
56function submitSuccess(reply) {
57    if (typeof reply != 'undefined') {
58	$('#msgdiv').html('');
59	if (reply.ok) {
60	    $('.hideafterdone').slideUp();
61	    $('.removeafterdone').remove();
62	    $('#msgdiv').hide().html(reply.msg).slideDown();
63	} else if (reply.errmsg) {
64	    enableSubmitcomic();
65	    $('.submitcomic button[type=submit]').removeAttr('disabled');
66	    $('#msgdiv').hide().html(reply.errmsg).slideDown();
67	}
68    }
69}
70
71function submitError() {
72    enableSubmitcomic();
73    $('.submitcomic button[type=submit]').removeAttr('disabled');
74    $('#msgdiv').hide().html("Unexpected error").slideDown();
75}
76
77function enableSubmitcomic() {
78    var submitButton = $('.submitcomic button[type=submit]');
79    submitButton.removeAttr('disabled');
80    $('.submitcomic').one('submit', function() {
81	submitButton.attr('disabled', 1);
82	var options = {url: '/s/submit',
83		       type: 'POST',
84		       dataType: 'json',
85		       error: submitError,
86		       success: submitSuccess
87		      };
88	if (window.FormData) {
89	    options.data = new FormData(this);
90	    options.processData = false;
91	    options.contentType = false;
92	} else {
93	    options.data = $(this).serialize();
94	}
95	$.ajax(options);
96	return false;
97    });
98    if (window.FormData) {
99	$('.script.hasformdata').show();
100    } else {
101	$('.script.noformdata').show();
102    }
103}
104
105function setTagsEpedias(rpy) {
106    $('#tagdiff .diff').empty();
107    var origtags = {};
108    $.each(rpy.origtags, function(idx, tagid){
109	origtags[tagid] = true;
110    });
111    $('#tags-container')
112	.off('change', 'input[type=checkbox]')
113	.find('input[type=checkbox]').prop('checked', false);
114    $('#tags-container').on('change', 'input[type=checkbox]', function() {
115	var tagid = this.getAttribute('value');
116	const desc = $('label[for='+this.getAttribute('id')+']').text();
117	if (desc.startsWith('site:'))
118	    return;
119	if ((origtags[tagid] && this.checked) || (!origtags[tagid] && !this.checked)) {
120	    $('span#difftag-'+tagid).remove();
121	} else {
122	    var diffelem = document.createElement('span');
123	    diffelem.id = 'difftag-'+tagid;
124	    diffelem.textContent = desc;
125	    if (origtags[tagid]) {
126		$('#removedtags').show();
127		$('#removedtags .diff').append(diffelem);
128	    } else {
129		$('#addedtags').show();
130		$('#addedtags .diff').append(diffelem);
131	    }
132	}
133    });
134    var initialNewtags = {};
135    $.each(rpy.tags, function(idx, tagid){
136	$('#cat-sel-'+tagid).click();
137	initialNewtags[tagid] = true;
138    });
139    $.each(rpy.origtags, function(idx, tagid){
140	if (!initialNewtags[tagid])
141	    $('#cat-sel-'+tagid).trigger('change');
142    });
143    $.each(rpy.epedias, function(idx, dat) {
144	var epid = dat.epid || dat[0];
145	var entry = dat.entry || dat[1];
146	$('input[name=epedia-entry-'+epid+']').val(entry);
147    });
148}
149
150function resetSubmitForm() {
151    $('.submitcomic input[type=text]').val('');
152    $('.submitcomic textarea').val('');
153    $('.submitcomic input[type=checkbox]').removeAttr('checked');
154    $('.submitcomic').off('change');
155    $('#tagdiff .diff').html('');
156}
157
158function updateUnread(unread) {
159    if (unread.total_new > 0) {
160	var newin = $('#newin').show();
161	newin.text('('+unread.total_new+' new in '+unread.new_in+')');
162	var newinHeader = $('#unread-header').show();
163	newinHeader.text(unread.total_new+' new in '+unread.new_in);
164    } else {
165	$('#newin, #unread-header').hide();
166    }
167}
168
169// Also shows the account settings section that sets this.
170function getBookForward() {
171    const key = 'alwaysUseForward';
172    let hasStorage = false;
173    try {
174	const x = '__storage_test__';
175	window.localStorage.setItem(x, x);
176	window.localStorage.removeItem(x);
177	hasStorage = true;
178    } catch (e) {}
179    if (hasStorage) {
180	const alwaysUse = !!window.localStorage.getItem(key)
181	const el = document.getElementById('alwaysForwardBtn');
182	if (el) {
183	    el.checked = alwaysUse;
184	    addEventListener('change', function(event) {
185		if (event.target.checked) {
186		    window.localStorage.setItem(key, '1');
187		} else {
188		    window.localStorage.removeItem(key);
189		}
190	    });
191	}
192
193	$('#alwaysForwardBtnSection').show();
194	if (alwaysUse) {
195	    return '.booklink,.bookredir';
196	}
197    }
198    return '.booklink';
199}
200
201$(document).ready(function(){
202    var popup = $('.mobile #popup-container');
203    var menubuttonsvg = $('.mobile #menu-button');
204    var repage;
205    const bookForward = getBookForward();
206    $('.noscript').hide();
207    $('.script.show').show();
208    $('.scripthidden.show').removeClass('scripthidden');
209    $(bookForward).prepend($('<button type="button" class="bookmarkforward">&gt;</button>')
210			   .click(function(){
211			       var par = $(this).parent()
212			       const cid = par.data('cid');
213			       $.ajax({url: '/s/uprefs',
214				       data: {
215					   bookmark: [cid, 'max'],
216					   csrf_ham: csrf_ham,
217					   getunread: getunread
218				       }
219				      })
220				   .done(function(rpy) {
221				       if (rpy && rpy.ok) {
222					   par.fadeOut('fast');
223					   updateUnread(rpy);
224				       }
225				   });
226			   }));
227    if ($('.submitcomic').length > 0) {
228	if (cid != null && $('.submitcomic input[name="cid"]').length > 0) {
229	    $.ajax({url:'/s/cinfo/'+cid,
230		    dataType: 'json',
231		    success:function(rpy){
232			if (rpy != null) {
233			    rpy.origtags = rpy.tags;
234			    setTagsEpedias(rpy);
235			    enableSubmitcomic();
236			}
237		    }});
238	} else {
239	    enableSubmitcomic();
240	}
241    }
242
243    var hilitedialog;
244    var loadedtags = new Object;
245    var tagcounts = new Object;
246    $('form:has(#piperka_list)').on('hilite', '#piperka_list', function(){
247	$('#piperka_list li > a').each(function(){
248	    var cid = this.href.match(/\d+/);
249	    if (cid)
250		cid = cid[0];
251	    if (cid && tagcounts[cid] != undefined) {
252		var li = $(this).parent();
253		if (tagcounts[cid] > 0) {
254		    li.addClass('hilite');
255		} else {
256		    li.removeClass('hilite');
257		}
258	    }
259	});
260    });
261    $('#tagselect').tagSelect().trigger('change');
262    $('button.hilitetags').on('click', function() {
263	if (hilitedialog == undefined) {
264	    $.ajax({url: '/d/tags.xml',
265		    dataType: 'html',
266		    success: function(content){
267			hilitedialog = $('#hilitedialog').html(content);
268			hilitedialog.find('#tagselect').tagSelect().trigger('change');
269			var lastTop, lastLeft;
270			hilitedialog.dialog({appendTo: '#notmuch', dialogClass: 'isover', minWidth:200, width:400, open: function(){
271			    if (lastTop != undefined)  {
272				var style = this.parentNode.style;
273				style.top = lastTop;
274				style.left = lastLeft;
275			    }
276			}, close: function(){
277			    var style = this.parentNode.style;
278			    lastTop = style.top;
279			    lastLeft = style.left;
280			}});
281			hilitedialog.on('change', 'input', function() {
282			    function updateTagcounts(tagid, diff) {
283				$.each(loadedtags[tagid], function() {
284				    if (tagcounts[this] == undefined) {
285					tagcounts[this] = 0;
286				    }
287				    tagcounts[this] += diff;
288				});
289				$('#piperka_list').trigger('hilite');
290			    }
291			    var tagid = this.getAttribute('value');
292			    var diff = this.checked ? 1 : -1;
293			    if (loadedtags[tagid] == undefined) {
294				hilitedialog.find('.tagselector').hide();
295				$.ajax({url:'/s/tagslist/'+tagid,
296					method:'GET',
297					dataType:'json',
298					success:function(rpy){
299					    hilitedialog.find('.tagselector').show();
300					    if (rpy != null) {
301						if (rpy.cids) {
302						    loadedtags[tagid] = rpy.cids;
303						}
304					    }
305					    updateTagcounts(tagid, diff);
306					}});
307			    } else {
308				updateTagcounts(tagid, diff);
309			    }
310			});
311		    }});
312	} else {
313	    hilitedialog.dialog('open');
314	}
315    });
316
317    // Reader history chart
318    if ($('.chart').length > 0) {
319	var rhist = $('#readerchart');
320	if (cid != null && rhist.length > 0) {
321	    $.when($.ajax({url:'/d/readershistory/'+cid, method:'GET', dataType:'json'}))
322		.then(function(rpy){
323		    chart = rpy;
324		    if (rpy.length > 1) {
325			var day = new Date(), dateTicks = [];
326			for (var i=0; i < 5; ++i) {
327			    dateTicks[4-i] = day.toISOString().substring(0,10);
328			    day.setTime(day.getTime()-7*24*60*60*1000);
329			}
330			var maxReader = 0, minReader;
331			$.each(rpy, function() {
332			    var reader = this[1];
333			    minReader = minReader == null || reader < minReader ? reader : minReader;
334			    maxReader = maxReader < reader ? reader : maxReader;
335			});
336			var readerTicks = [minReader-1 < 0 ? 0 : minReader-1], lastTick = minReader-1, j=1;
337			for (var i=0; i < 5; ++i) {
338			    var num = Math.floor(minReader + (maxReader-minReader)/5*i);
339			    if (num != lastTick) {
340				lastTick = num;
341				readerTicks[j++] = num;
342			    }
343			}
344			readerTicks[j++] = maxReader;
345			readerTicks[j] = maxReader+1;
346			rhist.show();
347			$.jqplot('readerchart', [chart],
348				 {axes:{xaxis:{renderer: $.jqplot.DateAxisRenderer,
349					       ticks:dateTicks,
350					       tickRenderer: $.jqplot.CanvasAxisTickRenderer,
351					       tickOptions: {angle: -30, formatString: '%m-%d'}
352					      },
353					yaxis:{ticks:readerTicks,
354					       tickOptions:{formatString: '%i'}}
355				       },
356				  title:"Reader history"
357				 });
358		    }
359		});
360	}
361    }
362
363    if ($('#quicksearch').length > 0) {
364	var form = $('#quicksearch form');
365	form.on('submit', function() {
366	    var cid = form.data('cid');
367	    var sorttype = $('#quicksearch input[name=sorttype]').attr('value');
368	    if (cid) {
369		$.getJSON('/s/qsearch', {cid: cid, sorttype: sorttype}, function(rpy) {
370		    if (rpy && rpy.offset != undefined) {
371			if (repage != undefined) {
372			    repage(rpy.offset);
373			} else {
374			    if (sorttype == 'top') {
375				document.location = '/top.html?offset='+rpy.offset;
376			    } else {
377				document.location = '/browse.html?sort='+sorttype+'&offset='+rpy.offset;
378			    }
379			}
380		    }
381		});
382	    }
383	    return false;
384	});
385	$('#quicksearch').qsearch({
386	    select: function(event, ui) {
387		form.data('cid', ui.item.cid);
388		form.submit();
389	    }});
390    }
391
392    // AJAX subscribe buttons
393    $('form:has(#piperka_list)').on('click', 'button', function(event) {
394	event.preventDefault();
395	var name = this.getAttribute('name');
396	var subscid = this.getAttribute('value');
397	var button = this;
398	if (name == 'subscribe') {
399	    var startAtFirst = $('#atfirst:checked').length > 0;
400	    $.ajax({url: '/s/uprefs',
401		    method: 'POST',
402		    data: {bookmark: [subscid, startAtFirst ? 0 : 'max'], csrf_ham: csrf_ham, getunread: getunread},
403		    success: function(rpy) {
404			if (rpy && rpy.ord != undefined) {
405			    $(button).attr('name', 'unsubscribe').attr('class', 'minus').text('-');
406			    updateUnread(rpy);
407			}
408		    }
409		   });
410	} else if (name == 'unsubscribe') {
411	    $.ajax({url: '/s/uprefs',
412		    method: 'POST',
413		    data: {bookmark: [subscid, 'del'], csrf_ham: csrf_ham, getunread:getunread},
414		    success: function(rpy) {
415			if (rpy && rpy.ok) {
416			    $(button).attr('name', 'subscribe').attr('class', 'plus').text('+');
417			    updateUnread(rpy);
418			}
419		    }
420		   });
421	}
422    });
423
424    // AJAX paginate
425    if (!!(window.history && history.pushState) && $('.paginate').length > 0) {
426	var b = {start: $('.paginate .start'),
427		 prev: $('.paginate .prev'),
428		 next: $('.paginate .next'),
429		 end: $('.paginate .end')};
430	var getOffsetRegexp = /[?&].*offset=([0-9]+)/;
431	var getOffset = function(url) {
432	    var matches = getOffsetRegexp.exec(url);
433	    if (matches != undefined && matches.length > 1)
434		return parseInt(matches[1]);
435	    else
436		return null;
437	}
438	var getMaxcidRegexp = /&_max=([0-9]+)/;
439	var getMaxcid = function(url) {
440	    var matches = getMaxcidRegexp.exec(url);
441	    if (matches && matches.length > 1)
442		return parseInt(matches[1]);
443	    else
444		return null;
445	};
446	var offset = getOffset(window.location.href);
447	var sorttype;
448	if (window.location.pathname == '/top.html') {
449	    sorttype = 'top';
450	} else {
451	    sorttype = /[?&]sort=([a-z]+)/.exec(window.location.search);
452	    if (sorttype) {
453		sorttype = sorttype[1];
454		switch (sorttype) {
455		case 'top': case 'name': case 'new': case 'update': case 'hot': case 'recommend':
456		    break;
457		default:
458		    sorttype = 'name';
459		}
460	    } else {
461		sorttype = 'name';
462	    }
463	}
464	var prev = getOffset(b.prev.attr('href'));
465	var container = $('#piperka_list');
466	if (offset == undefined) {
467	    offset = 0;
468	}
469	var total, perPage = getOffset($(b.next).attr('href'));
470	if (perPage == undefined) {
471	    if (prev != undefined) {
472		perPage = offset-prev;
473		total = $('#piperka_list li').length+offset;
474	    }
475	} else {
476	    perPage -= offset;
477	    total = getOffset(b.end.attr('href'))+perPage;
478	}
479	if (total != undefined) {
480	    var lastAjax;
481	    repage = function(event) {
482		popup.slideUp();
483		menubuttonsvg.removeClass('active');
484		var newOffset, newSorttype, href, maxcidMarker = '';
485		if (typeof event == 'number') {
486		    newOffset = event;
487		    newSorttype = sorttype;
488		} else {
489		    event.stopPropagation();
490		    event.preventDefault();
491		    var forceUpdate = false;
492		    if (event.type == 'popstate') {
493			href = document.location.href;
494			if (event.originalEvent.state) {
495			    forceUpdate = true;
496			}
497		    } else {
498			href = this.getAttribute('href');
499			if (href == undefined)
500			    return;
501		    }
502		    newOffset = getOffset(href);
503		    if (window.location.pathname == '/top.html') {
504			newSorttype = 'top';
505		    } else {
506			newSorttype = /\?.*sort=([a-z]+)/.exec(href);
507			if (newSorttype)
508			    newSorttype = newSorttype[1];
509			else
510			    newSorttype = 'name';
511			switch (newSorttype) {
512			case 'top': case 'name': case 'new': case 'update': case 'hot': case 'recommend':
513			    if (newSorttype != sorttype) {
514				if (newSorttype == 'name') {
515				    $('#alphabet_index').show();
516				} else if (sorttype == 'name') {
517				    $('#alphabet_index').hide();
518				}
519				$('#quicksearch input[name=sorttype]').attr('value', newSorttype);
520			    }
521			    break;
522			default:
523			    newSorttype = sorttype;
524			}
525		    }
526		    if (newOffset == undefined) {
527			newOffset = 0;
528			var maxcid;
529			if (newSorttype == 'new' && (maxcid = getMaxcid(href)))
530			    maxcidMarker = '&_max='+maxcid;
531		    }
532		}
533		newOffset = newOffset < 0 ? 0 : newOffset >= total ? (total-1) : newOffset;
534		if (!forceUpdate && offset == newOffset && sorttype == newSorttype)
535		    return;
536		sorttype = newSorttype;
537		offset = newOffset;
538		if (window.location.pathname == '/top.html') {
539		    href = '/top.html?offset='
540		} else {
541		    var search = window.location.search
542			.replace(/[?&]offset=[^&#]*/, '')
543			.replace(/[?&]sort=[^&#]*/, '')
544			.replace(/[?&]_max=[^&#]*/, '');
545		    if (search == '') {
546			search = '?'
547		    } else {
548			search = search.replace(/^./, '?') + '&';
549		    }
550		    href = window.location.pathname + search + 'sort='+sorttype+'&offset=';
551		}
552		if (offset == 0) {
553		    b.start.removeAttr('href');
554		    b.prev.removeAttr('href');
555		} else {
556		    b.start.attr('href', href + '0');
557		    var newPrev = offset-perPage;
558		    if (newPrev < 0)
559			newPrev = 0;
560		    b.prev.attr('href', href + newPrev);
561		}
562		var newNext = offset+perPage;
563		if (newNext >= total) {
564		    b.next.removeAttr('href');
565		    b.end.removeAttr('href');
566		} else {
567		    b.next.attr('href', href + newNext);
568		    b.end.attr('href', href + (total-perPage));
569		}
570		container.fadeTo('slow', 0, 'linear');
571		if (lastAjax)
572		    lastAjax.abort();
573		href = href + offset + maxcidMarker;
574		lastAjax = $.ajax({url: href,
575				   dataType: 'html',
576				   data: {min: 1},
577				   success: function(content) {
578				       lastAjax = null;
579				       container.stop();
580				       var newContent = $(content);
581				       var newContainer = newContent.filter('#piperka_list');
582				       if (newContainer.length == 0)
583					   newContainer = newContent.find('#piperka_list');
584				       let newTotal = newContainer.data('total');
585				       if (newTotal && total != newTotal) {
586					   total = newTotal;
587					   b.end.attr('href', href.substring(0, href.indexOf('offset=')+7)+(total-perPage));
588				       }
589				       container.replaceWith(newContainer);
590				       container = newContainer;
591				       if (event.type != 'popstate')
592					   history.pushState(true, null, href);
593				       container.trigger('hilite');
594				   }});
595	    };
596	    $('.paginate').on('click', 'a', repage);
597	    $('#alphabet_index').on('click', 'a', repage);
598	    $('#sort_by').on('click', 'a', repage);
599	    $(window).on('popstate', repage);
600	    href = undefined;
601	    $('body').on('click', 'a', function(event) {
602		history.replaceState(false, $('title').text());
603		return true;
604	    });
605	}
606    }
607
608    $(".followcheck").on('change', 'input[type="checkbox"]', function(){
609	var type = this.getAttribute('name');
610	var name = this.getAttribute('value');
611	var checkbox = this;
612	var action;
613	if (type == 'followee') {
614	    action = this.checked ? 'permit' : 'deny';
615	} else if (type == 'follow') {
616	    action = this.checked ? 'follow' : 'unfollow';
617	}
618	if (action == undefined) {
619	    this.checked = !this.checked;
620	    return;
621	}
622	var docancel = function() {
623	    checkbox.checked = (action == 'permit' || action == 'follow') ? false : true;
624	};
625	this.setAttribute('disabled', true);
626	$.ajax({url: '/s/profile',
627		method: 'POST',
628		data: {action: action, name: name, csrf_ham: csrf_ham},
629		success: function(rpy) {
630		    if (!(rpy && rpy.rows && rpy.rows > 0)) {
631			docancel();
632		    }
633		    checkbox.removeAttribute('disabled');
634		},
635		failure: function(rpy) {
636		    docancel();
637		    checkbox.removeAttribute('disabled');
638		}
639	       });
640    });
641
642    $('#followee').on('click', 'button', function(){
643	var row = $(this).parents('tr');
644	var button = this;
645	this.setAttribute('disabled', true);
646	$.ajax({url: '/s/profile',
647		method: 'POST',
648		data: {action: 'unfollow', name: this.getAttribute('name'),  csrf_ham: csrf_ham},
649		success: function(rpy) {
650		    if (rpy.rows != undefined && rpy.rows > 0) {
651			row.fadeOut();
652		    } else {
653			button.removeAttribute('disabled');
654		    }
655		},
656		failure: function(rpy) {
657		    button.removeAttribute('disabled');
658		}
659	       });
660    });
661
662    $('span#interest').on('click', 'button', function() {
663	var action = this.getAttribute('id') == 'cancel' ? 'unfollow' : 'follow';
664	var button = this;
665	button.setAttribute('disabled', true);
666	$.ajax({url: '/s/profile',
667		method: 'POST',
668		data: {action: action, name: this.getAttribute('name'), csrf_ham: csrf_ham},
669		success: function(rpy) {
670		    if (rpy.rows != undefined && rpy.rows > 0) {
671			window.location.href = window.location.href;
672		    } else {
673			button.removeAttribute('disabled');
674		    }
675		}
676	       });
677    });
678
679    if ($('#ticket').length >
679 0 && $('#ticket').tabs().length > 0) {
680	$('input[name="jsok"]').val(1);
681    }
682
683    // Account management field disables and enables
684    var oldPasswdInput = $('input[name="_password"]');
685    $('#authenticate_with').on('change', function() {
686	if ($(this).val() == 'Password') {
687	    oldPasswdInput.removeAttr('disabled');
688	} else {
689	    oldPasswdInput.attr('disabled', 1);
690	}
691    });
692    var passwdInput = $('input[name="new_passwd"], input[name="new_passwd_retype"]');
693    $('#only_oauth2').on('change', function() {
694	var checkbox = this;
695	if (this.checked) {
696	    passwdInput.attr('disabled', 1);
697	} else {
698	    passwdInput.removeAttr('disabled');
699	}
700    });
701
702    // Switch links on updates page to point to Reader
703    var readerCheck = $('#openinreader');
704    if (readerCheck.length > 0) {
705	readerCheck.parent().show();
706	readerCheck.on('readerChange', function() {
707	    var rg = /redir=(\d+)/;
708	    var links = $('#piperka_list a');
709	    if (readerCheck[0].checked) {
710		links.each(function(idx, item) {
711		    var cid = rg.exec(item.href)[1];
712		    $(item).data('orig', [item.href, item.getAttribute('target')]);
713		    item.href = 'http://'+window.location.hostname+'/reader/?cid='+cid;
714		    item.setAttribute('target', 'reader');
715		});
716	    } else {
717		links.each(function(idx, item) {
718		    var orig = $(item).data('orig');
719		    item.href = orig[0];
720		    item.setAttribute('target', orig[1]);
721		});
722	    }
723	});
724	readerCheck.on('change', function() {
725	    readerCheck.trigger('readerChange');
726	});
727    }
728
729    var archive;
730    $('#info-archive').on('click', function() {
731	var userreq, subscriptions;
732	if (!archive) {
733	    $('#archivedialog').pPageDialog();
734	    $('#thumbdialog').pThumbDialog();
735	    $('#archivedialog').on('archive_select', 'tr:has(td)', function(ev, existing) {
736		if (!existing) {
737		    var info = $('#archivedialog').data();
738		    const page = $(this).data('page');
739		    var url;
740		    if (page === null) {
741			url = info.fixed_head ? info.fixed_head : info.homepage;
742		    } else {
743			url = info.url_base+$(this).data('page')+info.url_tail;
744		    }
745		    window.open(url, 'ArchivePage');
746		}
747	    })
748	    archive = makePageDialog(cid);
749	}
750	$.when(archive)
751	    .then(function(archive) {
752		$('#archivedialog').dialog('open');
753	    });
754	return false;
755    });
756
757    var updateWatch = $('#updatewatch');
758    var supportsWebSockets = 'WebSocket' in window || 'MozWebSocket' in window;
759    $('.updatewatch-message').hide().removeClass('scripthidden');
760    var updateWatchMsg = function(id) {
761	$('.updatewatch-message').hide();
762	$('#'+id).show();
763    };
764    var updateWatchCustomMsg = function(msg) {
765	$('.updatewatch-message').hide();
766	$('#updatewatch-msg-custom').text(msg).show();
767    };
768    if (csrf_ham && updateWatch.length > 0 && supportsWebSockets) {
769	$('#watch-messages').removeClass('script');
770	var tgl = $('#updatewatch-toggle');
771	tgl.prop('checked', false);
772	var schedule = [10000, 20000, 40000, 100000]
773	var updatedComics = $('#piperka_list').updatedComics();
774	var icon = $('link[rel="icon"]');
775	window.onfocus = function() {
776	    icon.attr('href', '/favicon.png');
777	};
778	var watchFailed = function() {
779	    updateWatchMsg('updatewatch-msg-connfailed');
780	    tgl.prop('checked', false);
781	};
782	var watchUpdating = function() {
783	    updateWatchMsg('updatewatch-msg-upd');
784	};
785	var watchReconn = function() {
786	    updateWatchMsg('updatewatch-msg-reconnecting');
787	};
788	function initUpdateWatchWs(reconnect) {
789	    ws = new WebSocket(wsUri);
790	    ws.onerror = function(ev) {
791		if (tgl.prop('checked')) {
792		    watchFailed();
793		}
794		console.log({x: 'error', ev: ev})
795	    };
796	    ws.onopen = function(ev) {
797		$('.updatewatch-message').hide();
798	    };
799	    ws.onclose = function(ev) {
800		if (!tgl.prop('checked')) {
801		    return;
802		}
803		if (schedule[reconnect]) {
804		    $('#updatewatch-msg-reconnecting').show();
805		    window.setTimeout(initUpdateWatchWs, schedule[reconnect], reconnect+1);
806		} else {
807		    $('#updatewatch-msg-connfailed').show();
808		}
809	    };
810	    ws.onmessage = function(ev) {
811		watchUpdating();
812		$.ajax({url: '/?min=1',
813			dataType: 'html',
814			success: function(content) {
815			    $('.updatewatch-message').hide();
816			    var newContent = $(content);
817			    $('#piperka_list').replaceWith(newContent.find('#piperka_list'));
818			    $('#newin').text(newContent.filter('#newin').text());
819			    var newComics = newContent.filter('#newcomics').children().detach();
820			    if (newComics.length > 0) {
821				$('#menu-button circle').addClass('newcomics');
822			    } else {
823				$('#menu-button circle').removeClass('newcomics');
824			    }
825			    $('#newcomics').empty().append(newComics);
826			    var newExpected = newContent.filter('#expected-updates');
827			    var oldExpected = $('#expected-updates');
828			    if (oldExpected.length) {
829				oldExpected.replaceWith(newExpected);
830			    } else if (newExpected.length) {
831				$('#pre-expected-updates').after(newExpected);
832			    }
833			    if (readerCheck[0].checked) {
834				readerCheck.trigger('readerChange');
835			    }
836			    var newUpdated = $('#piperka_list').updatedComics();
837			    var anyNew = Object.keys(newUpdated).some(function(cid) {
838				return !updatedComics[cid];
839			    });
840			    if (anyNew) {
841				icon.attr('href', '/favicon-updates.png');
842			    }
843			    updatedComics = newUpdated;
844			},
845			failure: function() {
846			    watchFailed();
847			}
848		       });
849	    };
850	    return ws;
851	}
852
853	updateWatch.removeClass('scripthidden');
854	var wsUri = 'wss://'+window.location.host+'/s/updateWatch/init/'+csrf_ham;
855	tgl.on('change', function(ev) {
856	    $('.updatewatch-message').hide();
857	    if (ev.target.checked) {
858		initUpdateWatchWs(0);
859	    } else {
860		ws.close();
861	    }
862	});
863    }
864    var supportsNotification = 'Notification' in window && navigator.serviceWorker;
865    if (csrf_ham && updateWatch.length > 0 && supportsNotification) {
866	$('#update-push-container').removeClass('scripthidden');
867	var tgl2 = $('#update-push-toggle');
868	var updateSubscription = function(subscription) {
869	    var keys = subscription.toJSON().keys;
870	    var call = $.ajax({url: '/s/push/subscribe',
871			       method: 'POST',
872			       data: {'csrf_ham': csrf_ham,
873				      'endpoint': subscription.endpoint,
874				      'auth': keys.auth,
875				      'p256dh': keys.p256dh
876				     }
877			      });
878	    return call;
879	};
880	// Determine if notifications start as enabled
881	navigator.serviceWorker.getRegistration('/push.js')
882	    .then(function(registration) {
883		if (registration && Notification.permission == 'granted') {
884		    // SW has been registered, proceed to check if
885		    // there's an existing push subscription.
886		    registration.pushManager.getSubscription()
887			.then(function(subscription) {
888			    if (subscription) {
889				updateSubscription(subscription)
890				    .catch(function(e) {
891					tgl2.prop('checked', false);
892					throw e;
893				    })
894				    .then(function() {
895					tgl2.prop('checked', true);
896				    });
897			    }
898			});
899		} else {
900		    // Explicit uncheck since browsers may restore an
901		    // old state for the checkbox.
902		    tgl2.prop('checked', false);
903		}
904	    });
905
906	tgl2.on('change', function(ev) {
907	    $('.updatewatch-message').hide();
908	    if (ev.target.checked) {
909		// Request permission
910		Notification.requestPermission(function(status) {
911		    if (Notification.permission == 'granted') {
912			var errored = false;
913			navigator.serviceWorker.register('/push.js')
914			    .then(function(reg) {
915				var key = atob(document.getElementById('update-push-toggle').dataset.pushkey);
916				var buf = new Uint8Array(key.length);
917				for (i = 0; i < key.length; ++i) {
918				    buf[i] = key.charCodeAt(i);
919				}
920				var options = {userVisibleOnly: true,
921					       applicationServerKey: buf};
922				reg.pushManager.subscribe(options)
923				    .catch(function(err) {
924					console.log(err);
925					updateWatchCustomMsg('Push subscription failed on browser');
926					errored = true;
927					tgl2.prop('checked', false);
928					throw err;
929				    })
930				    .then(updateSubscription)
931				    .catch(function(err) {
932					if (errored)
933					    return;
934					console.log(err);
935					updateWatchCustomMsg('Push subscription failed on server');
936					tgl2.prop('checked', false);
937				    });
938			    });
939		    } else {
940			updateWatchCustomMsg('No permission for notifications');
941			tgl2.prop('checked', false);
942		    }
943		});
944	    } else {
945		// Unsubscribe
946		navigator.serviceWorker.getRegistration('/push.js')
947		    .then(function(registration) {
948			if (!registration)
949			    return;
950			registration.pushManager.getSubscription()
951			    .then(function(subscription) {
952				if (!subscription)
953				    return;
954				var endpoint = subscription.endpoint;
955				subscription.unsubscribe()
956				    .then(function(success) {
957					$.ajax({url: '/s/push/unsubscribe',
958						method: 'POST',
959						data: {'csrf_ham': csrf_ham,
960						       'endpoint': endpoint
961						      }
962					       });
963				    });
964			    });
965		    });
966	    }
967	});
968    }
969
970    var editAuthor = $('#editAuthor');
971    if (editAuthor.length > 0) {
972	var comicTable = $('#comics');
973	$('#editAuthorForm').on('submit', function(ev) {
974	    $.ajax({url: '/s/editAuthor',
975		    method: 'POST',
976		    data: $(this).serialize()
977		   })
978		.done(function(res) {
979		    $('#editAuthorForm input[name=auid]').val(res.auid);
980		    $('#editAuthorForm button[type=submit]').text('Update');
981		    $('#error').text();
982		})
983		.fail(function() {
984		    $('#error').text('update/create failed');
985		});
986	    return false;
987	});
988	$('#editAuthorForm').on('click', 'button.delete', function(ev) {
989	    this.parentElement.parentElement.remove();
990	});
991	$('#editAuthorSearch').qsearch({
992	    select: function(event, ui) {
993		var form = this.parentElement;
994		form.dataset.cid = ui.item.cid;
995		form.dataset.label = ui.item.label;
996		$(form).submit();
997	    }});
998	$('#editAuthorSearch form').on('submit', function() {
999	    var cid = this.dataset.cid;
1000	    var label = this.dataset.label;
1001	    if ($('#editAuthorForm input[name="cid"][value="'+cid+'"]').length > 0)
1002		return false;
1003	    $('<tr/>')
1004		.data('cid', cid)
1005		.append($('<td/>')
1006			.text(cid)
1007			.append($('<input/>')
1008				.attr('type', 'hidden')
1009				.attr('name', 'cid')
1010				.attr('value', cid))
1011		       )
1012		.append($('<td/>')
1013			.append($('<a/>')
1014				.text(label)
1015				.attr('target', '_blank')
1016				.attr('href', 'info.html?cid='+cid))
1017		       )
1018		.append($('<td/>')
1019			.append($('<button>
1019Delete</button>')
1020				.attr('type', 'button')
1021				.attr('class', 'delete'))
1022		       )
1023		.appendTo($('#comics'));
1024	    return false;
1025	});
1026    }
1027
1028    // Epedias paste event
1029    $('#epedias').on('paste', 'input[type=text]', function(ev) {
1030	var paste = ev.originalEvent.clipboardData.getData('text/plain');
1031	var base = this.dataset.base;
1032	var tail = this.dataset.tail;
1033	if (paste.startsWith(base)) {
1034	    paste = paste.slice(base.length);
1035	}
1036	if (tail && paste.endsWith(tail)) {
1037	    paste = paste.slice(0, paste.length-tail.length);
1038	}
1039	this.value=paste;
1040	return false;
1041    });
1042
1043    $('.ads-question input').on('change', function() {
1044	$('.ads-question span.message').text('');
1045    });
1046    $('.ads-question button').on('click', function(el) {
1047	var consent;
1048	var refresh = false;
1049	if (el.target.name == 'ads') {
1050	    consent = el.target.value;
1051	    refresh = true;
1052	} else {
1053	    consent = $('.ads-question input:checked').val();
1054	}
1055	if (typeof(consent) != 'undefined') {
1056	    document.cookie="ads="+consent+";path=/;max-age=157680000; Secure; SameSite=Strict";
1057	    if (refresh) {
1058		document.location = 'ad_settings.html';
1059	    } else {
1060		$('.ads-question span.message').text('Done');
1061	    }
1062	}
1063    });
1064
1065    const unreadInNewTabs = document.getElementById('unreadInNewTabs');
1066    if (unreadInNewTabs) {
1067	const minNewTab = document.getElementById('minNewTab');
1068	const unreadTabOpenResult = document.getElementById('unreadTabOpenResult');
1069	unreadInNewTabs.onclick = function() {
1070	    const lim = minNewTab.valueAsNumber;
1071	    const tabs = getUpdatedComics()
1072		  .filter(function(c) {return c.n <= lim && c.n > 0;})
1073		  .map(function(update) {
1074		      return window.open(update.href, "Piperka Update "+update.cid);
1075		  });
1076	    if (tabs[0]) {
1077		tabs[0].focus();
1078	    }
1079	    unreadTabOpenResult.style.display = "inline";
1080	    unreadTabOpenResult.children[0].textContent = tabs.length;
1081	};
1082    }
1083
1084    // Mobile site
1085    $('.selectMobile').on('click', function() {
1086	var mobi = this.dataset.sel;
1087	document.cookie="mobi="+mobi+";path=/;max-age=157680000; Secure; SameSite=Strict";
1088	if (mobi == "1" && this.tagName == "BUTTON") {
1089	    $('#uncommittedMobileQuestion').hide(600);
1090	} else {
1091	    document.location=document.location;
1092	}
1093    });
1094
1095    var popupVisible = false;
1096    $('.mobile #page-controls').children().detach().appendTo(popup);
1097    popup.append('<hr/>');
1098    $('.mobile .sidebar').children().detach().appendTo(popup);
1099    $('.mobile #menu-button').on('click', function() {
1100	popup.slideToggle(300);
1101	menubuttonsvg.toggleClass('active');
1102    });
1103
1104    if (csrf_ham && /^https:\/\/[^/]+\/sso_redir.html/.exec(document.documentURI)) {
1105	$('form#sso').submit();
1106    }
1107
1108    var userHiatus = $('#user_hiatus');
1109    if (userHiatus.length > 0) {
1110	var hiatusButton = $('#user_hiatus button');
1111	userHiatus.attr('action', '/info.html?cid='+userHiatus.data('cid'));
1112	$('#user_hiatus select').on('change', function(ev) {
1113	    hiatusButton.prop('disabled', ev.target.value === '');
1114	});
1115
1116	userHiatus.on('submit', function() {
1117	    return !hiatusButton.prop('disabled');
1118	});
1119    }
1120
1121    // Moderator interface
1122    if ($.fn.pModerate) {
1123	$('#user-edits tbody').pModerate();
1124	$('#user-submits tbody').pSubmissions();
1125	$('#refresh-comiclists').pRefreshComicLists();
1126    }
1127});

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.