Better md example & hide deleted text

This commit is contained in:
Andras Schmelczer 2026-10-28 23:20:17 +00:00
commit 5eac6bb012
3 changed files with 108 additions and 53 deletions

View file

@ -99,7 +99,11 @@
<main> <main>
<section class="tokenizer-selector"> <section class="tokenizer-selector">
<div class="radio-group" role="radiogroup" aria-label="Tokenization strategy"> <div
class="radio-group"
role="radiogroup"
aria-label="Tokenization strategy"
>
<label class="radio-option"> <label class="radio-option">
<input <input
type="radio" type="radio"
@ -137,7 +141,7 @@
<span class="radio-custom" aria-hidden="true"></span> <span class="radio-custom" aria-hidden="true"></span>
<div class="radio-content"> <div class="radio-content">
<span class="radio-label">Markdown</span> <span class="radio-label">Markdown</span>
<span class="radio-description">Preserve formatting</span> <span class="radio-description">Keep Markdown markers with words</span>
</div> </div>
</label> </label>
</div> </div>
@ -175,31 +179,37 @@
</div> </div>
<div class="text-area-card diamond-result"> <div class="text-area-card diamond-result">
<label <div class="result-header">
id="merged-label" <label
title="The automatically merged result - edit the boxes above to see changes in real-time." id="merged-label"
> title="The automatically merged result - edit the boxes above to see changes in real-time."
Merged result
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
> >
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path> Merged result
<path <svg
d="M10 10l-6 6v4h4l6 -6m1.99 -1.99l2.504 -2.504a2.828 2.828 0 1 0 -4 -4l-2.5 2.5" xmlns="http://www.w3.org/2000/svg"
></path> width="24"
<path d="M13.5 6.5l4 4"></path> height="24"
<path d="M3 3l18 18"></path> viewBox="0 0 24 24"
</svg> fill="none"
</label> stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
aria-hidden="true"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M10 10l-6 6v4h4l6 -6m1.99 -1.99l2.504 -2.504a2.828 2.828 0 1 0 -4 -4l-2.5 2.5"
></path>
<path d="M13.5 6.5l4 4"></path>
<path d="M3 3l18 18"></path>
</svg>
</label>
<label class="history-toggle" for="show-deletions">
<input type="checkbox" id="show-deletions" aria-controls="merged" />
Show deleted text
</label>
</div>
<div <div
id="merged" id="merged"
role="textbox" role="textbox"

View file

