// Textarea auto-resize functionality function autoResizeTextarea(textarea) { textarea.style.height = 'auto'; textarea.style.height = (textarea.scrollHeight + 2) + 'px'; } function initAutoResizeTextareas() { const textareas = document.querySelectorAll('.translation-input'); textareas.forEach(textarea => { textarea.addEventListener('input', function() { autoResizeTextarea(this); }); textarea.addEventListener('keydown', function(e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); const start = this.selectionStart; const end = this.selectionEnd; this.value = this.value.substring(0, start) + '\n' + this.value.substring(end); this.selectionStart = this.selectionEnd = start + 1; autoResizeTextarea(this); } }); setTimeout(() => autoResizeTextarea(textarea), 0); }); } // Copy functionality function copyToTranslation(key, value, inputId) { const textarea = document.getElementById(inputId); textarea.value = value; textarea.focus(); autoResizeTextarea(textarea); const btn = event.target; const originalText = btn.textContent; btn.textContent = 'Copied!'; setTimeout(() => btn.textContent = originalText, 2000); } function visualizeWhitespace(element) { const text = element.textContent || element.value; let html = ''; // console.log("Visualating whitespace for: " + text) if (text === undefined){ return ' ' } for (let i = 0; i < text.length; i++) { const char = text[i]; if (char === ' ') { html += ' '; } else if (char === '\t') { html += '\t'; } else if (char === '\n') { html += '\n'; } else { html += char; } } return html; } function applyWhitespaceVisualization() { // Для значений в DeepDiff document.querySelectorAll('.old-value, .new-value').forEach(el => { el.innerHTML = visualizeWhitespace(el); }); } // Modify the copyToClipboard function to preserve newlines function copyToClipboard(event, text) { event.preventDefault(); const btn = event.currentTarget; const originalText = btn.textContent; // Preserve the exact text including newlines and spaces const textToCopy = text.replace(/\\n/g, '\n'); // Convert \n to actual newlines navigator.clipboard.writeText(textToCopy).then(() => { btn.textContent = 'Copied!'; btn.classList.add('copied'); setTimeout(() => { btn.textContent = originalText; btn.classList.remove('copied'); }, 2000); }).catch(() => { // Fallback for older browsers try { const textarea = document.createElement('textarea'); textarea.value = textToCopy; document.body.appendChild(textarea); textarea.select(); document.execCommand('copy'); document.body.removeChild(textarea); btn.textContent = 'Copied!'; btn.classList.add('copied'); setTimeout(() => { btn.textContent = originalText; btn.classList.remove('copied'); }, 2000); } catch (err) { btn.textContent = 'Failed!'; btn.classList.add('failed'); setTimeout(() => { btn.textContent = originalText; btn.classList.remove('failed'); }, 2000); } }); } // Translation suggestions functionality async function getTranslationSuggestions(text, inputId, containerId) { const container = document.getElementById(containerId); container.style.display = 'block'; container.innerHTML = `
Translation Suggestions ×
Loading suggestions...
`; try { const response = await fetch('/get_translation_suggestions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({text: text}) }); const data = await response.json(); if (data.error) { container.innerHTML = `
Error getting translation suggestions ×
${data.error}
`; return; } let html = `
Translation Suggestions ×
`; if (data.yandex) { html += `
Yandex: ${data.yandex}
`; } if (data.google) { html += `
Google: ${data.google}
`; } if (data.deepl) { html += `
DeepL: ${data.deepl}
`; } if (data.ai_suggestion) { html += `
AI: ${data.ai_suggestion}
`; } container.innerHTML = html; } catch (error) { container.innerHTML = `
Error getting translation suggestions ×
Error loading suggestions: ${error.message}
`; } } async function getAISuggestions(text, inputId, containerId) { const container = document.getElementById(containerId); container.style.display = 'block'; container.innerHTML = `
AI Translation Suggestions ×
Loading AI suggestions...
`; try { const response = await fetch('/get_ai_suggestions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({text: text.replace("\n", "\\n")}) }); const data = await response.json(); if (data.error) { container.innerHTML = `
Error getting AI suggestions ×
${data.error}
`; return; } let html = `
AI Translation Suggestions ×
`; if (data.suggestions?.length > 0) { data.suggestions.forEach((suggestion, index) => { html += `
${index + 1}. ${suggestion}
`; }); } else { html += `
No AI suggestions available
`; } container.innerHTML = html; } catch (error) { container.innerHTML = `
Error getting AI suggestions ×
Error loading AI suggestions: ${error.message}
`; } } async function getLearningAISuggestions(text, inputId, containerId) { const container = document.getElementById(containerId); container.style.display = 'block'; container.innerHTML = `
Learning AI Translation Suggestions ×
Loading Learning AI suggestions...
`; try { const response = await fetch('/get_learning_ai_suggestions', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({text: text.replace("\n", "\\n")}) }); const data = await response.json(); if (data.error) { container.innerHTML = `
Error getting Learning AI suggestions ×
${data.error}
`; return; } let html = `
Learning AI Translation Suggestions ×
`; if (data.suggestions?.length > 0) { data.suggestions.forEach((suggestion, index) => { html += `
${index + 1}. ${suggestion}
`; }); } else { html += `
No Learning AI suggestions available
`; } container.innerHTML = html; } catch (error) { container.innerHTML = `
Error getting Learning AI suggestions ×
Error loading Learning AI suggestions: ${error.message}
`; } } function closeSuggestions(containerId, clearContent = false) { const container = document.getElementById(containerId); container.style.display = 'none'; if (clearContent) { container.innerHTML = ''; } } function useSuggestion(inputId, suggestion) { const textarea = document.getElementById(inputId); textarea.dataset.programmaticChange = 'true'; textarea.value = suggestion; textarea.focus(); autoResizeTextarea(textarea); addToHistory(textarea); } function escapeHtml(unsafe) { return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } // Tab functionality function openTab(evt, tabName) { const tabcontent = document.getElementsByClassName("tab-content"); for (let i = 0; i < tabcontent.length; i++) { tabcontent[i].classList.remove("active"); } const tablinks = document.getElementsByClassName("tab"); for (let i = 0; i < tablinks.length; i++) { tablinks[i].classList.remove("active"); } if (tabName === 'markedTodo') { updateTodoTab(); } document.getElementById(tabName).classList.add("active"); evt.currentTarget.classList.add("active"); applyWhitespaceVisualization(); initAutoResizeTextareas(); } // Scroll to top functionality function scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); } window.addEventListener('scroll', function() { const scrollButton = document.querySelector('.scroll-top'); if (window.pageYOffset > 300) { scrollButton.classList.add('visible'); } else { scrollButton.classList.remove('visible'); } }); // Translation saving functionality function collectTranslations() { const translations = {}; const textareas = document.querySelectorAll('.translation-input'); textareas.forEach(textarea => { const key = textarea.id.replace('input-', ''); console.log(key) const originalKey = document.querySelector(`#translation-${key} .key`).textContent; let value = textarea.value; if (value.startsWith("[") && value.endsWith("]")) { translations[originalKey] = eval(value); console.warn("EVALED: " + value) console.warn("EVALED: " + eval(value)) } else { translations[originalKey] = value; } }); return translations; } function markAsTodo(inputId) { const textarea = document.getElementById(inputId); if (!textarea.value.includes('//TODO')) { textarea.value = textarea.value + ' //TODO'; autoResizeTextarea(textarea); addToHistory(textarea); showToast('Marked as TODO', 'success'); } else { showToast('Already marked as TODO', 'info'); } } function collectTodoItems() { const todoItems = []; const textareas = document.querySelectorAll('#translation .translation-input'); textareas.forEach(textarea => { if (textarea.value.includes('//TODO') || textarea.value.includes('//REDO')) { const key = textarea.id.replace('input-', ''); const keyElement = document.querySelector(`#translation-${key} .key`); const valueElement = document.querySelector(`#translation-${key} .new-value`); if (keyElement && valueElement) { todoItems.push({ key: keyElement.textContent, value: valueElement.textContent, translation: textarea.value }); } } }); return todoItems; } function updateTodoTab() { const todoTab = document.getElementById('markedTodo'); if (!todoTab) return; const todoItems = collectTodoItems(); const todoList = todoTab.querySelector('.key-list'); if (todoItems.length === 0) { todoList.innerHTML = '

