stack_tweaked 4

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>Word Index Tracker</title>

</head>
<body>


<form id="indexer">
  <button id="toggle" class="off" type="button"></button>
  <button id="add-selection" type="button">Add Selected Text</button>
  <fieldset id="source">
    Paste text here
  </fieldset>
  <output id="selected">
    <ol></ol>
  </output>
</form>
 </body>
</html>

CSS

:root {
  font: 2ch/1.5 "Segoe UI"
}

#toggle, #add-selection {
  margin-bottom: 1rem;
  padding: 5px 10px;
  border-radius: 8px;
  font: inherit;
  cursor: pointer;
  background: 
  radial-gradient(
    ellipse at center,    
    rgba(197,222,234,1) 0%,
    rgba(138,187,215,1) 31%,
    rgba(6,109,171,1) 100%);
}

#toggle::before {
  content: "Edit Mode"
}

#toggle::after {
  content: " ON";
}

#toggle.off {
  color: #fff;
  background: 
  radial-gradient(
    ellipse at center,
    rgba(125, 126, 125, 1) 0%, 
    rgba(14, 14, 14, 1) 100%);
}

#add-selection {
  display: inline-block;
}

#source[contenteditable="true"] + #add-selection {
  display: inline-block;
}

JavaScript

// Reference <form>
const form = document.forms.indexer;
/**
 * Reference all form controls of <form>
 * In this layout it is:
 *   - <button>
 *   - <fieldset>
 *   - <output>
 */
const io = form.elements;
// Reference <button>
const btn = io.toggle;
const addSelectionBtn = io['add-selection'];
// Reference <fieldset>
const src = io.source;
// Reference <output>
const sel = io.selected;

/**
 * Index each word by extracting the string,
 * converting the string into an array of 
 * substrings, and wrapping each word with
 * a <mark> element. Each <mark> is then 
 * assigned a [data-idx] attribute with the
 * index starting from 1.
 */
const indexText = () => {
  const text = src.innerText;
  let marks = text.split(/\s+/);

  marks = marks.map((word, idx) => {
    const wordIndex = idx + 1; // Index starts from 1
    return `<mark data-idx="${wordIndex}">${word}</mark>`;
  });

  src.innerHTML = marks.join(" ").trim();
};

/**
 * Handle "click" event when the user clicks
 * button#toggle. Basically an on/off switch
 * for whether fieldset#source can be edited
 * and when the text can be indexed.
 * @param {object} e - Event object
 */
const editText = (e) => {
  e.target.classList.toggle("off");
  src.toggleAttribute("contenteditable");
  indexText();
};

/**
 * Handle "click" event when user clicks "Add Selected Text" button.
 * Adds the selected portion of text along with its start and end indices.
 */
const addSelection = () => {
  const selection = window.getSelection();
  const selectedText = selection.toString().trim();

  if (selectedText) {
    const words = src.innerText.split(/\s+/);
    const startIdx = words.indexOf(selectedText) + 1; // 1-based index
    const endIdx = startIdx; // For single word selection

    sel.firstElementChild.innerHTML += `<li>${selectedText} (Start Index: ${startIdx}, End Index: ${endIdx})</li>`;
  }
};

btn.addEventListener("click", editText);
addSelectionBtn.addEventListener("click", addSelection);