This commit is contained in:
Andras Schmelczer 2026-10-29 08:13:37 +00:00
commit a050ec252b
2 changed files with 58 additions and 65 deletions

View file

@ -99,65 +99,72 @@ function updateMergedText(): void {
selectedTokenizer selectedTokenizer
); );
let selectionStart: number = Number.NEGATIVE_INFINITY; const selection =
let selectionEnd: number = Number.NEGATIVE_INFINITY; results.cursors?.length === 2
if ((results.cursors?.length ?? 0) > 0) { ? {
selectionStart = results.cursors![0].position; start: results.cursors[0].position,
selectionEnd = results.cursors![1].position; end: results.cursors[1].position,
side: leftCursors ? 'left' : 'right',
} }
const isSelection = selectionStart !== selectionEnd; : undefined;
const selectionSide = leftCursors ? 'left' : 'right';
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
function appendSpan(text: string, className: string): void {
if (text.length === 0) {
return;
}
const span = document.createElement('span');
span.className = className;
span.textContent = text;
fragment.appendChild(span);
}
function appendCaret(): void {
if (!selection) {
return;
}
fragment.appendChild(
createSelectionOverlay(
selection.side === 'left',
selection.start !== selection.end
)
);
}
let currentPosition = 0; let currentPosition = 0;
if (selectionEnd === 0) { if (selection?.end === 0) {
fragment.appendChild(createSelectionOverlay(selectionSide === 'left', isSelection)); appendCaret();
} }
for (const { text, history } of results.history) { for (const { text, history } of results.history) {
const isDelete = history === 'RemovedFromLeft' || history === 'RemovedFromRight'; const isDelete = history === 'RemovedFromLeft' || history === 'RemovedFromRight';
let spanChars: string[] = []; if (isDelete || !selection) {
let currentClass = ''; appendSpan(text, history);
continue;
const flushSpan = () => {
if (spanChars.length > 0) {
const span = document.createElement('span');
span.className = currentClass;
span.textContent = spanChars.join('');
fragment.appendChild(span);
spanChars = [];
}
};
for (const character of text) {
let className = history;
if (
!isDelete &&
selectionStart <= currentPosition &&
currentPosition < selectionEnd
) {
className += ` selection-${selectionSide}`;
} }
if (className !== currentClass) { // Reconcile positions count Unicode scalars, rather than UTF-16 code units.
flushSpan(); const chars = [...text];
currentClass = className; const start = Math.max(0, Math.min(chars.length, selection.start - currentPosition));
} const end = Math.max(0, Math.min(chars.length, selection.end - currentPosition));
spanChars.push(character); appendSpan(chars.slice(0, start).join(''), history);
appendSpan(
if (!isDelete) { chars.slice(start, end).join(''),
if (currentPosition === selectionEnd - 1) { `${history} selection-${selection.side}`
flushSpan();
fragment.appendChild(
createSelectionOverlay(selectionSide === 'left', isSelection)
); );
}
currentPosition++; if (
} currentPosition < selection.end &&
selection.end <= currentPosition + chars.length
) {
appendCaret();
} }
flushSpan(); appendSpan(chars.slice(end).join(''), history);
currentPosition += chars.length;
} }
mergedTextArea.innerHTML = ''; mergedTextArea.innerHTML = '';

View file

@ -25,7 +25,6 @@ $gradient-end: #e0e7ef;
* { * {
box-sizing: border-box; box-sizing: border-box;
margin: 0; margin: 0;
user-select: none;
} }
html, html,
@ -76,13 +75,6 @@ header > h1 {
text-align: center; text-align: center;
} }
h1,
code,
p,
p * {
user-select: text;
}
code { code {
background: $code-bg; background: $code-bg;
color: $code-text; color: $code-text;
@ -121,6 +113,7 @@ main {
} }
.radio-group { .radio-group {
user-select: none;
display: flex; display: flex;
gap: 16px; gap: 16px;
justify-content: center; justify-content: center;
@ -265,6 +258,7 @@ main {
} }
label { label {
user-select: none;
display: inline-block; display: inline-block;
margin-bottom: 8px; margin-bottom: 8px;
font-weight: 600; font-weight: 600;
@ -309,7 +303,6 @@ textarea {
box-sizing: border-box; box-sizing: border-box;
resize: none; resize: none;
cursor: text; cursor: text;
user-select: text;
caret-color: $primary-blue; caret-color: $primary-blue;
outline: 2px solid transparent; outline: 2px solid transparent;
outline-offset: 2px; outline-offset: 2px;
@ -334,7 +327,6 @@ textarea:focus {
#merged { #merged {
width: 100%; width: 100%;
user-select: text;
> * { > * {
position: relative; position: relative;
@ -349,14 +341,9 @@ textarea:focus {
} }
} }
.Unchanged {
user-select: text;
}
.Left, .Left,
.AddedFromLeft, .AddedFromLeft,
.RemovedFromLeft { .RemovedFromLeft {
user-select: text;
background: $green; background: $green;
} }
@ -381,7 +368,6 @@ textarea:focus {
.Right, .Right,
.AddedFromRight, .AddedFromRight,
.RemovedFromRight { .RemovedFromRight {
user-select: text;
background: $light-blue; background: $light-blue;
} }
@ -396,6 +382,7 @@ $CARET_WIDTH: 2;
$DOT_RADIUS: 4; $DOT_RADIUS: 4;
.selection-caret { .selection-caret {
user-select: none;
position: relative; position: relative;
z-index: 1000; z-index: 1000;
@ -453,7 +440,6 @@ $DOT_RADIUS: 4;
top: -1.3em; top: -1.3em;
left: calc(-#{$CARET_WIDTH} / 2 * 1px); left: calc(-#{$CARET_WIDTH} / 2 * 1px);
font-size: 0.9em; font-size: 0.9em;
user-select: none;
color: white; color: white;
padding: 0 2px; padding: 0 2px;
transition: opacity 0.3s ease-in-out; transition: opacity 0.3s ease-in-out;