| |
| |
|
|
| let analyzeTimeout; |
| let analyzeAbortController = null; |
| let _lastInputTime = 0; |
| const ANALYZE_DEBOUNCE_MS = 1000; |
| const MAX_ANALYZE_LENGTH = 5000; |
|
|
| |
| let _isApplyingSuggestion = false; |
|
|
| |
| const _undoStack = []; |
| const _redoStack = []; |
| const _MAX_UNDO = 50; |
|
|
| function pushUndoState() { |
| const editor = getEditorElement(); |
| if (!editor) return; |
| const html = editor.innerHTML; |
| |
| if (_undoStack.length > 0 && _undoStack[_undoStack.length - 1] === html) return; |
| _undoStack.push(html); |
| if (_undoStack.length > _MAX_UNDO) _undoStack.shift(); |
| _redoStack.length = 0; |
| } |
|
|
| function editorUndo() { |
| const editor = getEditorElement(); |
| if (!editor || _undoStack.length === 0) return false; |
| _redoStack.push(editor.innerHTML); |
| editor.innerHTML = _undoStack.pop(); |
| updateEditorStats(); |
| updatePlaceholder(); |
| analyzeTextDelayed(); |
| return true; |
| } |
|
|
| function editorRedo() { |
| const editor = getEditorElement(); |
| if (!editor || _redoStack.length === 0) return false; |
| _undoStack.push(editor.innerHTML); |
| editor.innerHTML = _redoStack.pop(); |
| updateEditorStats(); |
| updatePlaceholder(); |
| analyzeTextDelayed(); |
| return true; |
| } |
|
|
| |
| const _dismissedWords = new Set( |
| JSON.parse(localStorage.getItem('bayan_dismissed_words') || '[]') |
| ); |
|
|
| function _saveDismissedWords() { |
| try { |
| localStorage.setItem('bayan_dismissed_words', JSON.stringify([..._dismissedWords])); |
| } catch (e) {} |
| } |
|
|
| |
| |
| |
| function initEditor() { |
| const editor = getEditorElement(); |
| if (!editor) { |
| console.warn('Editor element not found'); |
| return; |
| } |
|
|
| |
| try { |
| const draft = localStorage.getItem('bayan_editor_draft'); |
| if (draft && !editor.innerHTML.trim()) { |
| editor.innerHTML = draft; |
| |
| setTimeout(analyzeTextDelayed, 500); |
| } |
| } catch (e) {} |
|
|
| |
| let _undoInputTimer = null; |
| editor.addEventListener('input', () => { |
| |
| if (_isApplyingSuggestion) return; |
| _lastInputTime = Date.now(); |
| updateEditorStats(); |
| updatePlaceholder(); |
| analyzeTextDelayed(); |
| |
| clearTimeout(_undoInputTimer); |
| _undoInputTimer = setTimeout(pushUndoState, 500); |
| try { |
| localStorage.setItem('bayan_editor_draft', editor.innerHTML); |
| } catch (e) {} |
| }); |
|
|
| |
| |
| editor.addEventListener('paste', (e) => { |
| e.preventDefault(); |
| const text = (e.clipboardData || window.clipboardData).getData('text/plain'); |
| if (!text) return; |
|
|
| |
| const selection = window.getSelection(); |
| if (!selection.rangeCount) return; |
|
|
| const range = selection.getRangeAt(0); |
| range.deleteContents(); |
|
|
| |
| const lines = text.split(/\r?\n/); |
| const fragment = document.createDocumentFragment(); |
| lines.forEach((line, i) => { |
| if (i > 0) fragment.appendChild(document.createElement('br')); |
| fragment.appendChild(document.createTextNode(line)); |
| }); |
|
|
| range.insertNode(fragment); |
|
|
| |
| range.collapse(false); |
| selection.removeAllRanges(); |
| selection.addRange(range); |
|
|
| |
| updateEditorStats(); |
| updatePlaceholder(); |
| analyzeTextDelayed(); |
| try { |
| localStorage.setItem('bayan_editor_draft', editor.innerHTML); |
| } catch (e) {} |
| }); |
|
|
| editor.addEventListener('click', (e) => { |
| handleEditorClick(e); |
| }); |
|
|
| document.addEventListener('keydown', (e) => { |
| if (e.key === 'Escape') hideTooltip(); |
| }); |
|
|
| |
| |
| editor.addEventListener('keydown', (e) => { |
| if ((e.ctrlKey || e.metaKey) && e.code === 'KeyZ' && !e.shiftKey) { |
| if (_undoStack.length > 0) { |
| e.preventDefault(); |
| e.stopImmediatePropagation(); |
| editorUndo(); |
| return; |
| } |
| } |
| if ((e.ctrlKey || e.metaKey) && (e.code === 'KeyY' || (e.code === 'KeyZ' && e.shiftKey))) { |
| if (_redoStack.length > 0) { |
| e.preventDefault(); |
| e.stopImmediatePropagation(); |
| editorRedo(); |
| return; |
| } |
| } |
| }, true); |
|
|
| document.addEventListener('click', (e) => { |
| const popover = document.getElementById('editor-tooltip'); |
| if (popover && popover.classList.contains('show') && |
| !popover.contains(e.target) && |
| !e.target.classList.contains('spelling-error') && |
| !e.target.classList.contains('grammar-error') && |
| !e.target.classList.contains('punctuation-suggestion')) { |
| hideTooltip(); |
| } |
| }); |
|
|
| const applyAllBtn = document.getElementById('apply-all-btn'); |
| const applyAllSheet = document.getElementById('apply-all-sheet'); |
| if (applyAllBtn) applyAllBtn.addEventListener('click', applyAllSuggestions); |
| if (applyAllSheet) applyAllSheet.addEventListener('click', applyAllSuggestions); |
|
|
| updatePlaceholder(); |
| } |
|
|
| function updateEditorStats() { |
| const text = getEditorText(); |
| const words = text.trim() ? text.trim().split(/\s+/).length : 0; |
| const wordCountEl = document.getElementById('word-count'); |
| if (wordCountEl) { |
| wordCountEl.textContent = words.toLocaleString('ar-EG'); |
| } |
|
|
| |
| const goalEl = document.getElementById('word-goal-indicator'); |
| if (goalEl) { |
| try { |
| const goal = parseInt(localStorage.getItem('bayan_word_goal') || '0', 10); |
| if (goal > 0) { |
| const pct = Math.min(Math.round((words / goal) * 100), 100); |
| goalEl.style.display = 'inline-block'; |
| goalEl.textContent = `${pct.toLocaleString('ar-EG')}% من ${goal.toLocaleString('ar-EG')}`; |
| goalEl.classList.toggle('goal-reached', pct >= 100); |
| } else { |
| goalEl.style.display = 'none'; |
| } |
| } catch(e) { goalEl.style.display = 'none'; } |
| } |
|
|
| |
| if (typeof updateEnhancedStats === 'function') { |
| updateEnhancedStats(); |
| } |
| } |
|
|
| function updatePlaceholder() { |
| const editor = getEditorElement(); |
| if (!editor) return; |
|
|
| const text = (editor.textContent || '').trim(); |
| if (!text || text.length === 0) { |
| editor.setAttribute('data-empty', 'true'); |
| } else { |
| editor.removeAttribute('data-empty'); |
| } |
| } |
|
|
| function analyzeTextDelayed() { |
| clearTimeout(analyzeTimeout); |
| |
| if (analyzeAbortController) { |
| analyzeAbortController.abort(); |
| } |
| analyzeTimeout = setTimeout(() => { |
| |
| const timeSinceLastInput = Date.now() - _lastInputTime; |
| if (timeSinceLastInput >= ANALYZE_DEBOUNCE_MS - 100) { |
| analyzeText(); |
| } |
| }, ANALYZE_DEBOUNCE_MS); |
| } |
|
|
| function findSuggestionById(id) { |
| |
| const suggestions = window.currentSuggestions || []; |
| return suggestions.find(s => s.id === id) || null; |
| } |
|
|
| function findSuggestionElement(id) { |
| return document.querySelector(`[data-suggestion-id="${id}"]`); |
| } |
|
|
| async function analyzeText() { |
| const text = getEditorText(); |
| updateEditorStats(); |
| updatePlaceholder(); |
|
|
| if (!text || text.trim().length === 0) { |
| renderWithoutSuggestions(text); |
| updateSuggestionCounts(0, 0, 0); |
| updateWritingScore(0, 0, 0); |
| updateSuggestionsList([]); |
| window.currentSuggestions = []; |
| updateAnalysisLimitBanner(false); |
| return; |
| } |
|
|
| const isTruncated = text.length > MAX_ANALYZE_LENGTH; |
| const textForApi = isTruncated ? text.substring(0, MAX_ANALYZE_LENGTH) : text; |
| updateAnalysisLimitBanner(isTruncated); |
|
|
| if (analyzeAbortController) { |
| analyzeAbortController.abort(); |
| } |
| analyzeAbortController = new AbortController(); |
|
|
| setAnalyzingState(true); |
|
|
| try { |
| const savedSelection = saveSelection(); |
|
|
| |
| const longerTimer = setTimeout(() => { |
| if (typeof showToast === 'function') showToast('\u0627\u0644\u062a\u062d\u0644\u064a\u0644 \u064a\u0623\u062e\u0630 \u0648\u0642\u062a\u064b\u0627 \u0623\u0637\u0648\u0644...', 'warning'); |
| }, 10000); |
|
|
| const response = await fetch('/api/analyze', { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ text: textForApi }), |
| signal: analyzeAbortController.signal |
| }); |
|
|
| clearTimeout(longerTimer); |
|
|
| if (!response.ok) { |
| console.error('Analyze API error:', response.status); |
| renderWithoutSuggestions(text); |
| return; |
| } |
|
|
| const data = await response.json(); |
|
|
| if (data.status !== 'success' || !data.suggestions) { |
| renderWithoutSuggestions(text); |
| return; |
| } |
|
|
| |
| const rawSuggestions = sortSuggestions(data.suggestions || []); |
| window.currentSuggestions = rawSuggestions.filter( |
| s => !_dismissedWords.has(s.original) |
| ); |
|
|
| |
| const editor = getEditorElement(); |
| overlaySuggestions(editor, window.currentSuggestions); |
|
|
| if (savedSelection) { |
| restoreSelection(savedSelection); |
| } |
|
|
| const spellingCount = window.currentSuggestions.filter((s) => s.type === 'spelling').length; |
| const grammarCount = window.currentSuggestions.filter((s) => s.type === 'grammar').length; |
| const punctuationCount = window.currentSuggestions.filter((s) => s.type === 'punctuation').length; |
|
|
| updateSuggestionCounts(spellingCount, grammarCount, punctuationCount); |
| updateWritingScore(spellingCount, grammarCount, punctuationCount); |
| updateSuggestionsList(window.currentSuggestions); |
| } catch (error) { |
| if (error.name === 'AbortError') return; |
| console.error('Analysis error:', error); |
| renderWithoutSuggestions(text); |
| if (typeof showToast === 'function') showToast('\u062a\u0639\u0630\u0651\u0631 \u0627\u0644\u062a\u062d\u0644\u064a\u0644 \u2014 \u062a\u062d\u0642\u0642 \u0645\u0646 \u0627\u0644\u0627\u062a\u0635\u0627\u0644', 'error'); |
| } finally { |
| setAnalyzingState(false); |
| } |
| } |
|
|
| function renderWithoutSuggestions(text) { |
| const editor = getEditorElement(); |
| if (!editor) return; |
| |
| clearOverlays(editor); |
| updatePlaceholder(); |
| } |
|
|
| function updateSuggestionCounts(spelling, grammar, punctuation) { |
| const spellingEl = document.getElementById('spelling-count'); |
| const grammarEl = document.getElementById('grammar-count'); |
| const punctuationEl = document.getElementById('punctuation-count'); |
|
|
| if (spellingEl) spellingEl.textContent = spelling.toLocaleString('ar-EG'); |
| if (grammarEl) grammarEl.textContent = grammar.toLocaleString('ar-EG'); |
| if (punctuationEl) punctuationEl.textContent = punctuation.toLocaleString('ar-EG'); |
| } |
|
|
| function handleEditorClick(e) { |
| const target = e.target; |
| if (target.classList.contains('spelling-error') || |
| target.classList.contains('grammar-error') || |
| target.classList.contains('punctuation-suggestion')) { |
| showTooltip(target); |
| } |
| } |
|
|
| function showTooltip(element) { |
| const id = element.dataset.suggestionId; |
| const suggestion = findSuggestionById(id); |
|
|
| if (!suggestion) return; |
|
|
| const tooltip = document.getElementById('editor-tooltip'); |
| if (!tooltip) return; |
|
|
| const typeEl = document.getElementById('tooltip-type'); |
| const originalEl = document.getElementById('tooltip-original'); |
| const alternativesEl = document.getElementById('tooltip-alternatives'); |
|
|
| const typeMap = { |
| spelling: { label: 'خطأ إملائي' }, |
| grammar: { label: 'خطأ نحوي' }, |
| punctuation: { label: 'علامات ترقيم' } |
| }; |
|
|
| if (typeEl) { |
| const typeInfo = typeMap[suggestion.type] || { label: suggestion.type }; |
| typeEl.innerHTML = typeInfo.label; |
| typeEl.className = `popover-type popover-type--${suggestion.type}`; |
| } |
|
|
| if (originalEl) { |
| originalEl.innerHTML = `<span class="popover-original-word" style="text-decoration:line-through; opacity:0.6;">${escapeHtml(suggestion.original)}</span> <span class="popover-arrow">←</span> <span class="popover-correction-word" style="color:var(--color-success); font-weight:600;">${escapeHtml(suggestion.correction)}</span>`; |
| } |
|
|
| |
| if (alternativesEl) { |
| |
| const alts = (typeof resolveAlternatives === 'function') |
| ? resolveAlternatives(suggestion) |
| : (suggestion.alternatives && suggestion.alternatives.length > 0) |
| ? suggestion.alternatives |
| : [suggestion.correction, suggestion.original]; |
| let html = ''; |
| |
| alts.forEach((alt, i) => { |
| const isKeep = alt === suggestion.original; |
| if (isKeep) return; |
| const isMain = i === 0; |
| const btnClass = isMain ? 'popover-alt-btn popover-alt-main' : 'popover-alt-btn'; |
| html += `<button class="${btnClass}" data-alt-correction="${escapeHtml(alt)}" type="button">${isMain ? '✓ ' : ''}${escapeHtml(alt)}</button>`; |
| }); |
| |
| html += `<button class="popover-alt-btn popover-alt-keep" data-alt-correction="${escapeHtml(suggestion.original)}" type="button">إبقاء كما هي</button>`; |
| alternativesEl.innerHTML = html; |
|
|
| |
| alternativesEl.querySelectorAll('.popover-alt-btn').forEach(btn => { |
| btn.addEventListener('click', () => { |
| const correctionText = btn.dataset.altCorrection; |
| if (correctionText === suggestion.original) { |
| dismissSuggestion(suggestion); |
| } else { |
| applyAlternativeCorrection(suggestion, correctionText); |
| } |
| }); |
| }); |
| } |
|
|
| const rect = element.getBoundingClientRect(); |
| let top = rect.bottom + 10; |
| let left = rect.left; |
|
|
| if (left + 320 > window.innerWidth) { |
| left = window.innerWidth - 330; |
| } |
| if (top + 150 > window.innerHeight) { |
| top = rect.top - 150; |
| } |
|
|
| tooltip.style.top = `${top}px`; |
| tooltip.style.left = `${Math.max(8, left)}px`; |
| tooltip.classList.add('show'); |
|
|
| window.currentApplySuggestion = suggestion; |
| window.currentSuggestionElement = element; |
| window.currentSuggestionId = id; |
| } |
|
|
| function hideTooltip() { |
| const tooltip = document.getElementById('editor-tooltip'); |
| if (tooltip) { |
| tooltip.classList.remove('show'); |
| } |
| window.currentApplySuggestion = null; |
| window.currentSuggestionElement = null; |
| } |
|
|
| function applySuggestionAtOffsets(suggestion) { |
| _isApplyingSuggestion = true; |
| try { |
| pushUndoState(); |
| |
| const suggestionId = suggestion.id; |
| const errorSpan = suggestionId ? document.querySelector(`[data-suggestion-id="${suggestionId}"]`) : null; |
|
|
| if (errorSpan) { |
| const parent = errorSpan.parentNode; |
| const correctedNode = document.createTextNode(suggestion.correction); |
| parent.insertBefore(correctedNode, errorSpan); |
| parent.removeChild(errorSpan); |
| parent.normalize(); |
| |
| try { |
| const sel = window.getSelection(); |
| const r = document.createRange(); |
| r.setStartAfter(correctedNode); |
| r.collapse(true); |
| sel.removeAllRanges(); |
| sel.addRange(r); |
| } catch(e) {} |
| } else { |
| |
| const allErrorSpans = document.querySelectorAll('.spelling-error, .grammar-error, .punctuation-suggestion'); |
| let found = false; |
| allErrorSpans.forEach(span => { |
| if (!found && span.textContent === suggestion.original) { |
| const p = span.parentNode; |
| const correctedNode = document.createTextNode(suggestion.correction); |
| p.insertBefore(correctedNode, span); |
| p.removeChild(span); |
| p.normalize(); |
| |
| try { |
| const sel = window.getSelection(); |
| const r = document.createRange(); |
| r.setStartAfter(correctedNode); |
| r.collapse(true); |
| sel.removeAllRanges(); |
| sel.addRange(r); |
| } catch(e) {} |
| found = true; |
| } |
| }); |
| if (!found) { |
| |
| const text = getEditorText(); |
| const before = text.substring(0, suggestion.start); |
| const after = text.substring(suggestion.end); |
| const newText = before + suggestion.correction + after; |
| setEditorHTML(escapeHtml(newText)); |
| |
| setCaretOffset(suggestion.start + suggestion.correction.length); |
| } |
| } |
| hideTooltip(); |
| |
| const _ed = getEditorElement(); if (_ed) _ed.focus(); |
|
|
| |
| if (window.currentSuggestions) { |
| window.currentSuggestions = window.currentSuggestions.filter( |
| s => s.id !== suggestion.id |
| ); |
|
|
| const spellingCount = window.currentSuggestions.filter(s => s.type === 'spelling').length; |
| const grammarCount = window.currentSuggestions.filter(s => s.type === 'grammar').length; |
| const punctuationCount = window.currentSuggestions.filter(s => s.type === 'punctuation').length; |
| updateSuggestionCounts(spellingCount, grammarCount, punctuationCount); |
| updateWritingScore(spellingCount, grammarCount, punctuationCount); |
| updateSuggestionsList(window.currentSuggestions); |
| } |
| |
| } finally { |
| |
| |
| setTimeout(() => { _isApplyingSuggestion = false; }, 400); |
| } |
| |
| |
| setTimeout(() => { analyzeText(); }, 300); |
| } |
|
|
| function applyCorrection() { |
| if (!window.currentApplySuggestion) return; |
| applySuggestionAtOffsets(window.currentApplySuggestion); |
| if (typeof showToast === 'function') showToast('✓ تم التصحيح'); |
| } |
|
|
| function applyAlternativeCorrection(suggestion, correctionText) { |
| _isApplyingSuggestion = true; |
| try { |
| pushUndoState(); |
| |
| const suggestionId = suggestion.id; |
| const errorSpan = suggestionId ? document.querySelector(`[data-suggestion-id="${suggestionId}"]`) : null; |
|
|
| if (errorSpan) { |
| const parent = errorSpan.parentNode; |
| const correctedNode = document.createTextNode(correctionText); |
| parent.insertBefore(correctedNode, errorSpan); |
| parent.removeChild(errorSpan); |
| parent.normalize(); |
| |
| try { |
| const sel = window.getSelection(); |
| const r = document.createRange(); |
| r.setStartAfter(correctedNode); |
| r.collapse(true); |
| sel.removeAllRanges(); |
| sel.addRange(r); |
| } catch(e) {} |
| } else { |
| const allErrorSpans = document.querySelectorAll('.spelling-error, .grammar-error, .punctuation-suggestion'); |
| let found = false; |
| allErrorSpans.forEach(span => { |
| if (!found && span.textContent === suggestion.original) { |
| const p = span.parentNode; |
| const correctedNode = document.createTextNode(correctionText); |
| p.insertBefore(correctedNode, span); |
| p.removeChild(span); |
| p.normalize(); |
| |
| try { |
| const sel = window.getSelection(); |
| const r = document.createRange(); |
| r.setStartAfter(correctedNode); |
| r.collapse(true); |
| sel.removeAllRanges(); |
| sel.addRange(r); |
| } catch(e) {} |
| found = true; |
| } |
| }); |
| if (!found) { |
| const text = getEditorText(); |
| const before = text.substring(0, suggestion.start); |
| const after = text.substring(suggestion.end); |
| const newText = before + correctionText + after; |
| setEditorHTML(escapeHtml(newText)); |
| |
| setCaretOffset(suggestion.start + correctionText.length); |
| } |
| } |
| hideTooltip(); |
| |
| const _ed2 = getEditorElement(); if (_ed2) _ed2.focus(); |
| |
| if (window.currentSuggestions) { |
| window.currentSuggestions = window.currentSuggestions.filter( |
| s => s.id !== suggestion.id |
| ); |
|
|
| const spellingCount = window.currentSuggestions.filter(s => s.type === 'spelling').length; |
| const grammarCount = window.currentSuggestions.filter(s => s.type === 'grammar').length; |
| const punctuationCount = window.currentSuggestions.filter(s => s.type === 'punctuation').length; |
| updateSuggestionCounts(spellingCount, grammarCount, punctuationCount); |
| updateWritingScore(spellingCount, grammarCount, punctuationCount); |
| updateSuggestionsList(window.currentSuggestions); |
| } |
| |
| } finally { |
| |
| setTimeout(() => { _isApplyingSuggestion = false; }, 400); |
| } |
| |
| setTimeout(() => { analyzeText(); }, 300); |
| } |
|
|
| function dismissSuggestion(suggestion) { |
| pushUndoState(); |
| |
| if (suggestion.original) { |
| _dismissedWords.add(suggestion.original); |
| _saveDismissedWords(); |
| } |
|
|
| |
| |
| const suggestionId = suggestion.id; |
| const errorSpan = suggestionId ? document.querySelector(`[data-suggestion-id="${suggestionId}"]`) : null; |
|
|
| if (errorSpan) { |
| |
| const parent = errorSpan.parentNode; |
| while (errorSpan.firstChild) { |
| parent.insertBefore(errorSpan.firstChild, errorSpan); |
| } |
| parent.removeChild(errorSpan); |
| parent.normalize(); |
| } |
|
|
| if (window.currentSuggestions) { |
| window.currentSuggestions = window.currentSuggestions.filter( |
| s => s.id !== suggestion.id |
| ); |
| |
|
|
| const spellingCount = window.currentSuggestions.filter(s => s.type === 'spelling').length; |
| const grammarCount = window.currentSuggestions.filter(s => s.type === 'grammar').length; |
| const punctuationCount = window.currentSuggestions.filter(s => s.type === 'punctuation').length; |
| updateSuggestionCounts(spellingCount, grammarCount, punctuationCount); |
| updateWritingScore(spellingCount, grammarCount, punctuationCount); |
| updateSuggestionsList(window.currentSuggestions); |
| } |
| hideTooltip(); |
| |
| const _ed3 = getEditorElement(); if (_ed3) _ed3.focus(); |
| } |
|
|
| function applySuggestionById(id) { |
| |
| const suggestion = findSuggestionById(id); |
| if (!suggestion) return; |
| applySuggestionAtOffsets(suggestion); |
| } |
|
|
| function applyAllSuggestions() { |
| |
| const suggestions = [...(window.currentSuggestions || [])].sort((a, b) => b.start - a.start); |
| if (suggestions.length === 0) return; |
| _isApplyingSuggestion = true; |
| try { |
| pushUndoState(); |
|
|
| |
| |
| |
| |
| |
| |
| let text = getEditorText(); |
| suggestions.forEach((s) => { |
| if (s.start >= 0 && s.end <= text.length && s.start <= s.end) { |
| text = text.substring(0, s.start) + s.correction + text.substring(s.end); |
| } |
| }); |
| setEditorHTML(escapeHtml(text)); |
|
|
| |
| const editor = getEditorElement(); |
| if (editor) { |
| try { |
| const sel = window.getSelection(); |
| const range = document.createRange(); |
| range.selectNodeContents(editor); |
| range.collapse(false); |
| sel.removeAllRanges(); |
| sel.addRange(range); |
| } catch(e) {} |
| editor.focus(); |
| } |
|
|
| hideTooltip(); |
|
|
| |
| window.currentSuggestions = []; |
| updateSuggestionCounts(0, 0, 0); |
| updateWritingScore(0, 0, 0); |
| updateSuggestionsList([]); |
| if (typeof showToast === 'function') showToast('✓ تم تطبيق ' + suggestions.length + ' تصحيح'); |
| } finally { |
| |
| setTimeout(() => { _isApplyingSuggestion = false; }, 400); |
| } |
| |
| |
| |
| } |
|
|
| function clearEditor() { |
| |
| const text = getEditorText(); |
| if (text.trim().length > 0) { |
| if (!confirm('هل أنت متأكد من مسح كل المحتوى؟')) return; |
| } |
| setEditorHTML(''); |
| window.currentSuggestions = []; |
| updateSuggestionCounts(0, 0, 0); |
| updateWritingScore(0, 0, 0); |
| updateSuggestionsList([]); |
| updateEditorStats(); |
| updatePlaceholder(); |
| updateAnalysisLimitBanner(false); |
| if (typeof updateExportButtonStates === 'function') updateExportButtonStates(); |
| } |
|
|
| |
| |
| |
| |
| |
| function loadDocumentText(text, options = {}) { |
| const normalized = typeof normalizeImportedText === 'function' |
| ? normalizeImportedText(text) |
| : String(text || '').replace(/^\uFEFF/, ''); |
|
|
| setEditorHTML(escapeHtml(normalized)); |
| window.currentSuggestions = []; |
| hideTooltip(); |
| updatePlaceholder(); |
| updateEditorStats(); |
| updateSuggestionCounts(0, 0, 0); |
| updateWritingScore(0, 0, 0); |
| updateSuggestionsList([]); |
| updateAnalysisLimitBanner(normalized.length > MAX_ANALYZE_LENGTH); |
|
|
| if (typeof updateExportButtonStates === 'function') { |
| updateExportButtonStates(); |
| } |
|
|
| if (options.analyze !== false) { |
| analyzeTextDelayed(); |
| } |
| } |
|
|
| function copyText() { |
| const text = getEditorText(); |
| navigator.clipboard.writeText(text).then(() => { |
| if (typeof showToast === 'function') showToast('✓ تم نسخ النص'); |
| }).catch(() => { |
| const temp = document.createElement('textarea'); |
| temp.value = text; |
| document.body.appendChild(temp); |
| temp.select(); |
| document.execCommand('copy'); |
| document.body.removeChild(temp); |
| if (typeof showToast === 'function') showToast('✓ تم نسخ النص'); |
| }); |
| } |
|
|
| |
| function _sendFeedback(suggestion, helpful) { |
| const apiBase = window.BAYAN_API_BASE || ''; |
| fetch(`${apiBase}/api/feedback`, { |
| method: 'POST', |
| headers: { 'Content-Type': 'application/json' }, |
| body: JSON.stringify({ |
| suggestion_id: suggestion.id || '', |
| helpful: helpful, |
| original: suggestion.original || '', |
| correction: suggestion.correction || '', |
| text: (document.getElementById('editor-container')?.textContent || '').substring(0, 200), |
| }) |
| }).catch(err => console.warn('[Feedback] Failed:', err)); |
| } |
|
|
| if (typeof module !== 'undefined' && module.exports) { |
| module.exports = { |
| initEditor, |
| analyzeText, |
| analyzeTextDelayed, |
| clearEditor, |
| copyText, |
| loadDocumentText, |
| updateEditorStats, |
| showTooltip, |
| hideTooltip, |
| applyCorrection, |
| applySuggestionById, |
| applyAllSuggestions |
| }; |
| } |
|
|