@ -6,16 +6,27 @@ const originalTextArea = document.getElementById('original') as HTMLTextAreaElem
const leftTextArea = document.getElementById('left') as HTMLTextAreaElement; const leftTextArea = document.getElementById('left') as HTMLTextAreaElement;
const rightTextArea = document.getElementById('right') as HTMLTextAreaElement; const rightTextArea = document.getElementById('right') as HTMLTextAreaElement;
const mergedTextArea = document.getElementById('merged') as HTMLDivElement; const mergedTextArea = document.getElementById('merged') as HTMLDivElement;
const showDeletions = document.getElementById('show-deletions') as HTMLInputElement;
const tokenizerRadios = document.querySelectorAll( const tokenizerRadios = document.querySelectorAll(
'input[name="tokenizer"]' 'input[name="tokenizer"]'
) as NodeListOf<HTMLInputElement>; ) as NodeListOf<HTMLInputElement>;
const sampleText = `The reconcile-text library is embedded on this page as a WASM module and powers these text boxes. Experiment with changing the "Original", "First user's edit", and "Second user's edit" text boxes to see competing changes get merged in real-time within the "Merged result" box. const sampleText = `## Draft release
## Release plan
Here, you will see color-coded tokens marking the origin of each token, including ones that got deleted. The result depends on the tokenisation strategy selected above: Build steps:
- Character-based - Prototype
- Word-based (default) - Publish
- Markdown-based`;
Review checklist:
- [ ] Draft
- [ ] Review
Announcement:
> Internal
> Ready
Use color-coded changes to compare tokenisers.`;
let pendingUpdate: number | null = null; let pendingUpdate: number | null = null;
function scheduleUpdate(): void { function scheduleUpdate(): void {
@ -31,6 +42,9 @@ async function main(): Promise<void> {
originalTextArea.addEventListener('input', scheduleUpdate); originalTextArea.addEventListener('input', scheduleUpdate);
leftTextArea.addEventListener('input', scheduleUpdate); leftTextArea.addEventListener('input', scheduleUpdate);
rightTextArea.addEventListener('input', scheduleUpdate); rightTextArea.addEventListener('input', scheduleUpdate);
showDeletions.addEventListener('change', () => {
mergedTextArea.classList.toggle('show-deletions', showDeletions.checked);
});
document.addEventListener('selectionchange', () => { document.addEventListener('selectionchange', () => {
if ( if (
@ -51,15 +65,21 @@ async function main(): Promise<void> {
updateMergedText(); updateMergedText();
} }
// Edit the instructions to generate example edits // Deleting obsolete lines while reformatting their neighbours leaves orphaned
// markers with Word, but Markdown keeps each marker attached to its line's first word.
function loadSample(): void { function loadSample(): void {
originalTextArea.value = sampleText; originalTextArea.value = sampleText;
leftTextArea.value = leftTextArea.value = sampleText
sampleText.replace('color', 'colour') + .replace('## Draft release\n', '')
"\n\nCheck out what's the most complex conflict you can come up with!"; .replace('- Prototype\n', '')
.replace('- [ ] Draft\n', '')
.replace('> Internal\n', '')
.replace('color', 'colour');
rightTextArea.value = sampleText rightTextArea.value = sampleText
.replace('Experiment with changing', 'Try editing') .replace('## Release plan', '# Release plan')
.replace('WASM', 'WebAssembly'); .replace('- Publish', '1. Publish')
.replace('- [ ] Review', '- [x] Review')
.replace('> Ready', 'Ready');
} }
function updateMergedText(): void { function updateMergedText(): void {

View file

@ -68,7 +68,7 @@ header {
padding: 32px 32px 0 32px; padding: 32px 32px 0 32px;
} }
header > h1 { header>h1 {
font-size: 2.5rem; font-size: 2.5rem;
font-weight: 700; font-weight: 700;
color: $primary-blue; color: $primary-blue;
@ -93,13 +93,13 @@ code {
font-weight: 500; font-weight: 500;
} }
header > p { header>p {
color: $text-secondary; color: $text-secondary;
font-size: 1.1rem; font-size: 1.1rem;
margin-bottom: 0; margin-bottom: 0;
} }
header > p:not(:first-of-type) { header>p:not(:first-of-type) {
margin-top: 16px; margin-top: 16px;
} }
@ -234,6 +234,20 @@ main {
align-items: center; align-items: center;
} }
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 8px 16px;
}
.history-toggle {
gap: 6px;
font-size: 0.85rem;
cursor: pointer;
}
.diamond-result svg { .diamond-result svg {
width: 20px; width: 20px;
height: 20px; height: 20px;
@ -277,17 +291,26 @@ textarea {
resize: none; resize: none;
outline: none; outline: none;
margin-bottom: 0; margin-bottom: 0;
field-sizing: content; /* Doesn't work in Safari yet */ field-sizing: content;
/* Doesn't work in Safari yet */
} }
#merged { #merged {
width: 100%; width: 100%;
user-select: text; user-select: text;
> * { >* {
position: relative; position: relative;
white-space: pre-wrap; white-space: pre-wrap;
} }
&:not(.show-deletions) {
.RemovedFromLeft,
.RemovedFromRight {
display: none;
}
}
} }
.Unchanged { .Unchanged {
@ -348,12 +371,12 @@ $DOT_RADIUS: 4;
background: caret-colour($light-blue); background: caret-colour($light-blue);
} }
> * { >* {
position: absolute; position: absolute;
background-color: inherit; background-color: inherit;
} }
> .stick { >.stick {
left: 0; left: 0;
top: 0; top: 0;
transform: translateX(-50%); transform: translateX(-50%);
@ -364,7 +387,7 @@ $DOT_RADIUS: 4;
animation: blink-stick 1s steps(1) infinite; animation: blink-stick 1s steps(1) infinite;
} }
> .dot { >.dot {
border-radius: 50%; border-radius: 50%;
width: #{$DOT_RADIUS * 2}px; width: #{$DOT_RADIUS * 2}px;
height: #{$DOT_RADIUS * 2}px; height: #{$DOT_RADIUS * 2}px;
@ -386,11 +409,11 @@ $DOT_RADIUS: 4;
} }
} }
&:hover > .dot { &:hover>.dot {
opacity: 0; opacity: 0;
} }
> .info { >.info {
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;
@ -403,16 +426,18 @@ $DOT_RADIUS: 4;
border-radius: 3px 3px 3px 0; border-radius: 3px 3px 3px 0;
} }
&:hover > .info { &:hover>.info {
opacity: 1; opacity: 1;
} }
} }
@keyframes blink-stick { @keyframes blink-stick {
0%, 0%,
100% { 100% {
opacity: 1; opacity: 1;
} }
50% { 50% {
opacity: 0; opacity: 0;
} }
@ -423,11 +448,11 @@ $DOT_RADIUS: 4;
padding: 32px 18px 0 18px; padding: 32px 18px 0 18px;
} }
header > h1 { header>h1 {
margin-bottom: 18px; margin-bottom: 18px;
} }
header > p { header>p {
font-size: 1rem; font-size: 1rem;
} }
@ -494,14 +519,14 @@ footer {
gap: 16px; gap: 16px;
} }
.footer-links > a > svg { .footer-links>a>svg {
color: $text-secondary; color: $text-secondary;
width: 28px; width: 28px;
height: 28px; height: 28px;
transition: transform 0.2s; transition: transform 0.2s;
} }
.footer-links > a > svg:hover { .footer-links>a>svg:hover {
cursor: pointer; cursor: pointer;
transform: scale(1.15); transform: scale(1.15);
} }