JSFiddle - React, Tailwind, and code Playground
by alexb
HTML
<div class="content">
<span>Note</span>
<span>Front-end:</span> <span>Use</span> <span>Angular</span>, <span>React</span> <span>or</span> <span>the</span> <span>framework</span> <span>of</span> <span>your</span> <span>choice</span>.
<span>Back-end:</span> <span>Use</span> <span>NodeJS</span> <span>(preferably)</span> <span>+</span> <span>any</span> <span>fancy</span> <span>plugins</span> <span>you</span> <span>find</span> <span>suitable</span> <span>for</span> <span>the</span> <span>task</span> <span>at</span> <span>hand</span>. <span>Other</span> <span>back-ends</span> <span>are</span> <span>acceptable</span> <span>too</span>.
<span>You</span> <span>can</span> <span>use</span> <span>Google</span>, <span>your</span> <span>Github</span> <span>profile</span>, <span>peek</span> <span>into</span> <span>your</span> <span>older</span> <span>projects</span> <span>or</span> <span>use</span> <span>any</span> <span>code</span> <span>sharing</span> <span>websites</span> <span>you</span> <span>find</span> <span>relevant</span>.
<span>You</span> <span>can</span> <span>use</span> <span>Wikipedia</span> <span>for</span> <span>the</span> <span>text</span>. <span>~400</span> <span>words</span>. <span>The</span> <span>content</span> <span>and</span> <span>language</span> <span>is</span> <span>irrelevant</span>.
<span>Assignment</span> <span>description</span>
<span>Develop</span> <span>a</span> <span>Backend</span> <span>+</span> <span>Front-end</span> <span>solution</span> <span>that</span> <span>allows</span> <span>your</span> <span>user</span> <span>to</span> <span>annotate</span> <span>a</span> <span>blob</span> <span>of</span> <span>text</span>. <span>The</span> <span>user</span> <span>wants</span> <span>to</span> <span>be</span> <span>able</span> <span>to</span> <span>select</span> <span>a</span> <span>substring</span> <span>of</span> <span>the</span> <span>text</span> <span>document</span> <span>and</span> <span>persist</span> <span>the</span>...
CSS
.content {
white-space: pre-wrap;
}
.content span:hover {
/* background: cornflowerblue;
color: white;*/
cursor: pointer;
outline: 1px dashed cornflowerblue;
}
.content span.selected {
background: cornflowerblue;
color: white;
}
JavaScript
const selectedTokens = new Set();
function getTokenIndex(el) {
let index = 0;
while (el = el.previousSibling) {
if (el.tagName === 'SPAN') {
index++;
}
}
return index;
}
function toggleToken(e) {
if (e.target.tagName !== 'SPAN') {
return;
}
const index = getTokenIndex(e.target);
if (selectedTokens.has(index)) {
selectedTokens.delete(index);
e.target.classList.remove('selected');
}
else {
selectedTokens.add(index);
e.target.classList.add('selected');
}
}
document.querySelector('.content').addEventListener('click', toggleToken);