MikeTrizna commited on
Commit
c184c36
·
1 Parent(s): c388398

Added find and replace functionality, with case sensitivity functionality

Browse files
Files changed (1) hide show
  1. index.html +229 -0
index.html CHANGED
@@ -118,6 +118,65 @@
118
  align-items: center;
119
  }
120
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  .pdf-controls {
122
  display: flex;
123
  gap: 10px;
@@ -350,6 +409,21 @@
350
  <button class="export-btn" id="export-btn" disabled>Export Text</button>
351
  </div>
352
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
353
  <div class="panel-content">
354
  <textarea
355
  id="ocr-text"
@@ -388,6 +462,15 @@
388
  const zoomResetBtn = document.getElementById('zoom-reset-btn');
389
  const zoomLevel = document.getElementById('zoom-level');
390
 
 
 
 
 
 
 
 
 
 
391
  // Initialize application
392
  async function init() {
393
  try {
@@ -463,10 +546,154 @@
463
  zoomOutBtn.addEventListener('click', () => zoomPdf(-0.25));
464
  zoomResetBtn.addEventListener('click', () => resetZoom());
465
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  // Pan functionality
467
  setupPanControls();
468
  }
469
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
470
  async function loadRecord(record) {
471
  state.currentRecord = record;
472
  state.hasEdits = false;
@@ -543,6 +770,7 @@
543
  ocrText.value = '';
544
  ocrText.disabled = true;
545
  exportBtn.disabled = true;
 
546
  state.hasEdits = false;
547
 
548
  const loadingDiv = document.createElement('div');
@@ -563,6 +791,7 @@
563
  ocrText.value = text;
564
  ocrText.disabled = false;
565
  exportBtn.disabled = false;
 
566
  loadingDiv.remove();
567
  } catch (error) {
568
  console.error('OCR loading error:', error);
 
118
  align-items: center;
119
  }
120
 
121
+ /* Search Toolbar Styles */
122
+ .search-toolbar {
123
+ background-color: #ecf0f1;
124
+ padding: 8px 15px;
125
+ display: flex;
126
+ gap: 8px;
127
+ align-items: center;
128
+ border-bottom: 1px solid #bdc3c7;
129
+ }
130
+
131
+ .search-toolbar input[type="text"] {
132
+ padding: 4px 8px;
133
+ border: 1px solid #bdc3c7;
134
+ border-radius: 3px;
135
+ font-size: 13px;
136
+ width: 140px;
137
+ }
138
+
139
+ .search-toolbar button {
140
+ padding: 4px 10px;
141
+ background-color: #fff;
142
+ border: 1px solid #bdc3c7;
143
+ border-radius: 3px;
144
+ cursor: pointer;
145
+ font-size: 12px;
146
+ color: #2c3e50;
147
+ transition: all 0.2s;
148
+ }
149
+
150
+ .search-toolbar button:hover:not(:disabled) {
151
+ background-color: #e8e8e8;
152
+ border-color: #95a5a6;
153
+ }
154
+
155
+ .search-toolbar button:disabled {
156
+ opacity: 0.5;
157
+ cursor: not-allowed;
158
+ }
159
+
160
+ .search-toolbar label {
161
+ display: flex;
162
+ align-items: center;
163
+ gap: 4px;
164
+ font-size: 12px;
165
+ color: #2c3e50;
166
+ cursor: pointer;
167
+ user-select: none;
168
+ }
169
+
170
+ .search-toolbar input[type="checkbox"] {
171
+ cursor: pointer;
172
+ }
173
+
174
+ .search-msg {
175
+ font-size: 12px;
176
+ color: #7f8c8d;
177
+ margin-left: auto;
178
+ }
179
+
180
  .pdf-controls {
181
  display: flex;
182
  gap: 10px;
 
409
  <button class="export-btn" id="export-btn" disabled>Export Text</button>
410
  </div>
411
  </div>
412
+
413
+ <!-- Added Search Toolbar -->
414
+ <div class="search-toolbar">
415
+ <input type="text" id="find-input" placeholder="Find..." disabled>
416
+ <input type="text" id="replace-input" placeholder="Replace with..." disabled>
417
+ <button id="find-next-btn" disabled title="Find Next Occurrence">Find Next</button>
418
+ <button id="replace-btn" disabled title="Replace Current Selection">Replace</button>
419
+ <button id="replace-all-btn" disabled title="Replace All Occurrences">Replace All</button>
420
+ <label>
421
+ <input type="checkbox" id="case-sensitive-cb" disabled>
422
+ Match Case
423
+ </label>
424
+ <span id="search-msg" class="search-msg"></span>
425
+ </div>
426
+
427
  <div class="panel-content">
428
  <textarea
429
  id="ocr-text"
 
462
  const zoomResetBtn = document.getElementById('zoom-reset-btn');
463
  const zoomLevel = document.getElementById('zoom-level');
464
 
465
+ // Search DOM elements
466
+ const findInput = document.getElementById('find-input');
467
+ const replaceInput = document.getElementById('replace-input');
468
+ const findNextBtn = document.getElementById('find-next-btn');
469
+ const replaceBtn = document.getElementById('replace-btn');
470
+ const replaceAllBtn = document.getElementById('replace-all-btn');
471
+ const caseSensitiveCb = document.getElementById('case-sensitive-cb');
472
+ const searchMsg = document.getElementById('search-msg');
473
+
474
  // Initialize application
475
  async function init() {
476
  try {
 
546
  zoomOutBtn.addEventListener('click', () => zoomPdf(-0.25));
547
  zoomResetBtn.addEventListener('click', () => resetZoom());
548
 
549
+ // Search controls
550
+ findNextBtn.addEventListener('click', findNext);
551
+ replaceBtn.addEventListener('click', replaceCurrent);
552
+ replaceAllBtn.addEventListener('click', replaceAll);
553
+
554
+ // Allow "Enter" key in find input to trigger Find Next
555
+ findInput.addEventListener('keypress', (e) => {
556
+ if (e.key === 'Enter') {
557
+ findNext();
558
+ }
559
+ });
560
+
561
  // Pan functionality
562
  setupPanControls();
563
  }
564
 
565
+ // --- Search Functions ---
566
+
567
+ function findNext() {
568
+ const query = findInput.value;
569
+ if (!query) return;
570
+
571
+ const isCaseSensitive = caseSensitiveCb.checked;
572
+ const text = ocrText.value;
573
+ const startPos = ocrText.selectionEnd; // Start searching from after current cursor/selection
574
+
575
+ let nextPos = -1;
576
+
577
+ // Perform search based on case sensitivity
578
+ if (isCaseSensitive) {
579
+ nextPos = text.indexOf(query, startPos);
580
+ if (nextPos === -1) {
581
+ nextPos = text.indexOf(query, 0); // Wrap
582
+ searchMsg.textContent = nextPos !== -1 ? "Wrapped to top" : "Not found";
583
+ } else {
584
+ searchMsg.textContent = "";
585
+ }
586
+ } else {
587
+ const lowerText = text.toLowerCase();
588
+ const lowerQuery = query.toLowerCase();
589
+ nextPos = lowerText.indexOf(lowerQuery, startPos);
590
+ if (nextPos === -1) {
591
+ nextPos = lowerText.indexOf(lowerQuery, 0); // Wrap
592
+ searchMsg.textContent = nextPos !== -1 ? "Wrapped to top" : "Not found";
593
+ } else {
594
+ searchMsg.textContent = "";
595
+ }
596
+ }
597
+
598
+ if (nextPos !== -1) {
599
+ // Select the found text
600
+ ocrText.focus();
601
+ ocrText.setSelectionRange(nextPos, nextPos + query.length);
602
+
603
+ // Enhanced Scroll to selection logic
604
+ // Calculate percentage position of the match
605
+ const progress = nextPos / text.length;
606
+ // Set scroll top based on percentage of total scroll height
607
+ // Offset by half container height to try and center it
608
+ const scrollTarget = (ocrText.scrollHeight * progress) - (ocrText.clientHeight / 2);
609
+
610
+ ocrText.scrollTop = scrollTarget;
611
+
612
+ // Fallback: Trigger blur/focus which forces browser to scroll to cursor
613
+ // This handles edge cases where the calculation might be slightly off due to variable line wrapping
614
+ // setTimeout ensures the scroll calculation happens first, then the browser creates final view
615
+ setTimeout(() => {
616
+ ocrText.blur();
617
+ ocrText.focus();
618
+ }, 10);
619
+ }
620
+ }
621
+
622
+ function replaceCurrent() {
623
+ const query = findInput.value;
624
+ const replacement = replaceInput.value;
625
+ if (!query) return;
626
+
627
+ const isCaseSensitive = caseSensitiveCb.checked;
628
+
629
+ // Check if current selection matches the find query
630
+ const start = ocrText.selectionStart;
631
+ const end = ocrText.selectionEnd;
632
+ const selectedText = ocrText.value.substring(start, end);
633
+
634
+ let match = false;
635
+ if (isCaseSensitive) {
636
+ match = selectedText === query;
637
+ } else {
638
+ match = selectedText.toLowerCase() === query.toLowerCase();
639
+ }
640
+
641
+ if (match) {
642
+ // Perform replacement
643
+ ocrText.setRangeText(replacement, start, end, 'end');
644
+ state.hasEdits = true;
645
+ searchMsg.textContent = "Replaced";
646
+
647
+ // Automatically find next
648
+ findNext();
649
+ } else {
650
+ // If text isn't selected or doesn't match, try to find next occurrence first
651
+ findNext();
652
+ }
653
+ }
654
+
655
+ function replaceAll() {
656
+ const query = findInput.value;
657
+ const replacement = replaceInput.value;
658
+ if (!query) return;
659
+
660
+ const isCaseSensitive = caseSensitiveCb.checked;
661
+ const text = ocrText.value;
662
+
663
+ // Escape special regex characters to perform a literal "Replace All"
664
+ const escapedQuery = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
665
+ const flags = isCaseSensitive ? 'g' : 'gi';
666
+ const regex = new RegExp(escapedQuery, flags);
667
+
668
+ const matchCount = (text.match(regex) || []).length;
669
+
670
+ if (matchCount > 0) {
671
+ const newText = text.replace(regex, replacement);
672
+ ocrText.value = newText;
673
+ state.hasEdits = true;
674
+ searchMsg.textContent = `Replaced ${matchCount} occurrences`;
675
+ } else {
676
+ searchMsg.textContent = "0 matches found";
677
+ }
678
+ }
679
+
680
+ function updateSearchControls(enabled) {
681
+ findInput.disabled = !enabled;
682
+ replaceInput.disabled = !enabled;
683
+ findNextBtn.disabled = !enabled;
684
+ replaceBtn.disabled = !enabled;
685
+ replaceAllBtn.disabled = !enabled;
686
+ caseSensitiveCb.disabled = !enabled;
687
+ if (!enabled) {
688
+ findInput.value = '';
689
+ replaceInput.value = '';
690
+ caseSensitiveCb.checked = false;
691
+ searchMsg.textContent = '';
692
+ }
693
+ }
694
+
695
+ // --- End Search Functions ---
696
+
697
  async function loadRecord(record) {
698
  state.currentRecord = record;
699
  state.hasEdits = false;
 
770
  ocrText.value = '';
771
  ocrText.disabled = true;
772
  exportBtn.disabled = true;
773
+ updateSearchControls(false); // Disable search controls while loading
774
  state.hasEdits = false;
775
 
776
  const loadingDiv = document.createElement('div');
 
791
  ocrText.value = text;
792
  ocrText.disabled = false;
793
  exportBtn.disabled = false;
794
+ updateSearchControls(true); // Enable search controls
795
  loadingDiv.remove();
796
  } catch (error) {
797
  console.error('OCR loading error:', error);