PageSourceSearch

https://retmig.dk/static/tool.js?1765806505

js retmig.dk collected 2026-10-02 14:03:24 UTC 27,514 bytes, 991 lines download raw bytes

1/*!
2 * Copyright 2016-2024 GrammarSoft ApS <[email protected]> at https://grammarsoft.com/
3 * All Rights Reserved
4 * Linguistic backend by Eckhard Bick <[email protected]>
5 * Frontend by Tino Didriksen <[email protected]>
6 */
7'use strict';
8
9function onResizeHandler() {
10	let ibr = $('#img-blue-rounded');
11	if (ibr.length) {
12		ibr.outerHeight('auto');
13		ibr.parent().outerHeight('auto');
14		ibr.outerHeight(ibr.parent().outerHeight());
15	}
16
17	let iframe = $('.tox-edit-area').find('iframe').first();
18	if (!iframe.length) {
19		return;
20	}
21
22	$('.tox-tinymce').css('border', '0');
23	$('.tox-tinymce').css('height', 'auto');
24	$('.tox-edit-area').height(300);
25	iframe.height(300);
26
27	let height = $('#container').innerHeight();
28	height -= $('#headbar').outerHeight();
29	height -= $('#footbar').outerHeight();
30	height -= $('#ed-head').outerHeight();
31	height -= $('#ed-foot').outerHeight();
32	height -= 40;
33	//height -= $('.mce-menubar.mce-toolbar').outerHeight();
34	if ($('.tox-editor-header:visible').length) {
35		height -= $('.tox-editor-header').outerHeight();
36	}
37	//height -= $('.mce-statusbar').outerHeight();
38	height -= 1;
39	$('.tox-edit-area').height(height);
40
41	iframe.height(height);
42	// iOS cannot resize iframes, so handle this case by making the container bigger instead
43	if (iframe.height() != height) {
44		// console.log('Height was not set');
45		let height = iframe.height();
46		$('.tox-edit-area').height(height);
47		iframe.parent().height(height);
48	}
49
50	if (tinyMCE && tinyMCE.activeEditor) {
51		$(tinyMCE.activeEditor.getBody()).css('min-height', height-20);
52	}
53
54	editorHasText();
55}
56
57function onResize() {
58	setTimeout(onResizeHandler, 500);
59	setTimeout(onResizeHandler, 1000);
60}
61
62function showInput() {
63	$('#do_input_text').val(ccomma.text());
64	let m = bootstrap.Modal.getOrCreateInstance(document.getElementById('dg_input'));
65	m.show();
66}
67
68$(function() {
69	shared_init();
70
71	tnjq = Object.keys(text_nodes).join(',');
72
73	tinymce.init({
74		selector: 'textarea',
75		menubar: false,
76		statusbar: false,
77		plugins: ['searchreplace'],
78		browser_spellcheck: true,
79		language: 'en',
80		entity_encoding: 'raw',
81		language_url: 'static/'+UILANG2+'.js',
82		content_css: 'static/inline.css?v=4e397eab',
83		valid_elements: '*[*]',
84		extended_valid_elements: '*[*]',
85		allow_script_urls: true,
86		//toolbar: ['undo redo | bold italic underline strikethrough removeformat | searchreplace | alignleft aligncenter alignright al
86ignjustify'],
87		setup: function(editor) {
88				editor.on('keyup paste change', function() {
89					if (eht_prevent) {
90						return;
91					}
92					//// console.log(eht_timer);
93					if (eht_timer) {
94						clearTimeout(eht_timer);
95					}
96					eht_timer = setTimeout(editorHasText, 250);
97				});
98				editor.on('copy', function() { commaIgnoreAll(); matomo_event('ui', 'copy'); });
99				editor.on('paste', function() { commaIgnoreAll(); setTimeout(cleanEditorContent, 500); matomo_event('ui', 'paste'); });
100				editor.on('undo', function() { commaIgnoreAll(); setTimeout(cleanEditorContent, 500); /* matomo_event('ui', 'undo'); */ });
101				editor.on('postRender', function() {
102					let iframe = $('iframe');
103					$('#working').show().offset(iframe.offset()).width(iframe.width()).height(iframe.height()).hide();
104					$('.tox-editor-header').hide();
105					onResize();
106
107					// Selective translations
108					if (tinymce && tinymce.i18n && tinymce.i18n.data && tinymce.i18n.data.da) {
109						tinymce.i18n.data.da['Blocks'] = 'Afsnit';
110						tinymce.i18n.data.da['Inline'] = 'Skrifttype';
111					}
112				});
113				editor.on('click', function(e) {
114					e.preventDefault();
115					let p = $(tinyMCE.activeEditor.getBody());
116					p.find('.popover').remove();
117					p.find('span.comma').popover('dispose').removeClass('comma-selected');
118
119					ccomma = null;
120					return false;
121				});
122			}
123		});
124
125	checkDone();
126
127	$('#btn-check,#btn-correct-all,#btn-wrong-all,#btn-accept-yellows,#btn-copy,#btn-erase,#btn-close').addClass('disabled');
128	$('#btn-check').click(function() {
129		checkAll();
130		matomo_event('ui', 'check');
131	});
132	$('#btn-correct-all').click(function () {
133		if ($(this).hasClass('disabled')) {
134			return;
135		}
136		matomo_event('ui', 'correct-all');
137		log_click('correct-all');
138		$(tinyMCE.activeEditor.getBody()).find('span.comma-green,span.comma-yellow,span.comma-info').each(function() {
139			ccomma = $(this);
140			commaAccept();
141		});
142	});
143	$('#btn-wrong-all').click(function () {
144		if ($(this).hasClass('disabled')) {
145			return;
146		}
147		matomo_event('ui', 'wrong-all');
148		log_click('wrong-all');
149		$(tinyMCE.activeEditor.getBody()).find('span.comma-red').each(function() {
150			ccomma = $(this);
151			commaDiscard();
152		});
153	});
154	$('#btn-accept-yellows').click(function () {
155		if ($(this).hasClass('disabled')) {
156			return;
157		}
158		matomo_event('ui', 'correct-all-yellow');
159		log_click('correct-all-yellow');
160		$(tinyMCE.activeEditor.getBody()).find('span.comma-yellow').each(function() {
161			ccomma = $(this);
162			commaYellow();
163		});
164	});
165
166	$('#btn-menu').popover({
167		container: document.body,
168		sanitize: false,
169		animation: false,
170		html: true,
171		placement: 'bottom',
172		fallbackPlacements: ['left', 'top', 'right', 'bottom'],
173		trigger: 'click',
174		});
175
176	$('#btn-erase').click(function() {
177		$(tinyMCE.activeEditor.getBody()).text('');
178		editorHasText();
179		matomo_event('ui', 'erase');
180	});
181	$('#btn-close').click(function() {
182		commaIgnoreAll();
183		matomo_event('ui', 'close');
184	});
185	$('#btn-copy').click(function() {
186		commaIgnoreAll();
187		$(tinyMCE.activeEditor.getBody()).focus();
188		tinyMCE.activeEditor.execCommand('selectAll');
189		if (true || !iOS) { // ToDo: Test on newer iOS
190			tinyMCE.activeEditor.execCommand('copy');
191		}
192		matomo_event('ui', 'copy');
193	});
194	$('#btn-transfer').click(transfer_document);
195
196	$('[data-bs-toggle="popover"]').popover();
197
198	let opt = $('#options');
199	opt.on('hide.bs.modal', function() { opt.find('.modal-body').html(''); });
200	opt.on('hidden.bs.modal', function() { setTimeout(impl_loadUserdata, 2000); });
201	$('#btn-options').click(function() {
202		opt.find('.modal-body').text('');
203		opt.find('.modal-body').html('<iframe src="'+ROOT_URL_SELF+'/html/options.html?tool='+g_tool+'&singletool=1&host=web"></iframe>');
204		let m = bootstrap.Modal.getOrCreateInstance(opt.get(0));
205		m.show();
206	});
207
208	$('#dg_input').on('shown.bs.modal', function() {
209		$('#do_input_text').focus();
210	});
211	$('#do_input_one').click(function() {
212		let txt = $('#do_input_text').val();
213		matomo_event('comma', 'input', txt);
214		log_marking_action({'a': 'input', 'm': ccomma.attr('data-types'), 'w': ccomma.text(), 'r': txt, 't': ccomma.attr('data-tid')});
215		commaDo(txt);
216	});
217	$('#do_input_all').click(function() {
218		let txt = $('#do_input_text').val();
219		matomo_event('comma', 'input', txt);
220		log_marking_action({'a': 'input', 'm': ccomma.attr('data-types'), 'w': ccomma.text(), 'r': txt, 't': ccomma.attr('data-tid')});
221		commaDo(txt);
222	});
223
224	$('#btn-undo').click(undo_restore).addClass('disabled');
225
226	$(window).on('resize', onResize);
227	$(window).on('beforeunload', function(e) {
228		// console.log(e);
229		if (tinyMCE && tinyMCE.activeEditor && $(tinyMCE.activeEditor.getBody()).find('span.comma').length) {
230			let msg = l10n_translate('MSG_WARN_CLOSE');
231			e.returnValue = msg;
232			return msg;
233		}
234	});
235
236	$('.warnTitle').click(function() {
237		alert($(this).attr('title'));
238		return true;
239	});
240
241	$('#working').hide();
242	$('#warning').hide();
243	$('#info').hide();
244	iOS = window.navigator.userAgent.indexOf('Apple') !== -1
245			&& (window.navigator.userAgent.indexOf('iPad') !== -1
246				|| window.navigator.userAgent.indexOf('iPhone') !== -1
247				|| window.navigator.userAgent.indexOf('iPod') !== -1)
248			;
249	android = (window.navigator.userAgent.indexOf('Android') !== -1);
250
251	if (iOS) {
252		$('#btn-copy').find('.text').text(l10n_translate('BTN_SELECT_ALL'));
253	}
254
255	onResizeHandler();
256	shared_hook();
257	// ToDo: Remember focus, scroll to focus
258
259	g_impl.showWarning = function (msg) {
260		$('#warning').html(l10n_translate(msg));
261		$('#warning').show();
262	};
263	g_impl.showError = function(msg) {
264		checkDone();
265		alert(l10n_translate(msg));
266	};
267
268	g_impl.parseCheckStart = function() {
269		if (ts_slow) {
270			clearTimeout(ts_slow);
271		}
272		ts_slow = setTimeout(check_slow, 5000);
273	};
274	g_impl.parseProgress = function() {
275		ts_fail = 0;
276		if (ts_slow) {
277			clearTimeout(ts_slow);
278		}
279		ts_xhr = null;
280		ts_slow = null;
281	};
282	g_impl.parseNoResult = function() {
283		let html = $(tinyMCE.activeEditor.getBody()).html();
284		if (ts_fail < 2) {
285			++ts_fail;
286			--to_send_i;
287			log_warning({'e': 'empty', 'd': to_send_last, 'l': html.length, 'b': html.substr(0, Defs.MAX_RQ_SIZE)});
288			sendTexts();
289			return 0;
290		}
291		log_error({'e': 'empty', 'd': to_send_last, 'l': html.length, 'b': html.substr(0, Defs.MAX_RQ_SIZE)});
292		alert(l10n_translate('ERR_EMPTY_RESPONSE'));
293	};
294	g_impl.parseChunkDone = function() {
295		for ( ; segments_i<segments.length ; ++segments_i) {
296			let id = segment_ids[segments_i];
297			let txt = '';
298			for (let j=0 ; j<segments[segments_i].length ; ++j) {
299				let w = segments[segments_i][j];
300				if (w.mark.length > 1) {
301					commaCreateInline(id, txt, w);
302				}
303				if (w.word.search(/^[-,.:;?!$*½§£$%&()={}+]$/) === -1) {
304					txt += ' ';
305				}
306				txt += w.word;
307			}
308		}
309
310		$('#btn-check,#btn-correct-all,#btn-wrong-all,#btn-accept-yellows,#btn-options,#btn-close').removeClass('disabled');
311		$('#working').hide();
312		let editor = $(tinyMCE.activeEditor.getBody());
313		if (editor.find('span.comma').length) {
314			editor.removeAttr('contenteditable');
315		}
316		if (!ccomma) {
317			let first = editor.find('span.comma').first();
318			if (first.length) {
319				first.focus().click();
320			}
321		}
322
323		if (to_send_i < to_send.length) {
324			sendTexts();
325		}
326		else {
327			if (!ccomma) {
328				doneOpen('#btn-check');
329			}
330		}
331	};
332	g_impl.parseError = function(e) {
333		// console.log(e);
334		let html = $(tinyMCE.activeEditor.getBody()).html();
335		log_error({'e': 'parse', 'd': to_send_last, 'l': html.length, 'b': html.substr(0, Defs.MAX_RQ_SIZE)});
336		$('#warning').html(l10n_translate('ERR_CRITICAL') + '</p><p>' + e);
337		$('#warning').show();
338		commaIgnoreAll();
339	};
340	g_impl.parseSendStart = function() {};
341	g_impl.parseSendEnd = function() {
342		setTimeout(function() {
343			parseResult({c:'', v:VERSION_PROTOCOL, t:0});
344		}, 100);
345	};
346
347	if (!g_impl.hasOwnProperty('beforeSendTexts')) {
348		g_impl.beforeSendTexts = function(t) { return t; };
349	}
350	if (!g_impl.hasOwnProperty('beforeParseResult')) {
351		g_impl.beforeParseResult = function(t) { return t; };
352	}
353});
354
355function editorHasText() {
356	//// console.log(eht_timer);
357	eht_timer = null;
358	if (!tinyMCE || !tinyMCE.activeEditor || eht_prevent) {
359		return;
360	}
361	let ed = tinyMCE.activeEditor.getBody();
362	let editor = $(ed);
363
364	if (editor.find('span.comma-green,span.comma-yellow').length) {
365		$('#btn-correct-all').removeClass('disabled');
366	}
367	else {
368		$('#btn-correct-all').addClass('disabled');
369	}
370
371	if (editor.find('span.comma-red').length) {
372		$('#btn-wrong-all').removeClass('disabled');
373	}
374	else {
375		$('#btn-wrong-all').addClass('disabled');
376	}
377
378	if (editor.find('span.comma').length) {
379		editor.removeAttr('contenteditable');
380	}
381	else {
382		editor.attr('contenteditable', true);
383		editor.css('overflow-y', '');
384		last_result = null;
385	}
386
387	if (ed.childNodes.length && ed.textContent.length) {
388		$('#btn-check,#btn-copy,#btn-erase').removeClass('disabled');
389		$('#placeholder').hide();
390	}
391	else {
392		$('#btn-check,#btn-copy,#btn-erase').addClass('disabled');
393		$('#placeholder').show();
394	}
395
396	$(ed).find('span.comma').popover('dispose');
397	ccomma = null;
398}
399
400let g_undos = [];
401
402function undo_save() {
403	g_undos.push($(tinyMCE.activeEditor.getBody()).contents().clone(true, true));
404	while (g_undos.length > 10) {
405		g_undos.shift();
406	}
407	$('#btn-undo').removeClass('disabled');
408	// console.log(g_undos);
409}
410
411function undo_restore() {
412	let u = g_undos.pop();
413	if (u) {
414		let b = tinyMCE.activeEditor.getBody();
415		$(b).contents().remove();
416		$(b).append(u);
417	}
418	if (!g_undos.length) {
419		$('#btn-undo').addClass('disabled');
420	}
421	// console.log(g_undos);
422}
423
424function doneOpen(which) {
425	$(which).popover('show');
426	setTimeout(function() { $(which).popover('hide'); }, 4000);
427}
428
429function commaAddToDict() {
430	let word = ccomma.text();
431	addToDictionary(word);
432
433	let p = $(tinyMCE.activeEditor.getBody());
434	let cs = p.find('span.comma-yellow').each(function() {
435		ccomma = $(this);
436		if (ccomma.text() !== word && ccomma.text().toUpperCase() !== word.toUpperCase()) {
437			return;
438		}
439		commaYellow();
440	});
441	matomo_event('ui', 'dict-add', word);
442	log_marking_action({'a': 'dict-add', 'w': word, 't': ccomma.attr('data-tid')});
443}
444
445function commaPopup(c, exp) {
446	let b = tinyMCE.activeEditor.getBody();
447	let p = $(b);
448	p.find('.popover').remove();
449	p.find('span.comma').popover('dispose').removeClass('comma-selected');
450
451	// If this comma's popup is the open one, just close it
452	if (!exp && ccomma && ccomma.get(0) === c) {
453		ccomma = null;
454		return;
455	}
456	ccomma = $(c);
457	ccomma.focus().addClass('comma-selected');
458	//// console.log([c.offset(), c.width(), p.width()]);
459
460	let text = ccomma.text();
461	let all_upper = is_upper(text);
462	let first_upper = all_upper || is_upper(text.charAt(0));
463
464	let types = ccomma.attr('data-types');
465	if (g_marks.to_lower.test(types)) {
466		all_upper = first_upper = false;
467	}
468
469	let html = '<div id="popup">';
470	if (!ccomma.attr('data-sugs') && (text === ',' || text === '.')) {
471		let tgreen = l10n_translate('BTN_COMMA_INSERT');
472		let tred = l10n_translate('BTN_IGNORE');
473		if (g_marks.rx_del.test(types) || ccomma.hasClass('comma-red') || ccomma.hasClass('comma-info')) {
474			tgreen = l10n_translate('BTN_COMMA_KEEP');
475			tred = l10n_translate('BTN_COMMA_REMOVE');
476		}
477		if (ccomma.attr('data-types').indexOf('%k-stop') != -1) {
478			tgreen = l10n_translate('BTN_COMMA_INSERT_STOP');
479		}
480
481		if (ccomma.hasClass('comma-red')) {
482			tred = '<b>'+tred+'</b>';
483		}
484		else {
485			tgreen = '<b>'+tgreen+'</b>';
486		}
487		if (g_marks.rx_del.test(types)) {
488			html += '<div class="action"><a class="commaDiscard"><span class="icon icon-accept"></span><span>'+tgreen+'</span></a></div>';
489			html += '<div class="action"><a class="commaAccept"><span class="icon icon-discard"></span><span>'+tred+'</span></a></div>';
490		}
491		else {
492			html += '<div class="action"><a class="commaAccept"><span class="icon icon-accept"></span><span>'+tgreen+'</span></a></div>';
493			html += '<div class="action"><a class="commaDiscard"><span class="icon icon-discard"></span><span>'+tred+'</span></a></div>';
494		}
495	}
496	else {
497		let crs = ccomma.attr('data-sugs').split('\t');
498		for (let c=0 ; c<crs.length ; ++c) {
499			if (crs[c].length === 0) {
500				continue;
501			}
502			let txt = crs[c];
503			if (all_upper) {
504				txt = txt.toUpperCase();
505			}
506			else if (first_upper) {
507				txt = uc_first(txt);
508			}
509			html += '<div class="action"><a class="commaAccept"><span class="icon icon-accept"></span><span>'+escHTML(txt)+'</span></a></div>';
510		}
511		if (g_marks.rx_del.test(types)) {
512			html += '<div class="action"><a class="commaAccept"><span class="icon icon-discard"></span><span>'+l10n_translate('BTN_GRAMMAR_REMOVE')+'</span></a></div>';
513		}
514		if (g_marks.rx_ins.test(types)) {
515			html += '<div class="action"><a class="commaAccept"><span class="icon icon-accept"></span><span>'+l10n_translate('BTN_GRAMMAR_INSERT')+'</span></a></div>';
516		}
517		if (g_marks.dict.test(types)) {
518			html += '<div class="action"><a class="commaAddToDict"><span class="icon icon-accept"></span><span>'+l10n_translate('BTN_DICT_ADD')+'</span></a></div>';
519		}
520		if (types.indexOf('£question') !== -1) {
521			html += '<div class="action"><a class="commaDiscard"><span class="icon icon-accept"></span><span>Ok</span></a></div>';
522		}
523		else {
524			html += '<div class="action"><a class="showInput"><span class="icon icon-accept"></span><span>'+l10n_translate('BTN_EDIT')+'</span></a></div>';
525			html += '<div class="action"><a class="commaDiscard"><span class="icon icon-discard"></span><span>'+l10n_translate('BTN_IGNORE')+'</span></a></div>';
526		}
527	}
528	if (android || iOS) {
529		html += '<div class="action"><a class="commaIgnoreAll"><span class="icon icon-edit"></span><span>'+l10n_translate('BTN_EDIT_TEXT')+'</span></a></div>';
530	}
531	if (!exp) {
532		html += '<div class="action"><a class="commaExplain"><span class="icon icon-explain"></span><span>'+l10n_translate('BTN_EXPL_SEE')+'</span></a></div>';
533	}
534	html += '</div>';
535	if (exp) {
536		html += '<div id="explanation">';
537		let ts = ccomma.attr('data-types').split(/ /g);
538		for (let i=0 ; i<ts.length ; ++i) {
539			if (!g_marks.types.hasOwnProperty(ts[i])) {
540				// console.log("No such error type: "+ts[i]);
541				continue;
542			}
543			let ctype = '<h3>'+g_marks.types[ts[i]][0]+'</h3><p>'+g_marks.types[ts[i]][1].replace(/(<br>\s*)+<br>\s*/g, '</p><p>')+'</p>';
544			html += ctype.replace(/<p>\s*<\/p>/g, '');
545		}
546		html += '</div>';
547	}
548	else {
549		html += '<div id="explanation">';
550		let ts = ccomma.attr('data-types').split(/ /g);
551		for (let i=0 ; i<ts.length ; ++i) {
552			if (!g_marks.types.hasOwnProperty(ts[i])) {
553				// console.log("No such error type: "+ts[i]);
554				continue;
555			}
556			let ctype = g_marks.types[ts[i]][0]+'<br>';
557			html += ctype.replace(/<p>\s*<\/p>/g, '');
558		}
559		html += '</div>';
560	}
561
562	// If the comma is too close to the right edge, don't set the viewport as it messes up alignment
563	let vp = null;
564	if (ccomma.offset().left + ccomma.width() < p.width() - 100) {
565		vp = b;
566	}
567
568	// If the comma is too close to the top, force it to open below
569	let pl = 'top';
570	if (ccomma.offset().top < 300) {
571		pl = 'bottom';
572	}
573
574	ccomma.popover({
575		container: b,
576		sanitize: false,
577		animation: false,
578		content: html,
579		html: true,
580		placement: pl,
581		fallbackPlacements: ['bottom', 'top', 'right', 'left'],
582		trigger: 'manual',
583		viewport: vp,
584	}).on('shown.bs.popover', function() {
585		let pop = p.find('div.popover');
586		pop.click(function(e) {
587			e.preventDefault();
588			return false;
589		});
590		pop.find('.commaAccept').click(function(e) {
591			e.preventDefault();
592			commaAccept($(this));
593			return false;
594		});
595		pop.find('.commaDiscard').click(function(e) {
596			e.preventDefault();
597			commaDiscard();
598			return false;
599		});
600		pop.find('.commaAddToDict').click(function(e) {
601			e.preventDefault();
602			commaAddToDict();
603			return false;
604		});
605		pop.find('.commaIgnoreAll').click(function(e) {
606			e.preventDefault();
607			commaIgnoreAll();
608			return false;
609		});
610		pop.find('.commaExplain').click(function(e) {
611			e.preventDefault();
612			commaExplain();
613			return false;
614		});
615		pop.find('.showInput').click(function(e) {
616			e.preventDefault();
617			showInput();
618			return false;
619		});
620
621		//pop.scrollintoview();
622		pop.find('a[target="_blank"]').off().on('click', function() {
623			window.open($(this).attr('href'));
624		});
625	});
626	ccomma.popover('show');
627}
628
629function commaInlineClick(e) {
630	e.preventDefault();
631	commaPopup(this, false);
632	matomo_event('comma', 'click');
633	return false;
634}
635
636function commaExplain() {
637	commaPopup(ccomma.get(0), true);
638	matomo_event('comma', 'explain', ccomma.attr('data-types'));
639	log_click({'explain': ccomma.attr('data-types'), 'w': ccomma.attr('data-word')});
640}
641
642function commaCreateInline(id, txt, comma) {
643	let p = $(tinyMCE.activeEditor.getBody()).find('#s'+id);
644	if (p.length == 0) {
645		renumerate_editor();
646		p = $(tinyMCE.activeEditor.getBody()).find('#s'+id);
647	}
648	let ml = p.html();
649	let nonl = /[^\d\wa-zA-ZéÉöÖæÆøØåÅ.,]/ig;
650	let lg = /[<>]/;
651
652	let types = comma.mark.split(/ /g);
653	let col = markingColor(types);
654
655	txt = $.trim(txt.replace(/\s+/g, '').replace(nonl, '\ue000'));
656	// console.log([txt.length, txt, comma.word]);
657	let insert = g_marks.rx_ins.test(comma.mark);
658
659	// Find the spot in the editor where the comma is to be changed
660	let ti = 0;
661	let i = 0;
662	for ( ; i<ml.length ; ++i) {
663		i = skipNonText(ml, i);
664		for (let tn=ti ; tn<txt.length && tn<ti+10 ; ++tn) {
665			if (txt.charAt(tn) === ml.charAt(i) || (txt.charAt(tn) === '\ue000' && !lg.test(ml.charAt(i)) && nonl.test(ml.charAt(i)))) {
666				ti = tn;
667				// Find identical sequential letters, e.g. 1977
668				while (ti < txt.length-1 && txt.charAt(ti) === ml.charAt(i)) {
669					//// console.log([ti, i, txt.charAt(ti), ml.charAt(i)]);
670					++ti;
671					i = skipNonText(ml, i+1);
672				}
673				break;
674			}
675		}
676		//// console.log([ti, i, txt.charAt(ti), ml.charAt(i)]);
677		if (ti >= txt.length-1) {
678			// Check after text as well to find correct insert position
679			let nc = comma.word;
680			//// console.log([i, nc]);
681			if (!insert && nc && nc.length) {
682				let ni = skipNonText(ml, i+1);
683				//// console.log([ni, ml.charAt(ni), nc.charAt(0)]);
684				if (ml.charAt(ni) === nc.charAt(0)) {
685					// Skip tags backwards as well
686					if (ml.charAt(ni-1) === '>') {
687						while (ni > 0 && ml.charAt(ni) !== '<') {
688							--ni;
689						}
690					}
691					//// console.log([nc, i, ni]);
692					i = ni - 2;
693				}
694			}
695			break;
696		}
697	}
698	if (ml.charAt(i) !== ' ' && ml.charAt(i+1) === ' ' && (insert || comma.word === ',' || comma.word === '.')) {
699		++i;
700	}
701	// console.log([ti, i]);
702
703	let ins = comma.word;
704	if (ins === STR_NULLISH) {
705		ins = comma.oword;
706	}
707	if (ml.indexOf(comma.word, i) === -1 && ml.indexOf(escHTML(comma.word), i) !== -1) {
708		comma.word = escHTML(comma.word);
709	}
710
711	if (insert) {
712		if (!/^[,.]$/.test(ins)) {
713			ins = ' ' + ins;
714		}
715	}
716	else {
717		let ni = ml.indexOf(comma.word, i);
718		let ot = ml.lastIndexOf('<', ni);
719		let et = ml.indexOf('>', ot);
720		while (ni !== -1 && ni > ot && ni < et) {
721			ni = ml.indexOf(comma.word, et+1);
722			ot = ml.lastIndexOf('<', ni);
723			et = ml.indexOf('>', ot);
724		}
725		if (ni !== -1) {
726			i = ni;
727		}
728	}
729	if (ml.length >= i+2 && ml.charAt(i+1) == '<' && ml.charAt(i+2) == '/') {
730		i = skipNonText(ml, i+1);
731	}
732	// console.log([ti, i, ml.substr(i)]);
733
734	if (!insert && /\S/.test(ml.charAt(i + comma.word.length - 1))) {
735		comma.word = $.trim(comma.word);
736		//// console.log(comma);
737	}
738
739	let alt = '';
740	if (_live_options.config.opt_color) {
741		alt = ' alt';
742	}
743
744	let rpl = '<span class="comma comma-'+col+alt+'" data-types="'+escHTML(comma.mark)+'" data-sugs="'+escHTML(comma.suggs)+'" data-tid="'+comma.tid+'">'+escHTML(ins)+'</span>';
745
746	if (insert) {
747		ml = ml.substr(0, i) + rpl + ' ' + ml.substr(i);
748	}
749	else {
750		ml = ml.substr(0, i) + rpl + ml.substr(i+comma.word.length);
751	}
752	ml = ml.replace('&nbsp;', ' ').replace(/  +/g, ' ');
753	p.html(ml);
754	$(p).find('span.comma').off().click(commaInlineClick);
755}
756
757function commaDo(rpl) {
758	undo_save();
759
760	let p = $(tinyMCE.activeEditor.getBody());
761	p.find('.popover').remove();
762	let commas = p.find('span.comma');
763	commas.popover('dispose');
764	bootstrap.Modal.getOrCreateInstance(document.getElementById('dg_input')).hide();
765	if (!ccomma) {
766		return;
767	}
768
769	let c = 0;
770	for (let i=0 ; i<commas.length ; ++i) {
771		if (commas[i] === ccomma.get(0)) {
772			c = i;
773		}
774	}
775
776	ccomma.replaceWith(rpl);
777	ccomma = null;
778
779	if (p.find('span.comma').length == 0) {
780		$('#btn-correct-all,#btn-wrong-all,#btn-accept-yellows,#btn-close').addClass('disabled');
781		doneOpen('#btn-correct-all');
782		p.attr('contenteditable', true);
783	}
784	else {
785		p.removeAttr('contenteditable');
786		commas = p.find('span.comma');
787		if (c >= commas.length) {
788			--c;
789		}
790		commas.eq(c).click();
791	}
792}
793
794function commaAccept(e) {
795	if (!ccomma) {
796		return;
797	}
798
799	let rpl = '';
800	let types = ccomma.attr('data-types');
801	let click = {'a': 'accept', 'm': types, 'w': ccomma.text(), 't': ccomma.attr('data-tid')};
802	if (g_marks.rx_del.test(types)) {
803		click.a += '-remove';
804		rpl = '';
805	}
806	else if (g_marks.rx_ins.test(types)) {
807		click.a += '-insert';
808		rpl = ccomma.text();
809	}
810	else {
811		if (typeof e === 'undefined' || !e) {
812			return;
813		}
814		rpl = $(e).text();
815		click.r = rpl;
816	}
817
818	matomo_event('comma', 'accept', ccomma.attr('data-types'));
819	log_marking_action(click);
820	commaDo(rpl);
821}
822
823function commaDiscard() {
824	matomo_event('comma', 'discard', ccomma.attr('data-types'));
825	log_marking_action({'a': 'discard', 'm': ccomma.attr('data-types'), 'w': ccomma.text(), 't': ccomma.attr('data-tid')});
826
827	let types = ccomma.attr('data-types');
828	if (g_marks.rx_ins.test(types)) {
829		commaDo('');
830	}
831	else {
832		commaDo(ccomma.text());
833	}
834}
835
836function commaYellow() {
837	let click = {'a': 'yellow', 'm': ccomma.attr('data-types'), 'w': ccomma.text(), 't': ccomma.attr('data-tid')};
838	if (ccomma.attr('data-sugs')) {
839		let s = ccomma.attr('data-sugs').split('\t')[0];
840		if (s === ccomma.text() || s.toUpperCase() === ccomma.text().toUpperCase()) {
841			click.r = s;
842			commaDo(s);
843		}
844	}
845	else {
846		commaDo(ccomma.text());
847	}
848	matomo_event('comma', 'yellow', ccomma.attr('data-types'));
849	log_marking_action(click);
850}
851
852function commaIgnoreAll() {
853	if (ts_slow) {
854		clearTimeout(ts_slow);
855	}
856	ts_xhr = null;
857	ts_slow = null;
858	g_undos = [];
859	$('#btn-undo').addClass('disabled');
860
861	let ed = $(tinyMCE.activeEditor.getBody());
862	ed.find('.popover').remove();
863	ed.find('span.comma').popover('dispose');
864	ed.find('span.comma').each(function() {
865		let c = $(this);
866		if (g_marks.rx_ins.test(c.attr('data-types'))) {
867			c.replaceWith('');
868		}
869		else {
870			c.replaceWith(c.text());
871		}
872	});
873	ed.attr('contenteditable', true);
874	$('#btn-correct-all,#btn-wrong-all,#btn-accept-yellows,#btn-close').addClass('disabled');
875	ccomma = null;
876	$('#working').hide();
877}
878
879function checkDone() {
880	if (ts_slow) {
881		clearTimeout(ts_slow);
882	}
883	ts_slow = null;
884
885	if (tinyMCE && tinyMCE.activeEditor) {
886		let ed = $(tinyMCE.activeEditor.getBody());
887		commaIgnoreAll();
888		ed.find(tnjq).removeAttr('id');
889	}
890}
891
892function check(which, sel) {
893	if ($('#btn-check').hasClass('disabled')) {
894		return;
895	}
896
897	$('#warning').hide();
898	$('#info').hide();
899	checkDone();
900	$('#btn-check').addClass('disabled');
901	let iframe = $('iframe');
902	$('#working').show().offset(iframe.offset()).width(iframe.width()).height(iframe.height()).focus().find('span').text(l10n_translate('MSG_WORKING'));
903
904	let editor = $(tinyMCE.activeEditor.getBody());
905	prepEditorContent(editor);
906
907	last_result = '';
908	to_send = [];
909	to_send_i = 0;
910	ts_fail = 0;
911	ccomma = null;
912	g_undos = [];
913	$('#btn-undo').addClass('disabled');
914
915	let nodes = $(findTextNodes(editor.get(0))).closest(tnjq).get();
916	for (let i=0 ; i<nodes.length ; ++i) {
917		let need = false;
918		let run = [];
919		let wrap = false;
920		for (let j=0 ; j<nodes[i].childNodes.length ; ++j) {
921			if (text_nodes.hasOwnProperty(nodes[i].childNodes[j].nodeName)) {
922				if (run.length && wrap) {
923					//// console.log(run);
924					$(run).wrapAll('<div class="unwrap"></div>');
925				}
926				need = true;
927				run = [];
928				wrap = false;
929				continue;
930			}
931			if (nodes[i].childNodes[j].nodeType === 1 || (nodes[i].childNodes[j].nodeType === 3 && /\S/.test(nodes[i].childNodes[j].nodeValue))) {
932				//// console.log(nodes[i].childNodes[j]);
933				wrap = true;
934			}
935			run.push(nodes[i].childNodes[j]);
936		}
937		if (run.length && need && wrap) {
938			//// console.log(run);
939			$(run).wrapAll('<div class="unwrap"></div>');
940		}
941	}
942
943	let ps = [];
944	if (sel) {
945		let tns = findTextNodes(editor.get(0));
946		let s = findTextNodes(sel.getStart())[0];
947		let e = findTextNodes(sel.getEnd())[0];
948		//// console.log([s, e]);
949		let in_s = false;
950		for (let i=0 ; i<tns.length ; ++i) {
951			if (tns[i] == s) {
952				in_s = true;
953			}
954			if (in_s) {
955				ps.push(tns[i]);
956				//// console.log([i, nodes[i]]);
957			}
958			if (tns[i] == e) {
959				break;
960			}
961		}
962		ps = $(ps).closest(tnjq).get();
963	}
964	else {
965		ps = $(findTextNodes(editor.get(0))).closest(tnjq).get();
966	}
967	// console.log(ps);
968
969	let doc = [];
970	for (let i=0 ; i<ps.length ; ++i) {
971		let p = $(ps[i]);
972		let ptxt = $.trim(p.html().replace(/<br\/?\s*>/g, '\n').replace(/<[^>]+>/g, ''));
973		if (!ptxt) {
974			continue;
975		}
976
977		let id = i+1;
978		p.attr('id', 's'+id);
979		let par = {
980			i: id,
981			t: decHTML(ptxt),
982			};
983		doc.push(par);
984	}
985
986	if (!doc.length) {
987		return;
988	}
989
990	checkParagraphs(doc);
991}

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.