No TODO/REDO items found. Mark items in the Translation tab by adding //TODO.

'; return; } let html = '

TODO/REDO Items (' + todoItems.length + ')

'; todoItems.forEach((item, index) => { html += `
${item.key}: ${item.value}
`; }); todoList.innerHTML = html; initAutoResizeTextareas(); } function collectTodoTranslations() { const translations = {}; const textareas = document.querySelectorAll('#markedTodo .translation-input'); textareas.forEach(textarea => { const key = textarea.id.replace('todo-input-', ''); const originalKey = document.querySelector(`#todo-translation-${key} .key`).textContent; translations[originalKey] = textarea.value.trim(); }); return translations; } function resolveTodo(inputId) { const textarea = document.getElementById(inputId); textarea.value = textarea.value.replace('//TODO', '').replace('//REDO', '').trim(); autoResizeTextarea(textarea); addToHistory(textarea); showToast('TODO resolved', 'success'); updateTodoTab(); // Refresh the TODO tab } async function saveAllTodoTranslations() { const translations = collectTodoTranslations(); const statusMessage = document.getElementById('todo-status-message'); let state = true; try { const response = await fetch('/save_todo_translations', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(translations) .replace(/\\n/g, '\\n') // Явно сохраняем \n .replace(/\\r/g, '\\r') // И \r если есть }); const data = await response.json(); if (data.status === 'success') { statusMessage.textContent = `Successfully saved ${data.saved} TODO translations`; statusMessage.className = 'status-message success-message'; } else { statusMessage.textContent = 'Error: ' + (data.message || 'Unknown error'); statusMessage.className = 'status-message error-message'; state = false; } setTimeout(() => { statusMessage.textContent = ''; statusMessage.className = 'status-message'; }, 3000); return state; } catch (error) { statusMessage.textContent = 'Error: ' + error.message; statusMessage.className = 'status-message error-message'; return false; } } async function saveAllTranslations() { const translations = collectTranslations(); const statusMessage = document.getElementById('status-message'); let state = true; try { const response = await fetch('/save_translations', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(translations) .replace(/\\n/g, '\\n') // Явно сохраняем \n .replace(/\\r/g, '\\r') // И \r если есть }); const data = await response.json(); if (data.status === 'success') { statusMessage.textContent = `Successfully saved ${data.saved} translations`; statusMessage.className = 'status-message success-message'; } else { statusMessage.textContent = 'Error: ' + (data.message || 'Unknown error'); statusMessage.className = 'status-message error-message'; state = false; } setTimeout(() => { statusMessage.textContent = ''; statusMessage.className = 'status-message'; }, 3000); return state; } catch (error) { statusMessage.textContent = 'Error: ' + error.message; statusMessage.className = 'status-message error-message'; return false; } } // Undo/Redo functionality let changeHistory = {}; let currentHistoryIndex = {}; function initChangeTracking() { const textareas = document.querySelectorAll('.translation-input'); textareas.forEach(textarea => { const id = textarea.id; changeHistory[id] = [textarea.value]; currentHistoryIndex[id] = 0; textarea.addEventListener('input', function() { if (!this.dataset.programmaticChange) { addToHistory(this); } delete this.dataset.programmaticChange; }); }); } function addToHistory(textarea) { const id = textarea.id; const currentValue = textarea.value; if (changeHistory[id][currentHistoryIndex[id]] === currentValue) return; changeHistory[id] = changeHistory[id].slice(0, currentHistoryIndex[id] + 1); changeHistory[id].push(currentValue); currentHistoryIndex[id] = changeHistory[id].length - 1; updateUndoRedoButtons(id); } function updateUndoRedoButtons(inputId) { const undoBtn = document.querySelector(`#${inputId}`).closest('.key-item').querySelector('.undo-btn'); const redoBtn = document.querySelector(`#${inputId}`).closest('.key-item').querySelector('.redo-btn'); undoBtn.disabled = currentHistoryIndex[inputId] <= 0; redoBtn.disabled = currentHistoryIndex[inputId] >= changeHistory[inputId].length - 1; } function undoChange(inputId) { const textarea = document.getElementById(inputId); if (currentHistoryIndex[inputId] > 0) { currentHistoryIndex[inputId]--; textarea.dataset.programmaticChange = 'true'; textarea.value = changeHistory[inputId][currentHistoryIndex[inputId]]; autoResizeTextarea(textarea); updateUndoRedoButtons(inputId); } } function redoChange(inputId) { const textarea = document.getElementById(inputId); if (currentHistoryIndex[inputId] < changeHistory[inputId].length - 1) { currentHistoryIndex[inputId]++; textarea.dataset.programmaticChange = 'true'; textarea.value = changeHistory[inputId][currentHistoryIndex[inputId]]; autoResizeTextarea(textarea); updateUndoRedoButtons(inputId); } } function showToast(message, type = 'info') { const toast = document.createElement('div'); toast.className = `toast-notification ${type}`; toast.textContent = message; document.body.appendChild(toast); setTimeout(() => { toast.classList.add('show'); }, 10); setTimeout(() => { toast.classList.remove('show'); setTimeout(() => { document.body.removeChild(toast); }, 300); }, 3000); } // Keyboard shortcuts document.addEventListener('keydown', e => { // Save on Ctrl+S console.log("DEBUG") if (e.ctrlKey && (e.key.toLowerCase() === 's' || e.key.toLowerCase() === 'ы')) { e.preventDefault(); const activeTab = document.querySelector('.tab.active'); const tabId = activeTab.getAttribute('onclick').split("'")[1]; let result = false; if (tabId === 'translation') { showToast('Saving all translations...', 'info'); result = saveAllTranslations(); if (result) { showToast('Saved all translations!', 'success'); } else { showToast('Error saving translations.', 'error'); } } else if (tabId === 'markedTodo') { showToast('Saving all TODO translations...', 'info'); result = saveAllTodoTranslations(); if (result) { showToast('Saved all TODO translations!', 'success'); } else { showToast('Error saving TODO translations.', 'error'); } } } // Undo on Ctrl+Z if (e.ctrlKey && e.key.toLowerCase() === 'z' && !e.shiftKey) { e.preventDefault(); const activeTextarea = document.activeElement; if (activeTextarea?.classList.contains('translation-input')) { undoChange(activeTextarea.id); showToast('Undo last change', 'info'); } } // Redo on Ctrl+Y or Ctrl+Shift+Z if ((e.ctrlKey && e.key.toLowerCase() === 'y') || (e.ctrlKey && e.shiftKey && e.key.toLowerCase() === 'z')) { e.preventDefault(); const activeTextarea = document.activeElement; if (activeTextarea?.classList.contains('translation-input')) { redoChange(activeTextarea.id); showToast('Redo last change', 'info'); } } }); // Settings functionality function tabinitSettings() { // Theme switch const themeSwitch = document.getElementById('theme-switch'); themeSwitch.addEventListener('change', function() { document.body.classList.toggle('dark-theme', this.checked); localStorage.setItem('darkTheme', this.checked); }); // Load theme preference if (localStorage.getItem('darkTheme') === 'true') { themeSwitch.checked = true; document.body.classList.add('dark-theme'); } // Custom preprompt switch const prepromptSwitch = document.getElementById('custom-preprompt-switch'); const prepromptTextarea = document.getElementById('custom-preprompt'); prepromptSwitch.addEventListener('change', function() { prepromptTextarea.disabled = !this.checked; }); // Proxy switch const proxySwitch = document.getElementById('yandex-proxy-switch'); const proxySettings = document.querySelector('.proxy-settings'); proxySwitch.addEventListener('change', function() { proxySettings.style.display = this.checked ? 'block' : 'none'; }); // Load saved settings tabloadSettings(); } function tabloadSettings() { // This would load from localStorage or server in a real app console.log('Loading settings...'); } function tabsaveSettings() { const settings = { darkTheme: document.getElementById('theme-switch').checked, customPreprompt: { enabled: document.getElementById('custom-preprompt-switch').checked, text: document.getElementById('custom-preprompt').value }, deepseekApiKey: document.getElementById('deepseek-api-key').value, yandex: { apiType: document.getElementById('yandex-api-type').value, proxy: { enabled: document.getElementById('yandex-proxy-switch').checked, type: document.getElementById('proxy-type').value, address: document.getElementById('proxy-address').value } } }; // In a real app, this would save to localStorage or server console.log('Saving settings:', settings); const status = document.getElementById('settings-status'); status.textContent = 'Settings saved!'; status.className = 'status-message success-message'; setTimeout(() => { status.textContent = ''; status.className = 'status-message'; }, 3000); } function tabresetSettings() { if (confirm('Are you sure you want to reset all settings to defaults?')) { // Reset all settings to default values document.getElementById('theme-switch').checked = false; document.body.classList.remove('dark-theme'); document.getElementById('custom-preprompt-switch').checked = false; document.getElementById('custom-preprompt').value = ''; document.getElementById('custom-preprompt').disabled = true; document.getElementById('deepseek-api-key').value = ''; document.getElementById('yandex-api-type').value = 'web'; document.getElementById('yandex-proxy-switch').checked = false; document.querySelector('.proxy-settings').style.display = 'none'; document.getElementById('proxy-type').value = 'http'; document.getElementById('proxy-address').value = ''; const status = document.getElementById('settings-status'); status.textContent = 'Settings reset to defaults!'; status.className = 'status-message success-message'; setTimeout(() => { status.textContent = ''; status.className = 'status-message'; }, 3000); } } // Initialize when page is fully loaded window.addEventListener('load', function() { applyWhitespaceVisualization(); initAutoResizeTextareas(); initChangeTracking(); tabinitSettings(); });