Upload voice to text.html
Browse files- Speech to text/voice to text.html +255 -64
Speech to text/voice to text.html
CHANGED
|
@@ -622,6 +622,11 @@
|
|
| 622 |
|
| 623 |
// Cursor position
|
| 624 |
var cursorPosition = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 625 |
|
| 626 |
// 36 Years in milliseconds
|
| 627 |
var RETENTION_TIME = 1135296000000;
|
|
@@ -648,9 +653,15 @@
|
|
| 648 |
var editor = document.getElementById('textEditor');
|
| 649 |
editor.addEventListener('input', updateCounts);
|
| 650 |
editor.addEventListener('input', scheduleDraftSave);
|
|
|
|
| 651 |
editor.addEventListener('click', saveCursorPosition);
|
| 652 |
editor.addEventListener('keyup', saveCursorPosition);
|
| 653 |
editor.addEventListener('keydown', saveCursorPosition);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 654 |
|
| 655 |
// Initialize undo/redo
|
| 656 |
saveToHistoryStack();
|
|
@@ -717,38 +728,7 @@
|
|
| 717 |
}
|
| 718 |
|
| 719 |
if (final) {
|
| 720 |
-
|
| 721 |
-
var text = editor.value;
|
| 722 |
-
|
| 723 |
-
// Insert at cursor position
|
| 724 |
-
var beforeCursor = text.substring(0, cursorPosition);
|
| 725 |
-
var afterCursor = text.substring(cursorPosition);
|
| 726 |
-
|
| 727 |
-
// Add space before if needed
|
| 728 |
-
if (beforeCursor.length > 0 && !beforeCursor.endsWith(' ') && !beforeCursor.endsWith('\n')) {
|
| 729 |
-
final = ' ' + final;
|
| 730 |
-
}
|
| 731 |
-
|
| 732 |
-
// Add space after if needed
|
| 733 |
-
if (final.length > 0 && !final.endsWith(' ') && !final.endsWith('\n')) {
|
| 734 |
-
final = final + ' ';
|
| 735 |
-
}
|
| 736 |
-
|
| 737 |
-
editor.value = beforeCursor + final + afterCursor;
|
| 738 |
-
|
| 739 |
-
// Move cursor to after inserted text
|
| 740 |
-
cursorPosition = beforeCursor.length + final.length;
|
| 741 |
-
editor.selectionStart = cursorPosition;
|
| 742 |
-
editor.selectionEnd = cursorPosition;
|
| 743 |
-
|
| 744 |
-
updateCounts();
|
| 745 |
-
saveToHistoryStack();
|
| 746 |
-
scheduleDraftSave();
|
| 747 |
-
|
| 748 |
-
// Scroll to cursor position
|
| 749 |
-
if (editor.value.length > 0) {
|
| 750 |
-
editor.scrollTop = editor.scrollHeight * (cursorPosition / editor.value.length);
|
| 751 |
-
}
|
| 752 |
}
|
| 753 |
};
|
| 754 |
|
|
@@ -790,32 +770,47 @@
|
|
| 790 |
}
|
| 791 |
|
| 792 |
function toggleMic() {
|
| 793 |
-
if (
|
| 794 |
-
|
| 795 |
-
}
|
| 796 |
-
|
| 797 |
-
if (!recognition) return;
|
| 798 |
|
| 799 |
-
|
| 800 |
-
|
| 801 |
-
|
| 802 |
-
|
| 803 |
-
} catch(e) {}
|
| 804 |
-
updateMicUI(false);
|
| 805 |
-
showToast("⏹️ Recording stopped");
|
| 806 |
-
} else {
|
| 807 |
-
try {
|
| 808 |
-
recognition.start();
|
| 809 |
-
showToast("🎤 Recording... Speak now!");
|
| 810 |
-
} catch(e) {
|
| 811 |
-
console.error(e);
|
| 812 |
-
showToast("⚠️ Error starting microphone");
|
| 813 |
-
// Try re-initializing
|
| 814 |
recognition = initSpeech();
|
| 815 |
-
setTimeout(function() {
|
| 816 |
-
try { recognition.start(); } catch(e2) {}
|
| 817 |
-
}, 500);
|
| 818 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 819 |
}
|
| 820 |
}
|
| 821 |
|
|
@@ -943,8 +938,10 @@
|
|
| 943 |
editor.value = payload.text;
|
| 944 |
currentTextState = payload.text;
|
| 945 |
cursorPosition = payload.text.length;
|
|
|
|
| 946 |
editor.selectionStart = cursorPosition;
|
| 947 |
editor.selectionEnd = cursorPosition;
|
|
|
|
| 948 |
|
| 949 |
if (payload.lang === 'ur-PK' || payload.lang === 'en-US' || payload.lang === 'en-PK') {
|
| 950 |
setLang(payload.lang);
|
|
@@ -1058,27 +1055,215 @@
|
|
| 1058 |
document.getElementById('redoBtn').disabled = redoStack.length === 0;
|
| 1059 |
}
|
| 1060 |
|
| 1061 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1062 |
function saveCursorPosition() {
|
| 1063 |
var editor = document.getElementById('textEditor');
|
| 1064 |
-
|
| 1065 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1066 |
}
|
| 1067 |
|
| 1068 |
function insertAtCursor(text) {
|
| 1069 |
var editor = document.getElementById('textEditor');
|
| 1070 |
-
|
| 1071 |
-
|
| 1072 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1073 |
editor.value = beforeCursor + text + afterCursor;
|
| 1074 |
cursorPosition = beforeCursor.length + text.length;
|
| 1075 |
-
|
| 1076 |
-
|
| 1077 |
-
|
|
|
|
|
|
|
| 1078 |
updateCounts();
|
| 1079 |
saveToHistoryStack();
|
| 1080 |
scheduleDraftSave();
|
| 1081 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1082 |
|
| 1083 |
// --- SHARE FUNCTIONALITY ---
|
| 1084 |
function shareText() {
|
|
@@ -1298,6 +1483,10 @@
|
|
| 1298 |
saveToHistoryStack();
|
| 1299 |
scheduleDraftSave();
|
| 1300 |
cursorPosition = item.text.length;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1301 |
}
|
| 1302 |
}
|
| 1303 |
|
|
@@ -1396,6 +1585,8 @@
|
|
| 1396 |
saveToHistoryStack();
|
| 1397 |
clearDraftText();
|
| 1398 |
cursorPosition = 0;
|
|
|
|
|
|
|
| 1399 |
showToast("🗑️ Text cleared");
|
| 1400 |
}
|
| 1401 |
|
|
|
|
| 622 |
|
| 623 |
// Cursor position
|
| 624 |
var cursorPosition = 0;
|
| 625 |
+
var cursorEndPosition = 0;
|
| 626 |
+
var editorScrollTop = 0;
|
| 627 |
+
var editorScrollLeft = 0;
|
| 628 |
+
var speechInsertLock = false;
|
| 629 |
+
var micTransitionLock = false;
|
| 630 |
|
| 631 |
// 36 Years in milliseconds
|
| 632 |
var RETENTION_TIME = 1135296000000;
|
|
|
|
| 653 |
var editor = document.getElementById('textEditor');
|
| 654 |
editor.addEventListener('input', updateCounts);
|
| 655 |
editor.addEventListener('input', scheduleDraftSave);
|
| 656 |
+
editor.addEventListener('input', saveCursorPosition);
|
| 657 |
editor.addEventListener('click', saveCursorPosition);
|
| 658 |
editor.addEventListener('keyup', saveCursorPosition);
|
| 659 |
editor.addEventListener('keydown', saveCursorPosition);
|
| 660 |
+
editor.addEventListener('select', saveCursorPosition);
|
| 661 |
+
editor.addEventListener('mouseup', saveCursorPosition);
|
| 662 |
+
editor.addEventListener('touchend', saveCursorPosition);
|
| 663 |
+
editor.addEventListener('scroll', rememberEditorScrollPosition);
|
| 664 |
+
editor.addEventListener('blur', saveCursorPosition);
|
| 665 |
|
| 666 |
// Initialize undo/redo
|
| 667 |
saveToHistoryStack();
|
|
|
|
| 728 |
}
|
| 729 |
|
| 730 |
if (final) {
|
| 731 |
+
insertSpeechTextAtSavedCursor(final);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 732 |
}
|
| 733 |
};
|
| 734 |
|
|
|
|
| 770 |
}
|
| 771 |
|
| 772 |
function toggleMic() {
|
| 773 |
+
if (micTransitionLock) return;
|
| 774 |
+
micTransitionLock = true;
|
|
|
|
|
|
|
|
|
|
| 775 |
|
| 776 |
+
try {
|
| 777 |
+
saveCursorPosition();
|
| 778 |
+
|
| 779 |
+
if (!recognition) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 780 |
recognition = initSpeech();
|
|
|
|
|
|
|
|
|
|
| 781 |
}
|
| 782 |
+
|
| 783 |
+
if (!recognition) {
|
| 784 |
+
micTransitionLock = false;
|
| 785 |
+
return;
|
| 786 |
+
}
|
| 787 |
+
|
| 788 |
+
if (isRecording) {
|
| 789 |
+
isRecording = false;
|
| 790 |
+
try {
|
| 791 |
+
recognition.stop();
|
| 792 |
+
} catch(e) {}
|
| 793 |
+
updateMicUI(false);
|
| 794 |
+
showToast("⏹️ Recording stopped");
|
| 795 |
+
setTimeout(function() { micTransitionLock = false; }, 250);
|
| 796 |
+
} else {
|
| 797 |
+
try {
|
| 798 |
+
recognition.start();
|
| 799 |
+
showToast("🎤 Recording... Speak now!");
|
| 800 |
+
setTimeout(function() { micTransitionLock = false; }, 250);
|
| 801 |
+
} catch(e) {
|
| 802 |
+
console.error(e);
|
| 803 |
+
showToast("⚠️ Error starting microphone");
|
| 804 |
+
recognition = initSpeech();
|
| 805 |
+
setTimeout(function() {
|
| 806 |
+
try { recognition.start(); } catch(e2) {}
|
| 807 |
+
micTransitionLock = false;
|
| 808 |
+
}, 500);
|
| 809 |
+
}
|
| 810 |
+
}
|
| 811 |
+
} catch(error) {
|
| 812 |
+
console.error(error);
|
| 813 |
+
micTransitionLock = false;
|
| 814 |
}
|
| 815 |
}
|
| 816 |
|
|
|
|
| 938 |
editor.value = payload.text;
|
| 939 |
currentTextState = payload.text;
|
| 940 |
cursorPosition = payload.text.length;
|
| 941 |
+
cursorEndPosition = cursorPosition;
|
| 942 |
editor.selectionStart = cursorPosition;
|
| 943 |
editor.selectionEnd = cursorPosition;
|
| 944 |
+
saveCursorPosition();
|
| 945 |
|
| 946 |
if (payload.lang === 'ur-PK' || payload.lang === 'en-US' || payload.lang === 'en-PK') {
|
| 947 |
setLang(payload.lang);
|
|
|
|
| 1055 |
document.getElementById('redoBtn').disabled = redoStack.length === 0;
|
| 1056 |
}
|
| 1057 |
|
| 1058 |
+
// ═══════════════════════════════════════════════════════════════════════════════
|
| 1059 |
+
// کرسر اور اسکرول لاک سسٹم - جاوا اسکرپٹ
|
| 1060 |
+
// CURSOR AND SCROLL LOCK SYSTEM - JAVASCRIPT
|
| 1061 |
+
// ═══════════════════════════════════════════════════════════════════════════════
|
| 1062 |
+
function rememberEditorScrollPosition() {
|
| 1063 |
+
var editor = document.getElementById('textEditor');
|
| 1064 |
+
if (!editor) return;
|
| 1065 |
+
|
| 1066 |
+
editorScrollTop = editor.scrollTop || 0;
|
| 1067 |
+
editorScrollLeft = editor.scrollLeft || 0;
|
| 1068 |
+
}
|
| 1069 |
+
|
| 1070 |
function saveCursorPosition() {
|
| 1071 |
var editor = document.getElementById('textEditor');
|
| 1072 |
+
if (!editor) return;
|
| 1073 |
+
|
| 1074 |
+
cursorPosition = typeof editor.selectionStart === 'number' ? editor.selectionStart : cursorPosition;
|
| 1075 |
+
cursorEndPosition = typeof editor.selectionEnd === 'number' ? editor.selectionEnd : cursorPosition;
|
| 1076 |
+
editorScrollTop = editor.scrollTop || 0;
|
| 1077 |
+
editorScrollLeft = editor.scrollLeft || 0;
|
| 1078 |
+
|
| 1079 |
+
var cursorLabel = document.getElementById('cursorPos');
|
| 1080 |
+
if (cursorLabel) cursorLabel.textContent = cursorPosition;
|
| 1081 |
+
}
|
| 1082 |
+
|
| 1083 |
+
function getTextareaCaretPixelTop(editor, caretPos) {
|
| 1084 |
+
if (!editor) return 0;
|
| 1085 |
+
|
| 1086 |
+
var style = window.getComputedStyle(editor);
|
| 1087 |
+
var mirror = document.createElement('div');
|
| 1088 |
+
var marker = document.createElement('span');
|
| 1089 |
+
var properties = [
|
| 1090 |
+
'boxSizing', 'width', 'height', 'overflowX', 'overflowY', 'borderTopWidth',
|
| 1091 |
+
'borderRightWidth', 'borderBottomWidth', 'borderLeftWidth', 'paddingTop',
|
| 1092 |
+
'paddingRight', 'paddingBottom', 'paddingLeft', 'fontStyle', 'fontVariant',
|
| 1093 |
+
'fontWeight', 'fontStretch', 'fontSize', 'fontSizeAdjust', 'lineHeight',
|
| 1094 |
+
'fontFamily', 'textAlign', 'textTransform', 'textIndent', 'textDecoration',
|
| 1095 |
+
'letterSpacing', 'wordSpacing', 'tabSize', 'MozTabSize', 'direction',
|
| 1096 |
+
'whiteSpace', 'wordBreak', 'overflowWrap'
|
| 1097 |
+
];
|
| 1098 |
+
|
| 1099 |
+
mirror.style.position = 'absolute';
|
| 1100 |
+
mirror.style.visibility = 'hidden';
|
| 1101 |
+
mirror.style.left = '-99999px';
|
| 1102 |
+
mirror.style.top = '0';
|
| 1103 |
+
mirror.style.whiteSpace = 'pre-wrap';
|
| 1104 |
+
mirror.style.wordBreak = 'break-word';
|
| 1105 |
+
mirror.style.overflowWrap = 'break-word';
|
| 1106 |
+
|
| 1107 |
+
properties.forEach(function(prop) {
|
| 1108 |
+
try { mirror.style[prop] = style[prop]; } catch (error) {}
|
| 1109 |
+
});
|
| 1110 |
+
|
| 1111 |
+
mirror.style.width = editor.offsetWidth + 'px';
|
| 1112 |
+
mirror.textContent = editor.value.substring(0, caretPos);
|
| 1113 |
+
marker.textContent = editor.value.substring(caretPos, caretPos + 1) || '.';
|
| 1114 |
+
mirror.appendChild(marker);
|
| 1115 |
+
document.body.appendChild(mirror);
|
| 1116 |
+
|
| 1117 |
+
var top = marker.offsetTop;
|
| 1118 |
+
document.body.removeChild(mirror);
|
| 1119 |
+
return top;
|
| 1120 |
+
}
|
| 1121 |
+
|
| 1122 |
+
function keepCaretVisibleWithoutJump(editor, caretPos) {
|
| 1123 |
+
if (!editor) return;
|
| 1124 |
+
|
| 1125 |
+
var style = window.getComputedStyle(editor);
|
| 1126 |
+
var lineHeight = parseFloat(style.lineHeight);
|
| 1127 |
+
if (!lineHeight || isNaN(lineHeight)) {
|
| 1128 |
+
lineHeight = Math.max(18, parseFloat(style.fontSize) * 1.6);
|
| 1129 |
+
}
|
| 1130 |
+
|
| 1131 |
+
var paddingTop = parseFloat(style.paddingTop) || 0;
|
| 1132 |
+
var paddingBottom = parseFloat(style.paddingBottom) || 0;
|
| 1133 |
+
var caretTop = getTextareaCaretPixelTop(editor, caretPos);
|
| 1134 |
+
var visibleTop = editor.scrollTop + paddingTop;
|
| 1135 |
+
var visibleBottom = editor.scrollTop + editor.clientHeight - paddingBottom - lineHeight;
|
| 1136 |
+
|
| 1137 |
+
if (caretTop < visibleTop) {
|
| 1138 |
+
editor.scrollTop = Math.max(0, caretTop - paddingTop - lineHeight);
|
| 1139 |
+
} else if (caretTop > visibleBottom) {
|
| 1140 |
+
editor.scrollTop = Math.max(0, caretTop - editor.clientHeight + paddingBottom + (lineHeight * 2));
|
| 1141 |
+
}
|
| 1142 |
+
|
| 1143 |
+
editorScrollTop = editor.scrollTop || 0;
|
| 1144 |
+
editorScrollLeft = editor.scrollLeft || 0;
|
| 1145 |
+
}
|
| 1146 |
+
|
| 1147 |
+
function restoreEditorViewportAndCaret(startPos, endPos, keepFocus, scrollMode) {
|
| 1148 |
+
var editor = document.getElementById('textEditor');
|
| 1149 |
+
if (!editor) return;
|
| 1150 |
+
|
| 1151 |
+
var safeStart = Math.max(0, Math.min(startPos, editor.value.length));
|
| 1152 |
+
var safeEnd = Math.max(0, Math.min(endPos, editor.value.length));
|
| 1153 |
+
|
| 1154 |
+
editor.selectionStart = safeStart;
|
| 1155 |
+
editor.selectionEnd = safeEnd;
|
| 1156 |
+
editor.scrollTop = editorScrollTop;
|
| 1157 |
+
editor.scrollLeft = editorScrollLeft;
|
| 1158 |
+
|
| 1159 |
+
if (scrollMode === 'keep-caret-visible') {
|
| 1160 |
+
keepCaretVisibleWithoutJump(editor, safeStart);
|
| 1161 |
+
}
|
| 1162 |
+
|
| 1163 |
+
if (keepFocus) {
|
| 1164 |
+
try {
|
| 1165 |
+
editor.focus({ preventScroll: true });
|
| 1166 |
+
} catch (error) {
|
| 1167 |
+
editor.focus();
|
| 1168 |
+
editor.scrollTop = editorScrollTop;
|
| 1169 |
+
editor.scrollLeft = editorScrollLeft;
|
| 1170 |
+
if (scrollMode === 'keep-caret-visible') {
|
| 1171 |
+
keepCaretVisibleWithoutJump(editor, safeStart);
|
| 1172 |
+
}
|
| 1173 |
+
}
|
| 1174 |
+
}
|
| 1175 |
+
|
| 1176 |
+
requestAnimationFrame(function() {
|
| 1177 |
+
editor.selectionStart = safeStart;
|
| 1178 |
+
editor.selectionEnd = safeEnd;
|
| 1179 |
+
editor.scrollTop = editorScrollTop;
|
| 1180 |
+
editor.scrollLeft = editorScrollLeft;
|
| 1181 |
+
|
| 1182 |
+
if (scrollMode === 'keep-caret-visible') {
|
| 1183 |
+
keepCaretVisibleWithoutJump(editor, safeStart);
|
| 1184 |
+
}
|
| 1185 |
+
});
|
| 1186 |
+
}
|
| 1187 |
+
|
| 1188 |
+
function normalizeInsertedSpeechText(text, beforeText, afterText) {
|
| 1189 |
+
var cleanText = (text || '').replace(/\s+/g, ' ').trim();
|
| 1190 |
+
if (!cleanText) return '';
|
| 1191 |
+
|
| 1192 |
+
if (beforeText.length > 0 && !/[\s\n]$/.test(beforeText)) {
|
| 1193 |
+
cleanText = ' ' + cleanText;
|
| 1194 |
+
}
|
| 1195 |
+
|
| 1196 |
+
if (afterText.length > 0 && !/^[\s\n.,!?;:،۔؟]/.test(afterText)) {
|
| 1197 |
+
cleanText = cleanText + ' ';
|
| 1198 |
+
} else if (afterText.length === 0) {
|
| 1199 |
+
cleanText = cleanText + ' ';
|
| 1200 |
+
}
|
| 1201 |
+
|
| 1202 |
+
return cleanText;
|
| 1203 |
+
}
|
| 1204 |
+
|
| 1205 |
+
function insertSpeechTextAtSavedCursor(text) {
|
| 1206 |
+
if (speechInsertLock) return;
|
| 1207 |
+
speechInsertLock = true;
|
| 1208 |
+
|
| 1209 |
+
try {
|
| 1210 |
+
var editor = document.getElementById('textEditor');
|
| 1211 |
+
if (!editor) return;
|
| 1212 |
+
|
| 1213 |
+
var savedScrollTop = editorScrollTop;
|
| 1214 |
+
var savedScrollLeft = editorScrollLeft;
|
| 1215 |
+
var start = Math.max(0, Math.min(cursorPosition, editor.value.length));
|
| 1216 |
+
var end = Math.max(start, Math.min(cursorEndPosition || start, editor.value.length));
|
| 1217 |
+
var beforeCursor = editor.value.substring(0, start);
|
| 1218 |
+
var afterCursor = editor.value.substring(end);
|
| 1219 |
+
var finalText = normalizeInsertedSpeechText(text, beforeCursor, afterCursor);
|
| 1220 |
+
|
| 1221 |
+
if (!finalText) return;
|
| 1222 |
+
|
| 1223 |
+
editorScrollTop = savedScrollTop;
|
| 1224 |
+
editorScrollLeft = savedScrollLeft;
|
| 1225 |
+
editor.value = beforeCursor + finalText + afterCursor;
|
| 1226 |
+
|
| 1227 |
+
cursorPosition = beforeCursor.length + finalText.length;
|
| 1228 |
+
cursorEndPosition = cursorPosition;
|
| 1229 |
+
|
| 1230 |
+
restoreEditorViewportAndCaret(cursorPosition, cursorPosition, true, 'keep-caret-visible');
|
| 1231 |
+
updateCounts();
|
| 1232 |
+
saveToHistoryStack();
|
| 1233 |
+
scheduleDraftSave();
|
| 1234 |
+
} finally {
|
| 1235 |
+
speechInsertLock = false;
|
| 1236 |
+
}
|
| 1237 |
}
|
| 1238 |
|
| 1239 |
function insertAtCursor(text) {
|
| 1240 |
var editor = document.getElementById('textEditor');
|
| 1241 |
+
if (!editor) return;
|
| 1242 |
+
|
| 1243 |
+
saveCursorPosition();
|
| 1244 |
+
|
| 1245 |
+
var savedScrollTop = editorScrollTop;
|
| 1246 |
+
var savedScrollLeft = editorScrollLeft;
|
| 1247 |
+
var start = Math.max(0, Math.min(cursorPosition, editor.value.length));
|
| 1248 |
+
var end = Math.max(start, Math.min(cursorEndPosition || start, editor.value.length));
|
| 1249 |
+
var beforeCursor = editor.value.substring(0, start);
|
| 1250 |
+
var afterCursor = editor.value.substring(end);
|
| 1251 |
+
|
| 1252 |
editor.value = beforeCursor + text + afterCursor;
|
| 1253 |
cursorPosition = beforeCursor.length + text.length;
|
| 1254 |
+
cursorEndPosition = cursorPosition;
|
| 1255 |
+
editorScrollTop = savedScrollTop;
|
| 1256 |
+
editorScrollLeft = savedScrollLeft;
|
| 1257 |
+
|
| 1258 |
+
restoreEditorViewportAndCaret(cursorPosition, cursorPosition, true, 'keep-caret-visible');
|
| 1259 |
updateCounts();
|
| 1260 |
saveToHistoryStack();
|
| 1261 |
scheduleDraftSave();
|
| 1262 |
}
|
| 1263 |
+
// ═══════════════════════════════════════════════════════════════════════════════
|
| 1264 |
+
// ختم کرسر اور اسکرول لاک سسٹم - جاوا اسکرپٹ
|
| 1265 |
+
// END CURSOR AND SCROLL LOCK SYSTEM - JAVASCRIPT
|
| 1266 |
+
// ═══════════════════════════════════════════════════════════════════════════════
|
| 1267 |
|
| 1268 |
// --- SHARE FUNCTIONALITY ---
|
| 1269 |
function shareText() {
|
|
|
|
| 1483 |
saveToHistoryStack();
|
| 1484 |
scheduleDraftSave();
|
| 1485 |
cursorPosition = item.text.length;
|
| 1486 |
+
cursorEndPosition = cursorPosition;
|
| 1487 |
+
document.getElementById('textEditor').selectionStart = cursorPosition;
|
| 1488 |
+
document.getElementById('textEditor').selectionEnd = cursorPosition;
|
| 1489 |
+
saveCursorPosition();
|
| 1490 |
}
|
| 1491 |
}
|
| 1492 |
|
|
|
|
| 1585 |
saveToHistoryStack();
|
| 1586 |
clearDraftText();
|
| 1587 |
cursorPosition = 0;
|
| 1588 |
+
cursorEndPosition = 0;
|
| 1589 |
+
saveCursorPosition();
|
| 1590 |
showToast("🗑️ Text cleared");
|
| 1591 |
}
|
| 1592 |
|