Dynamic word tracker with added labels

by Julian

HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Index-Based Text Tracker</title>
  <style>
    textarea {
      width: 100%;
      height: 150px;
      margin-bottom: 10px;
      padding: 10px;
      font-size: 16px;
    }
    button {
      padding: 10px 20px;
      font-size: 16px;
      cursor: pointer;
    }
    pre {
      background-color: #f4f4f4;
      padding: 10px;
      font-size: 14px;
    }
    #targetTextDisplay {
      border: 1px solid #ccc;
      padding: 10px;
      font-size: 16px;
      white-space: pre-wrap;
      line-height: 1.5;
      background-color: #f9f9f9;
    }
    .word {
      position: relative;
      margin-right: 5px;
      display: inline-block;
    }
    .index {
      font-size: 12px;
      color: #999;
      position: absolute;
      top: -10px;
      right: 0;
    }
    .warning {
      color: red;
      font-weight: bold;
    }
  </style>
</head>
<body>

  <h1>Index-Based Text Tracker</h1>

  <label for="sourceText">Enter Source Text:</label>
  <textarea id="sourceText" placeholder="Paste the original source text here..." oninput="updateSourceWithIndexes()"></textarea>

  <h2>Source Text with Indexes</h2>
  <pre id="indexedSourceText"></pre>

  <button onclick="addSelectedText()">Paste Selected Text</button>

  <label for="targetTextDisplay">Target Text Area:</label>
  <div id="targetTextDisplay"></div>

  <h2>Word Indexes</h2>
  <pre id="output"></pre>
</body>
</html>

CSS

body {
      font-family: Arial, sans-serif;
      margin: 20px;
    }
    div[contenteditable] {
      border: 1px solid #ccc;
      padding: 10px;
      height: 100px;
      overflow-y: auto;
      margin-bottom: 10px;
    }
    .output {
      margin-top: 10px;
    }

JavaScript

let wordOccurrences = {}; // Maps words to their indexes
    let indexMap = {}; // Maps indexes to words
    let usedIndexes = new Set(); // Tracks used indexes

    function updateSourceWithIndexes() {
      const sourceText = document.getElementById('sourceText').value.trim();
      const indexedSourceText = document.getElementById('indexedSourceText');

      if (!sourceText) {
        indexedSourceText.textContent = "No source text provided.";
        return;
      }

      const sourceWords = sourceText.split(/\s+/);
      wordOccurrences = {};
      indexMap = {};
      usedIndexes = new Set();

      const indexedWords = sourceWords.map((word, index) => {
        const lowerWord = word.toLowerCase();
        const wordIndex = index + 1; // Start indexing from 1

        // Map word to its indexes and indexes to words
        if (!wordOccurrences[lowerWord]) {
          wordOccurrences[lowerWord] = [];
        }
        wordOccurrences[lowerWord].push(wordIndex);
        indexMap[wordIndex] = word;

        return `${word}(${wordIndex})`;
      });

      indexedSourceText.textContent = indexedWords.join(' ');
    }

    function addSelectedText() {
      const targetTextDisplay = document.getElementById('targetTextDisplay');
      const outputElement = document.getElementById('output');
      const selectedText = window.getSelection().toString().trim();

      if (!selectedText) {
        outputElement.textContent = "Please select text from the source text.";
        return;
      }

      const wordsToAdd = selectedText.split(/\s+/);
      const fragment = document.createDocumentFragment();
      const wordIndexMap = [];
      const warnings = [];

      let lastIndex = -1;

      wordsToAdd.forEach((word, i) => {
        let index;

        // Search index by word
        if (lastIndex !== -1) {
          index = findNextConsecutiveIndex(word.toLowerCase(), lastIndex);
        }

        // Fallback: find index directly and validate
        if (index ===...