PageSourceSearch

https://sokrati.ai/theme/js/custom.js

js sokrati.ai collected 2026-10-05 13:54:55 UTC 30,277 bytes, 929 lines download raw bytes

1$(document).ready(function (e) { 
2
3/* 	$.ajax({
4		url: '/get_max_symb',
5		type: 'POST',
6		success: function(response) {				
7			$('#maxs').val(response);				
8		},
9		error: function(response) {
10			console.log(response);
11		}
12	}); */
13
14    $('button.clear-btn').attr('disabled', true);
15    $('button.actionBtn').attr('disabled', true);
16    //$('button.checkai-btn').attr('disabled', true);
17    $('button.copy-btn').attr('disabled', true);
18    $('div.exportBtn').addClass('pointer_event_none');
19	
20    $('.download-btn').prop('disabled', true);
21
22    // start from here step-1
23    $(".input-text-orignaltext").on('keyup input', function () {
24        var words = 0;
25    // console.log(this.value, this.value.match(/\S+/g));
26        if (this.value != null && this.value != '') {
27            words = this.value.match(/\S+/g).length;
28            $('button.clear-btn').attr('disabled', false);
29            //$('button.actionBtn').attr('disabled', false);
30            $(this).prev().hide();
31            $(".textPasteBtn").hide();
32            $(".upbtn1").hide();
33        } else {
34            $('button.clear-btn').attr('disabled', true);
35            //$('button.actionBtn').attr('disabled', true);
36            $(this).prev().show();
37            $(".textPasteBtn").show();
38            $(".upbtn1").show();
39        }
40
41        const getroutenamvalue = $('#getroutenamvalue').text();
42        if (getroutenamvalue == 'index' ) {
43            var allowedWords = getLimitWord();
44        } else {
45            var allowedWords = parseInt($('#allowedWords').text()) !== 0 ? 1500 : 250;
46        }
47        if (words > allowedWords) {
48            var trimmed = this.value;
49            words = trimmed.match(/\S+/g).length;
50            $('span.display_count').text(words);
51            $('span.display_count').addClass('text-danger');
52            if ( i == 1 ){
53                $("#wLimitModal").modal("show");
54                i++;
55            }
56        } else {
57            $('span.display_count').text(words);
58            $('span.display_count').removeClass('text-danger');
59            i = 1 ;
60        }
61
62    });
63	
64	
65    function initTruncation() {
66        const $maxs = $('#maxs');
67        if (!$maxs.length) return;
68
69        const MAX_LENGTH = parseInt($maxs.val(), 10) || 100;
70        const $textarea = $('#textInput');
71
72        if ($textarea.length) {
73            $textarea.on('input', function() {
74                if (this.value.length > MAX_LENGTH) {
75                    this.value = truncateTextByWords(this.value, MAX_LENGTH);
76                    this.setSelectionRange(this.value.length, this.value.length);
77                }
78                updateCounter();
79            });
80            updateCounter();
81			$('button.actionBtn').attr('disabled', false);
82        }
83    }
84
85    // Наблюдаем за изменениями в body
86    const observer = new MutationObserver(function(mutations) {
87        if ($('#maxs').length > 0) {
88            observer.disconnect(); // останавливаем наблюдение
89            initTruncation();
90        }
91    });
92
93    observer.observe(document.body, {
94        childList: true,
95        subtree: true
96    });	
97	
98	$(".textPasteBtn").on("click", function () {				
99		try {
100			navigator.clipboard.readText().then((clipText) => {
101				const maxChars = $('#maxs').val();
102				var truncText = truncateTextByWords(clipText, maxChars);
103				if (truncText != null || truncText != ""){
104					$("#textInput").val(truncText);
105					$(".textPasteBtn").hide();
106					$(".upbtn1").hide();
107					$('button.clear-btn').attr('disabled', false);
108					$('button.actionBtn').attr('disabled', false);
109
110					//add the length of pasted text
111					var txtlen = $("#textInput").val().match(/\S+/g).length;
112					$("span.display_count").text(txtlen);
113					WordCountColor(txtlen);
114					updateCounter();
115				}
116			});
117		} catch (error) {
118			console.error("Error: "+error);
119		}
120	});	
121	
122	$('.clear-btn').on('click', function () {
123		$('#textInput').val(''); //inputtextarea
124 		$('span.display_count').text(0);
125		$('span#rephrase_count').text(0);
126		$(".textPasteBtn").show();
127		$(".upbtn1").show();
128		$('button.clear-btn').attr('disabled', true);
129		//$('button.actionBtn').attr('disabled', true);
130		//$('button.checkai-btn').attr('disabled', true);
131		$('#output_text_div').text('');
132		$('#output_text_exp').text('');
133		$('.placeHoldertext').attr('hidden', false);
134		
135		$("#uploadFile").attr('disabled', false);
136		updateCounter();	
137	});	
138	
139	$(document).on("click", "#exportPdf", function(e) {
140		var box2 = $("#output_text_exp").html(); // get HTML including spans
141	
142		if (box2.trim() !== "") {
143			// Convert HTML to plain text or handle span parsing (if needed)
144			let tempDiv = document.createElement("div");
145			tempDiv.innerHTML = box2;
146
147			// Optional: clean or convert span styles (e.g., bold, color) here
148			let textWithFormatting = [];
149			tempDiv.childNodes.forEach(node => {
150				if (node.nodeType === Node.TEXT_NODE) {
151					textWithFormatting.push({ text: node.textContent });
152				} else if (node.nodeType === Node.ELEMENT_NODE) {
153					if (node.tagName === "SPAN") {
154						let color = node.style.color || "#000000";
155						let fontWeight = node.style.fontWeight === "bold" ? true : false;
156
157						textWithFormatting.push({
158							text: node.textContent,
159							color: color,
160							bold: fontWeight
161						});
162					} else {
163						textWithFormatting.push({ text: node.textContent });
164					}
165				}
166			});
167
168			// Prepare the document
169			var docDefinition = {
170				content: [
171					{
172						text: textWithFormatting,
173						fontSize: 12,
174						font: 'TimesNewRoman'
175					}
176				],
177				defaultStyle: {
178					font: 'TimesNewRoman'
179				},
180				info: {
181					title: 'RewriteGPT'
182				}
183			};
184
185			pdfMake.createPdf(docDefinition).download("result_SokratiAI.pdf");
186		} else {
187			console.log("Please enter some text!");
188			$(".boxs p").html("Please enter some text!");
189			$("#largeFileAlert").removeClass("d-none");
190			setTimeout(function () {
191				$("#largeFileAlert").addClass("d-none");
192			}, 3000);
193		}
194	});	
195	
196	$(document).on("click", "#exportTxt", function (e) {
197		e.preventDefault();
198
199		var box = $("#output_text_exp").text();
200		if (box != "") {
201			console.log(box);
202
203			// ✅ Добавляем BOM для UTF-8
204			const bom = new Uint8Array([0xEF, 0xBB, 0xBF]);
205			const textEncoder = new TextEncoder();
206			const textData = textEncoder.encode(box);
207			const finalData = new Uint8Array(bom.length + textData.length);
208			finalData.set(bom);
209			finalData.set(textData, bom.length);
210
211			// ✅ Создаём Blob с правильным типом и данными
212			const textToBLOB = new Blob([finalData], {
213				type: "text/plain;charset=utf-8"
214			});
215
216			const sFileName = "result_SokratiAI.txt";
217
218			let newLink = document.createElement("a");
219			newLink.download = sFileName;
220
221			if (window.webkitURL != null) {
222				newLink.href = window.webkitURL.createObjectURL(textToBLOB);
223			} else {
224				newLink.href = window.URL.createObjectURL(textToBLOB);
225				newLink.style.display = "none";
226				document.body.appendChild(newLink);
227			}
228
229			newLink.click();
230
231			// ✅ Очистка URL
232			setTimeout(() => {
233				window.URL.revokeObjectURL(newLink.href);
234				if (newLink.parentNode) {
235					document.body.removeChild(newLink);
236				}
237			}, 100);
238
239		} else {
240			$(".boxs p").html("Please enter some text!");
241			$("#largeFileAlert").removeClass("d-none");
242			setTimeout(function () {
243				$("#largeFileAlert").addClass("d-none");
244			}, 3000);
245		}
246	});	
247	
248	$('.input-file input[type=file]').on('change', function(){
249		let file = this.files[0];
250		$(this).next().html(file.name);
251	});	
252	
253	$('#uploadFile').on('change', handleFileUpload);
254
255    // Функция обработки загрузки файла
256    function handleFileUpload(e) {
257        const file = e.target.files[0];
258        const textArea = $('#textInput');
259        const maxChars = $('#maxs').val();
260        const fileSize = file.size;	
261        
262        // Проверка базовых условий
263        if (!file) return;
264        if (fileSize > 50 * 1024 * 1024) { // Ограничение 50MB
265            showStatus('Файл слишком большой (максимум 50MB)', true);
266            return;
267        }
268		
269		const fileExtension = file.name.split('.').pop().toLowerCase();
270 		const allowedExtensions = ['txt', 'doc', 'docx'];
271		const fileName = file.name.toLowerCase();		
272
273 		if (!allowedExtensions.includes(fileExtension)) {
274			showStatus('Разрешены только файлы с расширениями: .txt, .doc, .docx', true);
275			return;
276		} 			
277
278		$(".upbtn1").prop("disabled", true);
279		$(".upbtn1 .loadSpinner").removeClass("d-none");
280			
281		//$(this).prop( "disabled", true );
282		$('.loader').show();			
283        
284        switch(fileExtension) {
285            case 'txt':
286                handleTextFile(file, textArea, maxChars);
287                break;
288                
289            case 'docx':
290                handleDocxFile(file, textArea, maxChars);
291                break;
292                
293            case 'doc':
294				handleDocFile(file, maxChars);
295				//newOrderActionsMsg('Извините, чтение .'+fileExtension+' файлов не поддерживается');
296                break;
297                
298            default:
299				newOrderActionsMsg('Загрузите файл в формате txt,doc,docx или вставьте текстом');
300                showStatus('Неподдерживаемый формат файла',true);
301				break;
302        }
303    }
304
305    // Обработка текстовых файлов
306    function handleTextFile(file, textArea, maxChars) {
307        const reader = new FileReader();
308        
309        reader.onload = function(e) {
310            let content = e.target.result;
311            const truncated = truncateTextByWords(content, maxChars);
312			
313			$(".upbtn1 .loadSpinner").addClass("d-none");
314			$(".upbtn1").prop("disabled", false);
315			$("#uploadFile").val("");
316
317			$("#textInput").val(truncated);			
318			
319			$(".textPasteBtn").hide();
320			$('button.actionBtn').attr('disabled', false);
321			$('button.clear-btn').attr('disabled', false);
322
323			$(".upbtn1").hide();
324			var txtlen = $("#textInput").val().match(/\S+/g).length;
325			$("span.display_count").text(txtlen);
326			WordCountColor(txtlen);			
327			updateCounter();
328        };
329        
330        reader.onerror = function() {
331			console.log('Ошибка чтения файла');
332        };
333        
334        reader.readAsText(file, "UTF-8");
335    }
336	
337    // Обработка DOC файлов через php
338     function handleDocFile(file,maxChars) {
339		const textArea = $('#textInput');
340		const formData = new FormData();
341		//const maxChars = $('#maxs').val();
342		formData.append('file', file);		
343	
344 		$.ajax({
345			url: '/convert',
346			type: 'POST',
347			data: formData,
348			cache : false,
349			processData: false,
350			contentType: false,			
351			success: function(res) {
352				//console.log(res.file);
353				if(res.ok){
354
355					// Загружаем файл с сервера как Blob
356					fetch(res.file)
357					.then(response => {
358						if (!response.ok) {
359							throw new Error("Ошибка загрузки docx");
360						}
361						return response.blob(); // Получаем как Blob
362					})
363					.then(blob => {
364						// Превращаем blob в файл-подобный объект
365						const file = new File([blob], res.file, { type: blob.type });
366						handleDocxFile(file,textArea,maxChars); // передаём в существующую функцию
367					})
368					.catch(err => {
369						console.error("Ошибка обработки docx:", err);
370					});					
371				}
372				
373			},
374			error: function(res) {
375				console.log(res);
376			}
377		}); 
378
379	} 
380	
381	//режет текст до maxLength симолов по словам (чтобы не обрывать слово посреди)
382	function truncateTextByWords(text, maxLength) {
383	  if (text.length <= maxLength) {
384		return text;
385	  }
386
387	  // Обрезаем до maxLength и удаляем последнее неполное слово
388	  const truncated = text.substring(0, maxLength);
389	  const lastSpaceIndex = truncated.lastIndexOf(' ');
390
391	  // Если есть пробел, обрезаем до него
392	  if (lastSpaceIndex > 0) {
393		return truncated.substring(0, lastSpaceIndex) + '...';
394	  }
395
396	  // Если нет пробелов — возвращаем просто обрезанный текст
397	  return truncated + '...';
398	}	
399
400    // Обработка DOCX файлов
401    function handleDocxFile(file, textArea, maxChars) {
402        const reader = new FileReader();
403        //console.log(reader);
404        reader.onload = function(e) {
405            const arrayBuffer = e.target.result;
406            mammoth.extractRawText({ arrayBuffer })
407                .then(function(result) {
408                    let content = result.value;
409                    const truncated = truncateTextByWords(content, maxChars); 
410
411					$(".upbtn1 .loadSpinner").addClass("d-none");
412					$(".upbtn1").prop("disabled", false);
413					$("#uploadFile").val("");
414
415					$("#textInput").val(truncated);			
416					
417					$(".textPasteBtn").hide();
418					$('button.actionBtn').attr('disabled', false);
419					$('button.clear-btn').attr('disabled', false);
420
421					$(".upbtn1").hide();
422					var txtlen = $("#textInput").val().match(/\S+/g).length;
423					$("span.display_count").text(txtlen);
424					WordCountColor(txtlen);			
425					updateCounter();					
426					
427					if(truncated.length > maxChars){
428						$('#msg').html('Текст должен содержать не более 300к символов.').show().delay(2000).fadeOut(300);
429						$(textArea).prop('disabled', true);
430						$('.check-button').prop('disabled', true);							
431						newOrderActions();					
432					}
433                })
434                .catch(function(error) {
435					console.log('Ошибка обработки DOC,DOCX: ${error.message}');
436                });
437        };
438        
439        reader.onerror = function() {
440            showStatus('Ошибка чтения файла',true);
441        };
442        
443        reader.readAsArrayBuffer(file);
444    }
445
446    // Вспомогательные функции
447    function showError(message) {
448        showNotification(message, 'error');
449    }
450
451    function showInfo(message) {
452        showNotification(message, 'info');
453    }
454
455    function showNotification(message, type) {
456        const notification = $('#resultContainer');
457        notification.removeClass('error info').addClass(type);
458        notification.text(message);
459        notification.fadeIn(200).delay(5000).fadeOut(200);
460    }	
461	
462	function newOrderActions() {
463		$('.check-button').hide();
464		$('.input-file input').prop( "disabled", true );
465		$('.another').show();
466	}
467	
468	function newOrderActionsMsg(msg){
469		$('#msg').html(msg).show().delay(2000).fadeOut(300);
470		$('.loader').hide();
471		$('.input-file input').prop( "disabled", false );
472		$('.input-file span').html('Выберите файл');	
473	}
474	
475	function extractTextFromDocx(arrayBuffer) {
476        var zip = new JSZip();
477        zip.loadAsync(arrayBuffer).then(function(zip) {
478            return zip.file("word/document.xml").async("string");
479        }).then(function(content) {
480            var parser = new DOMParser();
481            var xmlDoc = parser.parseFromString(content, "text/xml");
482            var body = xmlDoc.getElementsByTagName("w:body")[0];
483            var textNodes = body.getElementsByTagName("w:t");
484            var text = "";
485            for (var i = 0; i < textNodes.length; i++) {
486                text += textNodes[i].textContent + " ";
487            }
488            $('#textInput').val(text.trim());
489			updateCounter()
490			$('.loader').hide();
491        });
492    }	
493	
494	document.querySelectorAll('.scroll-link').forEach(anchor => {
495		anchor.addEventListener('click', function (e) {
496			e.preventDefault();
497			const targetId = this.getAttribute('data-target');
498			const targetElement = document.getElementById(targetId);
499			if (targetElement) {
500				targetElement.scrollIntoView({
501					behavior: 'smooth'
502				});
503			}
504		});
505	});	
506
507	function changeCount() {
508		$.ajax({
509			url: '/counter_change',
510			type: 'POST',
511			success: function(response) {				
512				$('.count_check span').html(response.count_value);				
513			},
514			error: function(response) {
515				console.log(response);
516			}
517		});
518
519		//return false; 
520	}		
521
522	$("#fileBtn, #fileBtn1").on("click", function () {
523		var text = document.getElementById("uploadFile").click();
524	});
525
526	let i = 1 ;
527	//word count color
528	function WordCountColor(words) {
529		const getRouteNameValue = $('#getroutenamvalue').text();
530		const allowedWordsElement = parseInt($('#allowedWords').text());
531		let allowedWords;
532
533		if (getRouteNameValue === 'index') {
534			allowedWords = getLimitWord();
535		} else {
536			allowedWords = allowedWordsElement !== 0 ? 1500 : 250;
537		}
538
539		if (words > allowedWords) {
540			$('span.display_count').addClass('text-danger');
541			if ( i == 1 ){
542				$("#wLimitModal").modal("show");
543				i++;
544			}
545		} else {
546			$('span.display_count').removeClass('text-danger');
547			i = 1;
548		}
549	}	
550	
551
552	// simultaneously
553	const inputTextarea = document.getElementById('textInput');
554	const outputTextDiv = document.getElementById('output_text_div');
555
556	// Function to update scrollbar visibility
557	function updateScrollbars() {
558		inputTextarea.style.overflowY = inputTextarea.value.trim() ? 'auto' : 'hidden';
559		outputTextDiv.style.overflowY = outputTextDiv.innerText.trim() ? 'auto' : 'hidden';
560	}
561
562	// Sync scroll positions
563	function syncScroll(source, target) {
564		target.scrollTop = source.scrollTop;
565	}	
566    
567    // --- Функция для отображения статуса ---
568    function showStatus(message, isError = false) {
569        const statusDiv = $('#status-message');
570		//console.log(message);
571        statusDiv.html(message);
572        statusDiv.removeClass('text-success text-danger');
573        if (isError) {
574            statusDiv.addClass('text-danger');
575			// Очистить сообщение через 3 секунды
576			setTimeout(() => {
577				 if (statusDiv.html() === message) {
578					 statusDiv.html('');
579					 statusDiv.removeClass('text-danger');
580				 }
581			}, 3000);			
582        } else {
583            statusDiv.addClass('text-success');
584        }
585    }
586
587	function updateCounterEnd() {
588		const text =  $('#output_text_div').text();
589		const words = text.trim().split(/\s+/).filter(word => word.length > 0).length;
590		const characters = text.length;
591		document.getElementById('rephrase_count').textContent = `Символов: ${characters} | Слов: ${words}`;
592	}
593
594	$('#process-btn').click(function() {
595		
596		var percent_compress = 100-parseInt($("#percent_compress").val()),
597			protect_numbers = $("#protect_important_numbers").val();		
598		const text = $('#textInput').val().trim();
599
600			if (!text) {
601				showStatus('Пожалуйста, введите текст', true);
602				return;
603			}
604			if ((percent_compress<5) || (percent_compress>95)) {
605				showStatus('Процент % сжатия текста должен быть от 5 до 95%', true);
606				return;
607			}	
608			//console.log(percent_compress);
609			const startTime = performance.now();
610
611			// Блокируем кнопку на время запроса
612			const processBtn = $(this);
613			
614			const originalHtml = processBtn.html(); 
615			processBtn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm mr-2" role="status" aria-hidden="true"></span> Обработка...');
616			$(".bodyArea").addClass("disabled");	
617
618/* 			let timerInterval = setInterval(() => {
619				const elapsed = ((performance.now() - startTime) / 1000).toFixed(1);
620				$('#status-message').html('Время обработки: '+elapsed+ 'сек.');
621			}, 100); */ 
622
623/* 			console.log(JSON.stringify({
624					text: text,
625					keep_ratio_percent: percent_compress,
626					preserve_structure: protect_numbers
627				}));	 */
628
629			$.ajax({
630				url: '/api/summarize',
631				type: 'POST',
632				contentType: 'application/json', 
633				data: JSON.stringify({
634					text: text,
635					keep_ratio_percent: percent_compress,
636					preserve_structure: protect_numbers
637				}),		
638				dataType: 'json',
639				beforeSend: function() {
640					$('#result').html('<p>Анализируем...</p>').show();
641				},
642				success: function(response) {
643					//console.log(percent_compress);
644					//console.log(response);
645
646					$('#result').html('');
647					$('div#output_text_div').html(response.summary_text);
648					$('div#output_text_exp').html(response.summary_text);					
649					$('.placeHoldertext').attr('hidden', false);
650					$('button.copy-btn').attr('disabled', false);
651					$('.placeHoldertext').attr('hidden', true);
652					$('div.exportBtn').removeClass('pointer_event_none').removeClass('disabled');
653					$('.win2').css('display', 'block');	
654					$('.win2').addClass('win3');
655					showStatus(response.message, false);
656					$('.bodyArea').removeClass('disabled');
657					
658					$('.clear').hide();
659					//$('.clear-btn').hide();
660					//$('#process-btn').hide();
661					//$('#process-btn').hide();
662					processBtn.prop('disabled', false).text('Сократить текст еще раз');
663					//$('#process-btn');
664					$('#afterblock').removeClass('d-none');		
665
666					const highlightedHtml = highlightRemovedText(text,response.spans);
667					
668					var resultHtml = `<div id="watermark"></div>
669						<div class="result reportArea" id="reportArea">
670							<div class="row">
671								<h3 class="col-8 col-md-10 mb-4">Отчет по сжатию текста</h3>
672								<span class="col-4 col-md-2"><img src="/theme/img/sokrati_logo.webp" class="logo-icon mr-2">Sokrati.AI</span>
673							</div>	
674							<div class="row explanation">
675								<p><span style="color:#5A2D91">🟪 - Фиолетовым фоном выделен текст, который был удален</span> </p> 
676							</div>				
677							<div class="row ml-1">
678								Удалено предоженией ${response.stats.removed} из ${response.stats.total} (Оставлено: ${response.stats.kept})
679							</div>
680							<div class="row pt-4">
681								<div class="col-md-12" id="content-to-export">							
682									${highlightedHtml}
683								</div>
684							</div>		
685						</div>						
686						<p class="text-center inbuttons">
687							<button class="btn btn-danger1 mb-2" id="screenshotBtn3">Сохранить отчет</button>
688							
689						</p>
690						<div class="loader_btn"></div>`;
691
692					$('div#report-container').html(resultHtml);
693					//document.getElementById('report-container').innerHTML = highlightedHtml;
694
695					updateCounterEnd()					
696					changeCount();
697					
698			
699					
700				},
701				error: function(xhr, status, error) {
702					let msg = 'Произошла ошибка при анализе.';
703					if (xhr.responseJSON && xhr.responseJSON.detail) {
704						msg = xhr.responseJSON.detail;
705					}
706					$('#result').html(msg);
707				}
708			});
709			
710				
711	});
712	
713	// Обработчик для кнопки "анализ текста"
714	$('#detailedAnalysisButton').click(function(e) {
715		e.preventDefault();
716		$('#detailedAnalysisTable').toggle();
717	});	 		
718	
719/**
720 * Подсвечивает удалённые фрагменты текста.
721 * @param {string} text - Полный исходный текст.
722 * @param {Array<Array<number>>} spans - Массив спанов [[start, end], ...]
723 * @returns {string} - HTML-строка с подсвеченными участками.
724 */
725function highlightRemovedText(text, spans) {
726    if (!spans || spans.length === 0) {
727        return text.replace(/\n/g, '<br>');
728    }
729
730    // Сортируем спаны по возрастанию start (на всякий случай)
731    const sortedSpans = [...spans].sort((a, b) => a[0] - b[0]);
732
733    let result = '';
734    let lastEnd = 0;
735
736    for (const [start, end] of sortedSpans) {
737        // Добавляем текст до начала удалённого фрагмента
738        if (start > lastEnd) {
739            result += escapeHtml(text.slice(lastEnd, start));
740        }
741
742        // Добавляем удалённый фрагмент в обёртке
743        const removedFragment = text.slice(start, end);
744        result += `<span class="removed">${escapeHtml(removedFragment)}</span>`;
745
746        lastEnd = end;
747    }
748
749    // Добавляем остаток текста после последнего спана
750    if (lastEnd < text.length) {
751        result += escapeHtml(text.slice(lastEnd));
752    }
753
754    // Заменяем переносы строк на <br> для корректного отображения в HTML
755    return result.replace(/\n/g, '<br>');
756}
757
758/**
759 * Экранирует HTML-символы для безопасности.
760 */
761function escapeHtml(unsafe) {
762    return unsafe
763        .replace(/&/g, "&amp;")
764        .replace(/</g, "<")
765        .replace(/>/g, ">")
766        .replace(/"/g, "&quot;")
767        .replace(/'/g, "&#039;");
768}
769
770
771	// OnClick Copy button 1 Text
772	$(".copy-btn").click(function () {
773
774		if ($("#output_text_div").text() != "") {
775			let divText = $("#output_text_exp").text();
776			navigator.clipboard.writeText(divText);
777			$("#copyAlert").removeClass("d-none");
778
779			setTimeout(function () {
780				$("#copyAlert").addClass("d-none");
781			}, 2000);
782		} else {
783			$("#copyAlert").addClass("d-none");
784		}
785
786	});
787	
788	$(document).on('click','#screenshotBtn',function(){
789
790		const inButtons = $('.inbuttons');
791		const originalDisplay = inButtons.css('display');
792		//inButtons.css('display', 'none');
793		$('.loader_btn').show();
794		$('body').addClass('body_loader');
795		const now = new Date();
796		function padZero(num) {
797			return num < 10 ? "0" + num : num;
798		}
799		const filename = `report_SokratiAI_${padZero(now.getDate())}${padZero(now.getMonth() + 1)}${String(now.getFullYear()).slice(-2)}_${padZero(now.getHours())}${padZero(now.getMinutes())}${padZero(now.getSeconds())}`;
800
801		const targetElement = $('.reportArea')[0];
802		const watermark = $('#watermark')[0];
803
804		// Создаем временный контейнер для объединения основного элемента и водяного знака
805		const tempContainer = $('<div></div>');
806		tempContainer.css({
807			position: 'relative',
808			width: targetElement.offsetWidth + 'px',
809			height: targetElement.offsetHeight + 'px'
810		});
811
812		// Клонируем основной элемент и водяной знак
813		const targetClone = $(targetElement).clone();
814		const watermarkClone = $(watermark).clone();
815
816		// Устанавливаем стили для клонов
817		targetClone.css({
818			position: 'absolute',
819			top: '0',
820			left: '0',
821			zIndex: '1'
822		});
823
824		watermarkClone.css({
825			position: 'absolute',
826			top: '50%',
827			left: '50%',
828			transform: 'translate(-50%, -50%) rotate(-5deg)',
829			color: 'rgba(255, 0, 0, 0.5)',
830			fontSize: '24px',
831			zIndex: '2',
832			whiteSpace: 'nowrap',
833			pointerEvents: 'none',
834			display: 'flex',
835			flexDirection: 'column',
836			alignItems: 'center',
837			justifyContent: 'center',
838			height: '100%',
839			width: '100%',
840			transformOrigin: 'center'
841		});
842
843		// Добавляем клонированные элементы в временный контейнер
844		tempContainer.append(targetClone);
845		tempContainer.append(watermarkClone);
846
847		// Добавляем временный контейнер в документ
848		$('body').append(tempContainer);
849
850		domtoimage.toPng(tempContainer[0]).then(function(dataUrl) {
851			// Удаляем временный контейнер после создания снимка
852			tempContainer.remove();
853
854			// Создаем ссылку для скачивания файла
855			const link = $('<a></a>');
856			link.attr('href', dataUrl);
857			link.attr('download', `${filename}.png`);
858			$('body').append(link);
859			link[0].click();
860			link.remove();
861			$('.loader_btn').hide();
862			$('body').removeClass('body_loader');
863			// Восстанавливаем отображение кнопок
864			//inButtons.css('display', originalDisplay);
865		}).catch(function(error) {
866			$('.loader_btn').hide();
867			$('body').removeClass('body_loader');
868			//inButtons.css('display', originalDisplay);
869			console.log("Ошибка при создании скриншота: " + error);
870		});
871	});	
872
873	document.body.addEventListener('click', function(e) {
874	  if (e.target && e.target.id === 'screenshotBtn3') {
875		const element = document.getElementById('content-to-export');
876
877		const content = convertHtmlToPdfMakeContent(element.innerHTML);
878
879		const docDefinition = {
880		  content: [
881			{
882			  text: content,
883			  fontSize: 12
884			}
885		  ],
886		  defaultStyle: {
887			font: 'TimesNewRoman' // или другой шрифт с кириллицей
888		  }
889		};
890
891		pdfMake.createPdf(docDefinition).download('report_SokratiAI_text.pdf');
892	  }
893	});
894
895
896	function convertHtmlToPdfMakeContent(html) {
897	  const tempDiv = document.createElement('div');
898	  tempDiv.innerHTML = html;
899
900	  const result = [];
901
902	  function traverse(node) {
903		if (node.nodeType === Node.TEXT_NODE) {
904		  result.push(node.textContent);
905		} else if (node.nodeType === Node.ELEMENT_NODE) {
906		  if (node.tagName === 'BR') {
907			result.push('\n');
908		  } else if (node.classList.contains('removed')) {
909			result.push({
910			  text: node.textContent,
911			  background: '#E6D8FF' // или другой цвет
912			});
913		  } else {
914			for (let child of node.childNodes) {
915			  traverse(child);
916			}
917		  }
918		}
919	  }
920
921	  for (let child of tempDiv.childNodes) {
922		traverse(child);
923	  }
924
925	  return result;
926	}
927
928
929}); 

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.