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>
<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">
<input
type="radio"
@ -137,7 +141,7 @@
<span class="radio-custom" aria-hidden="true"></span>
<div class="radio-content">
<span class="radio-label">Markdown</span>
<span class="radio-description">Preserve formatting</span>
<span class="radio-description">Keep Markdown markers with words</span>
</div>
</label>
</div>
@ -175,31 +179,37 @@
</div>
<div class="text-area-card diamond-result">
<label
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"
<div class="result-header">
<label
id="merged-label"
title="The automatically merged result - edit the boxes above to see changes in real-time."
>
<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>
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>
<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
id="merged"
role="textbox"

View file

@ -6,16 +6,27 @@ const originalTextArea = document.getElementById('original') as HTMLTextAreaElem
const leftTextArea = document.getElementById('left') as HTMLTextAreaElement;
const rightTextArea = document.getElementById('right') as HTMLTextAreaElement;
const mergedTextArea = document.getElementById('merged') as HTMLDivElement;
const showDeletions = document.getElementById('show-deletions') as HTMLInputElement;
const tokenizerRadios = document.querySelectorAll(
'input[name="tokenizer"]'
) 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:
- Character-based
- Word-based (default)
- Markdown-based`;
Build steps:
- Prototype
- Publish
Review checklist:
- [ ] Draft
- [ ] Review
Announcement:
> Internal
> Ready
Use color-coded changes to compare tokenisers.`;
let pendingUpdate: number | null = null;
function scheduleUpdate(): void {
@ -31,6 +42,9 @@ async function main(): Promise<void> {
originalTextArea.addEventListener('input', scheduleUpdate);
leftTextArea.addEventListener('input', scheduleUpdate);
rightTextArea.addEventListener('input', scheduleUpdate);
showDeletions.addEventListener('change', () => {
mergedTextArea.classList.toggle('show-deletions', showDeletions.checked);
});
document.addEventListener('selectionchange', () => {
if (
@ -51,15 +65,21 @@ async function main(): Promise<void> {
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 {
originalTextArea.value = sampleText;
leftTextArea.value =
sampleText.replace('color', 'colour') +
"\n\nCheck out what's the most complex conflict you can come up with!";
leftTextArea.value = sampleText
.replace('## Draft release\n', '')
.replace('- Prototype\n', '')
.replace('- [ ] Draft\n', '')
.replace('> Internal\n', '')
.replace('color', 'colour');
rightTextArea.value = sampleText
.replace('Experiment with changing', 'Try editing')
.replace('WASM', 'WebAssembly');
.replace('## Release plan', '# Release plan')
.replace('- Publish', '1. Publish')
.replace('- [ ] Review', '- [x] Review')
.replace('> Ready', 'Ready');
}
function updateMergedText(): void {

View file

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