Godito commited on
Commit
141728f
·
verified ·
1 Parent(s): d2afab9

edit when reading highlight only the word pronounced - Follow Up Deployment

Browse files
Files changed (1) hide show
  1. index.html +79 -80
index.html CHANGED
@@ -119,6 +119,12 @@
119
  margin-bottom: 1.5rem;
120
  text-align: justify;
121
  }
 
 
 
 
 
 
122
 
123
  .book-content img {
124
  max-width: 100%;
@@ -382,24 +388,6 @@
382
  justify-content: center;
383
  }
384
 
385
- .word-highlight {
386
- background-color: #dbeafe;
387
- border-radius: 4px;
388
- padding: 0 2px;
389
- transition: background-color 0.2s ease;
390
- }
391
-
392
- .word-highlight.current {
393
- background-color: #3b82f6;
394
- color: white;
395
- animation: pulse 0.5s infinite;
396
- }
397
-
398
- @keyframes pulse {
399
- 0%, 100% { opacity: 1; }
400
- 50% { opacity: 0.8; }
401
- }
402
-
403
  @keyframes pulse {
404
  0%, 100% { opacity: 1; }
405
  50% { opacity: 0.7; }
@@ -1087,8 +1075,9 @@
1087
  synthesis.cancel();
1088
  }
1089
 
1090
- // Prepare the text for word-by-word highlighting
1091
- highlightReadingText();
 
1092
 
1093
  currentUtterance = new SpeechSynthesisUtterance(text);
1094
  currentUtterance.rate = audioSpeed;
@@ -1102,43 +1091,81 @@
1102
  currentUtterance.addEventListener('end', () => {
1103
  isPlaying = false;
1104
  document.getElementById('playBtn').innerHTML = '<i class="fas fa-play"></i>';
1105
- removeHighlights();
1106
  });
1107
 
1108
  currentUtterance.addEventListener('boundary', (event) => {
 
 
 
 
 
1109
  if (event.name === 'word') {
1110
- const wordElements = bookContent.querySelectorAll('.word-highlight');
1111
- const currentIndex = event.charIndex;
1112
-
1113
- // Find the word element that contains this character index
1114
- let wordIndex = 0;
1115
- let charCount = 0;
1116
 
1117
- for (let i = 0; i < wordElements.length; i++) {
1118
- const wordLength = wordElements[i].textContent.length;
1119
- if (currentIndex >= charCount && currentIndex < charCount + wordLength) {
1120
- wordIndex = i;
1121
- break;
1122
- }
1123
- charCount += wordLength + 1; // +1 for space
1124
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1125
 
1126
- // Remove highlight from previous word
1127
- if (currentBook.lastHighlightedWord !== undefined) {
1128
- wordElements[currentBook.lastHighlightedWord]?.classList.remove('current');
1129
- }
1130
 
1131
- // Add highlight to current word
1132
- wordElements[wordIndex].classList.add('current');
1133
- wordElements[wordIndex].scrollIntoView({
1134
  behavior: 'smooth',
1135
  block: 'center'
1136
  });
1137
- currentBook.lastHighlightedWord = wordIndex;
1138
  }
1139
- });
 
1140
 
1141
- synthesis.speak(currentUtterance);
1142
  }
1143
  }
1144
 
@@ -1155,16 +1182,14 @@
1155
  synthesis.cancel();
1156
  isPlaying = false;
1157
  document.getElementById('playBtn').innerHTML = '<i class="fas fa-play"></i>';
1158
- removeHighlights();
1159
 
1160
- // Clean up
1161
- if (currentBook) {
1162
- currentBook.lastHighlightedWord = undefined;
1163
- if (currentBook.highlightInterval) {
1164
- clearInterval(currentBook.highlightInterval);
1165
- currentBook.highlightInterval = undefined;
1166
- }
1167
- }
1168
  }
1169
  }
1170
 
@@ -1207,32 +1232,6 @@
1207
  voicePitch = parseFloat(pitch);
1208
  }
1209
 
1210
- function highlightReadingText() {
1211
- const bookContent = document.getElementById('bookContent');
1212
- // Wrap each word in a span for highlighting
1213
- bookContent.innerHTML = bookContent.textContent.replace(/\S+/g, word =>
1214
- `<span class="word-highlight">${word}</span>`
1215
- );
1216
- }
1217
-
1218
- function removeHighlights() {
1219
- const bookContent = document.getElementById('bookContent');
1220
- const wordElements = bookContent.querySelectorAll('.word-highlight');
1221
-
1222
- // Clear any active highlighting interval
1223
- if (currentBook && currentBook.highlightInterval) {
1224
- clearInterval(currentBook.highlightInterval);
1225
- }
1226
-
1227
- // Remove all word highlights
1228
- wordElements.forEach(el => el.classList.remove('current'));
1229
-
1230
- // Restore original text (remove span wrappers)
1231
- if (wordElements.length > 0) {
1232
- bookContent.innerHTML = bookContent.textContent;
1233
- }
1234
- }
1235
-
1236
  function estimateReadingTime(text) {
1237
  const wordsPerMinute = 200;
1238
  const words = text.split(' ').length;
 
119
  margin-bottom: 1.5rem;
120
  text-align: justify;
121
  }
122
+
123
+ .highlight-word {
124
+ background-color: rgba(59, 130, 246, 0.3);
125
+ border-radius: 2px;
126
+ transition: background-color 0.2s ease;
127
+ }
128
 
129
  .book-content img {
130
  max-width: 100%;
 
388
  justify-content: center;
389
  }
390
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  @keyframes pulse {
392
  0%, 100% { opacity: 1; }
393
  50% { opacity: 0.7; }
 
1075
  synthesis.cancel();
1076
  }
1077
 
1078
+ // Clear previous highlights
1079
+ const highlighted = document.querySelectorAll('.highlight-word');
1080
+ highlighted.forEach(el => el.classList.remove('highlight-word'));
1081
 
1082
  currentUtterance = new SpeechSynthesisUtterance(text);
1083
  currentUtterance.rate = audioSpeed;
 
1091
  currentUtterance.addEventListener('end', () => {
1092
  isPlaying = false;
1093
  document.getElementById('playBtn').innerHTML = '<i class="fas fa-play"></i>';
 
1094
  });
1095
 
1096
  currentUtterance.addEventListener('boundary', (event) => {
1097
+ // Clear previous highlights
1098
+ const highlighted = document.querySelectorAll('.highlight-word');
1099
+ highlighted.forEach(el => el.classList.remove('highlight-word'));
1100
+
1101
+ // Highlight current word
1102
  if (event.name === 'word') {
1103
+ const textNode = event.utterance.text.substring(0, event.charIndex);
1104
+ const wordStart = textNode.lastIndexOf(' ') + 1;
1105
+ const wordEnd = event.utterance.text.indexOf(' ', event.charIndex);
1106
+ const word = event.utterance.text.substring(wordStart, wordEnd === -1 ? event.utterance.text.length : wordEnd);
 
 
1107
 
1108
+ // Find and highlight the word in the DOM
1109
+ highlightWordInContent(word, event.charIndex);
1110
+ }
1111
+ });
1112
+
1113
+ synthesis.speak(currentUtterance);
1114
+ }
1115
+ }
1116
+
1117
+ function highlightWordInContent(word, charIndex) {
1118
+ const bookContent = document.getElementById('bookContent');
1119
+ const textNodes = [];
1120
+
1121
+ // Find all text nodes in the content
1122
+ const walker = document.createTreeWalker(
1123
+ bookContent,
1124
+ NodeFilter.SHOW_TEXT,
1125
+ null,
1126
+ false
1127
+ );
1128
+
1129
+ let node;
1130
+ while (node = walker.nextNode()) {
1131
+ textNodes.push(node);
1132
+ }
1133
+
1134
+ // Find the node containing the word
1135
+ let cumulativeLength = 0;
1136
+ for (const node of textNodes) {
1137
+ const nodeText = node.nodeValue;
1138
+ const nodeLength = nodeText.length;
1139
+
1140
+ if (charIndex >= cumulativeLength && charIndex < cumulativeLength + nodeLength) {
1141
+ const offset = charIndex - cumulativeLength;
1142
+ const wordStartInNode = nodeText.lastIndexOf(' ', offset) + 1;
1143
+ const wordEndInNode = nodeText.indexOf(' ', offset);
1144
+ const wordInNode = nodeText.substring(
1145
+ wordStartInNode,
1146
+ wordEndInNode === -1 ? nodeText.length : wordEndInNode
1147
+ );
1148
+
1149
+ if (wordInNode === word) {
1150
+ // Create range and wrap the word in a span
1151
+ const range = document.createRange();
1152
+ range.setStart(node, wordStartInNode);
1153
+ range.setEnd(node, wordEndInNode === -1 ? nodeText.length : wordEndInNode);
1154
 
1155
+ const span = document.createElement('span');
1156
+ span.className = 'highlight-word';
1157
+ range.surroundContents(span);
 
1158
 
1159
+ // Scroll to the word
1160
+ span.scrollIntoView({
 
1161
  behavior: 'smooth',
1162
  block: 'center'
1163
  });
 
1164
  }
1165
+ break;
1166
+ }
1167
 
1168
+ cumulativeLength += nodeLength;
1169
  }
1170
  }
1171
 
 
1182
  synthesis.cancel();
1183
  isPlaying = false;
1184
  document.getElementById('playBtn').innerHTML = '<i class="fas fa-play"></i>';
 
1185
 
1186
+ // Clear all highlights
1187
+ const highlighted = document.querySelectorAll('.highlight-word');
1188
+ highlighted.forEach(el => {
1189
+ const parent = el.parentNode;
1190
+ parent.replaceChild(document.createTextNode(el.textContent), el);
1191
+ parent.normalize();
1192
+ });
 
1193
  }
1194
  }
1195
 
 
1232
  voicePitch = parseFloat(pitch);
1233
  }
1234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1235
  function estimateReadingTime(text) {
1236
  const wordsPerMinute = 200;
1237
  const words = text.split(' ').length;