Zwei Fassungen als PDF, Word oder Text: Jede Änderung wird Wort für Wort markiert. Ihre Dateien verlassen dabei nie Ihr Gerät.
-
-
Kostenlos & Open Source (MIT)
-
Keine Uploads – alles läuft lokal im Browser
-
Keine Werbung, kein Tracking, keine Cookies
-
+
Jede Änderung Wort für Wort markiert. Ihre Dateien verlassen nie Ihr Gerät.
@@ -3744,10 +3782,9 @@
1
Alte Fassung
- PDF (mit Text), Word (.docx), Text
+
Datei wählen
- oder hierher ziehen
@@ -3758,31 +3795,25 @@
2
Neue Fassung
- PDF (mit Text), Word (.docx), Text
+
Datei wählen
- oder hierher ziehen
-
Sie können auch beide Dateien auf einmal hierher ziehen – die erste gilt als alte Fassung.
+
PDF, Word (.docx) oder Text · Dateien auch hierher ziehen
-
-
+
+
+
Kostenlos und Open Source (MIT) · Keine Werbung, kein Tracking, keine Cookies
@@ -4415,18 +4446,15 @@
Ihre Daten
pasteText: 'Text einfügen',
loadSample: 'Beispiel ansehen',
heroTitle: 'Dokumente einfach vergleichen.',
- heroSub: 'Zwei Fassungen als PDF, Word oder Text: Jede Änderung wird Wort für Wort markiert. Ihre Dateien verlassen dabei nie Ihr Gerät.',
- trustFree: 'Kostenlos & Open Source (MIT)',
- trustLocal: 'Keine Uploads – alles läuft lokal im Browser',
- trustNoTracking: 'Keine Werbung, kein Tracking, keine Cookies',
+ heroSub: 'Jede Änderung Wort für Wort markiert. Ihre Dateien verlassen nie Ihr Gerät.',
+ trustLine: 'Kostenlos und Open Source (MIT) · Keine Werbung, kein Tracking, keine Cookies',
slotLeftTitle: 'Alte Fassung',
slotRightTitle: 'Neue Fassung',
- slotHint: 'PDF (mit Text), Word (.docx), Text',
+ slotHint: 'PDF, Word (.docx) oder Text',
slotChoose: 'Datei wählen',
- slotDrag: 'oder hierher ziehen',
+ slotDrag: ' · Dateien auch hierher ziehen',
slotClearLeft: 'Alte Fassung entfernen',
slotClearRight: 'Neue Fassung entfernen',
- dropBothHint: 'Sie können auch beide Dateien auf einmal hierher ziehen – die erste gilt als alte Fassung.',
howTitle: 'So funktioniert’s',
how1Title: 'Zwei Fassungen wählen',
how1Text: 'Als PDF, Word-Datei oder eingefügten Text. Die Dateien werden nur in diesem Browser-Tab gelesen.',
@@ -4815,18 +4843,15 @@
Your data
pasteText: 'Paste text',
loadSample: 'View example',
heroTitle: 'Compare documents easily.',
- heroSub: 'Two versions as PDF, Word or text: every change is marked word by word. Your files never leave your device.',
- trustFree: 'Free & open source (MIT)',
- trustLocal: 'No uploads – everything runs locally in your browser',
- trustNoTracking: 'No ads, no tracking, no cookies',
+ heroSub: 'Every change marked word by word. Your files never leave your device.',
+ trustLine: 'Free and open source (MIT) · No ads, no tracking, no cookies',
slotLeftTitle: 'Old version',
slotRightTitle: 'New version',
- slotHint: 'PDF (with text), Word (.docx), text',
+ slotHint: 'PDF, Word (.docx) or text',
slotChoose: 'Choose file',
- slotDrag: 'or drop it here',
+ slotDrag: ' · or drop the files here',
slotClearLeft: 'Remove old version',
slotClearRight: 'Remove new version',
- dropBothHint: 'You can also drop both files here at once – the first one becomes the old version.',
howTitle: 'How it works',
how1Title: 'Choose two versions',
how1Text: 'As PDF, Word file or pasted text. The files are only read inside this browser tab.',
@@ -7423,8 +7448,8 @@
Your data
} else {
titleEl.setAttribute('data-i18n', titleKey);
titleEl.textContent = t(titleKey);
- hintEl.setAttribute('data-i18n', 'slotHint');
- hintEl.textContent = t('slotHint');
+ // The formats are named once, under both slots.
+ hintEl.textContent = '';
ctaEl.setAttribute('data-i18n', 'slotChoose');
ctaEl.textContent = t('slotChoose');
}
@@ -9473,6 +9498,8 @@
Your data
* One place only: a compact column of three icon buttons (accept, reject, edit) in the right
* margin of the current paragraph of the new version (or of the stacked view). Every
* paragraph there keeps that margin free, so selecting a paragraph never moves any text.
+ * On phones the margin would cost too much of the narrow text column: the buttons sit in a
+ * row just above the bottom bar instead, within reach of the thumb.
*/
// Created on first use (the module is also loaded without a DOM by the export tests).
@@ -9487,6 +9514,13 @@
Your data
reviewBar.addEventListener('dblclick', e => e.stopPropagation());
// Roving focus inside the group: one tab stop, arrow keys move between the buttons.
reviewBar.addEventListener('keydown', e => handleRovingKeys(e, reviewBar));
+ // In the panes, clicks are handled by the pane; above the bottom bar, here.
+ reviewBar.addEventListener('click', e => {
+ const btn = e.target.closest('[data-review-action]');
+ if (!btn || !reviewBar.closest('.bottom-bar')) return;
+ e.stopPropagation();
+ runReviewAction(btn.getAttribute('data-review-action'), false);
+ });
return reviewBar;
}
@@ -9498,11 +9532,15 @@
Your data
const ICON_X = '';
const ICON_EDIT = '';
-// Icon buttons: accept / reject for an open change, edit for any paragraph of the new version.
-function reviewActionsHtml(item) {
+// Icon buttons: accept / reject for an open change, edit for any paragraph of the new version;
+// with `withUndo` (phones, where the margin has no undo button) also undo for a reviewed one.
+function reviewActionsHtml(item, withUndo = false) {
const btn = (action, icon, label, title, key, cls) =>
- ``;
+ ``;
const edit = btn('edit', ICON_EDIT, t('editBtn'), t('editTitle'), 'E', 'ra-edit');
+ if (withUndo && item && item.status !== 'open') {
+ return rovingFirst(btn('reopen', ICON_UNDO, t('reopenBtn'), t('reopenTitle'), '', 'ra-reopen') + edit);
+ }
if (item && item.status === 'open') {
return rovingFirst(
btn('accept', ICON_CHECK, t('acceptBtn'), t('acceptTitle'), 'A', 'ra-accept') +
@@ -9550,15 +9588,18 @@
// Phones show one column only.
if (tier === 'phone' && state.viewMode !== 'inline') setViewMode('inline');
else if (!initial && prev === 'phone' && !state.viewModeChosen) setViewMode(initialInline && tier !== 'desktop' ? 'inline' : 'split');
- applySidebarState();
+ applySidebarState(); // The review buttons sit in the paragraph margin, or above the bottom bar on phones.
+ if (!initial && typeof placeReviewBar === 'function') placeReviewBar();
}
function applySidebarState() {
@@ -14492,7 +14534,7 @@
Two versions as PDF, Word or text: every change is marked word by word. Your files never leave your device.
-
-
Free & open source (MIT)
-
No uploads – everything runs locally in your browser
-
No ads, no tracking, no cookies
-
+
Every change marked word by word. Your files never leave your device.
@@ -3747,10 +3785,9 @@
1
Old version
- PDF (with text), Word (.docx), text
+
Choose file
- or drop it here
@@ -3761,31 +3798,25 @@
2
New version
- PDF (with text), Word (.docx), text
+
Choose file
- or drop it here
-
You can also drop both files here at once – the first one becomes the old version.
+
PDF, Word (.docx) or text · or drop the files here
-
-
+
+
+
Free and open source (MIT) · No ads, no tracking, no cookies
@@ -4418,18 +4449,15 @@
Ihre Daten
pasteText: 'Text einfügen',
loadSample: 'Beispiel ansehen',
heroTitle: 'Dokumente einfach vergleichen.',
- heroSub: 'Zwei Fassungen als PDF, Word oder Text: Jede Änderung wird Wort für Wort markiert. Ihre Dateien verlassen dabei nie Ihr Gerät.',
- trustFree: 'Kostenlos & Open Source (MIT)',
- trustLocal: 'Keine Uploads – alles läuft lokal im Browser',
- trustNoTracking: 'Keine Werbung, kein Tracking, keine Cookies',
+ heroSub: 'Jede Änderung Wort für Wort markiert. Ihre Dateien verlassen nie Ihr Gerät.',
+ trustLine: 'Kostenlos und Open Source (MIT) · Keine Werbung, kein Tracking, keine Cookies',
slotLeftTitle: 'Alte Fassung',
slotRightTitle: 'Neue Fassung',
- slotHint: 'PDF (mit Text), Word (.docx), Text',
+ slotHint: 'PDF, Word (.docx) oder Text',
slotChoose: 'Datei wählen',
- slotDrag: 'oder hierher ziehen',
+ slotDrag: ' · Dateien auch hierher ziehen',
slotClearLeft: 'Alte Fassung entfernen',
slotClearRight: 'Neue Fassung entfernen',
- dropBothHint: 'Sie können auch beide Dateien auf einmal hierher ziehen – die erste gilt als alte Fassung.',
howTitle: 'So funktioniert’s',
how1Title: 'Zwei Fassungen wählen',
how1Text: 'Als PDF, Word-Datei oder eingefügten Text. Die Dateien werden nur in diesem Browser-Tab gelesen.',
@@ -4818,18 +4846,15 @@
Your data
pasteText: 'Paste text',
loadSample: 'View example',
heroTitle: 'Compare documents easily.',
- heroSub: 'Two versions as PDF, Word or text: every change is marked word by word. Your files never leave your device.',
- trustFree: 'Free & open source (MIT)',
- trustLocal: 'No uploads – everything runs locally in your browser',
- trustNoTracking: 'No ads, no tracking, no cookies',
+ heroSub: 'Every change marked word by word. Your files never leave your device.',
+ trustLine: 'Free and open source (MIT) · No ads, no tracking, no cookies',
slotLeftTitle: 'Old version',
slotRightTitle: 'New version',
- slotHint: 'PDF (with text), Word (.docx), text',
+ slotHint: 'PDF, Word (.docx) or text',
slotChoose: 'Choose file',
- slotDrag: 'or drop it here',
+ slotDrag: ' · or drop the files here',
slotClearLeft: 'Remove old version',
slotClearRight: 'Remove new version',
- dropBothHint: 'You can also drop both files here at once – the first one becomes the old version.',
howTitle: 'How it works',
how1Title: 'Choose two versions',
how1Text: 'As PDF, Word file or pasted text. The files are only read inside this browser tab.',
@@ -7426,8 +7451,8 @@
Your data
} else {
titleEl.setAttribute('data-i18n', titleKey);
titleEl.textContent = t(titleKey);
- hintEl.setAttribute('data-i18n', 'slotHint');
- hintEl.textContent = t('slotHint');
+ // The formats are named once, under both slots.
+ hintEl.textContent = '';
ctaEl.setAttribute('data-i18n', 'slotChoose');
ctaEl.textContent = t('slotChoose');
}
@@ -9476,6 +9501,8 @@
Your data
* One place only: a compact column of three icon buttons (accept, reject, edit) in the right
* margin of the current paragraph of the new version (or of the stacked view). Every
* paragraph there keeps that margin free, so selecting a paragraph never moves any text.
+ * On phones the margin would cost too much of the narrow text column: the buttons sit in a
+ * row just above the bottom bar instead, within reach of the thumb.
*/
// Created on first use (the module is also loaded without a DOM by the export tests).
@@ -9490,6 +9517,13 @@
Your data
reviewBar.addEventListener('dblclick', e => e.stopPropagation());
// Roving focus inside the group: one tab stop, arrow keys move between the buttons.
reviewBar.addEventListener('keydown', e => handleRovingKeys(e, reviewBar));
+ // In the panes, clicks are handled by the pane; above the bottom bar, here.
+ reviewBar.addEventListener('click', e => {
+ const btn = e.target.closest('[data-review-action]');
+ if (!btn || !reviewBar.closest('.bottom-bar')) return;
+ e.stopPropagation();
+ runReviewAction(btn.getAttribute('data-review-action'), false);
+ });
return reviewBar;
}
@@ -9501,11 +9535,15 @@
Your data
const ICON_X = '';
const ICON_EDIT = '';
-// Icon buttons: accept / reject for an open change, edit for any paragraph of the new version.
-function reviewActionsHtml(item) {
+// Icon buttons: accept / reject for an open change, edit for any paragraph of the new version;
+// with `withUndo` (phones, where the margin has no undo button) also undo for a reviewed one.
+function reviewActionsHtml(item, withUndo = false) {
const btn = (action, icon, label, title, key, cls) =>
- ``;
+ ``;
const edit = btn('edit', ICON_EDIT, t('editBtn'), t('editTitle'), 'E', 'ra-edit');
+ if (withUndo && item && item.status !== 'open') {
+ return rovingFirst(btn('reopen', ICON_UNDO, t('reopenBtn'), t('reopenTitle'), '', 'ra-reopen') + edit);
+ }
if (item && item.status === 'open') {
return rovingFirst(
btn('accept', ICON_CHECK, t('acceptBtn'), t('acceptTitle'), 'A', 'ra-accept') +
@@ -9553,15 +9591,18 @@
// Phones show one column only.
if (tier === 'phone' && state.viewMode !== 'inline') setViewMode('inline');
else if (!initial && prev === 'phone' && !state.viewModeChosen) setViewMode(initialInline && tier !== 'desktop' ? 'inline' : 'split');
- applySidebarState();
+ applySidebarState(); // The review buttons sit in the paragraph margin, or above the bottom bar on phones.
+ if (!initial && typeof placeReviewBar === 'function') placeReviewBar();
}
function applySidebarState() {
@@ -14495,7 +14537,7 @@
Zwei Fassungen als PDF, Word oder Text: Jede Änderung wird Wort für Wort markiert. Ihre Dateien verlassen dabei nie Ihr Gerät.
-
-
Kostenlos & Open Source (MIT)
-
Keine Uploads – alles läuft lokal im Browser
-
Keine Werbung, kein Tracking, keine Cookies
-
+
Jede Änderung Wort für Wort markiert. Ihre Dateien verlassen nie Ihr Gerät.
@@ -3747,10 +3785,9 @@
1
Alte Fassung
- PDF (mit Text), Word (.docx), Text
+
Datei wählen
- oder hierher ziehen
@@ -3761,31 +3798,25 @@
2
Neue Fassung
- PDF (mit Text), Word (.docx), Text
+
Datei wählen
- oder hierher ziehen
-
Sie können auch beide Dateien auf einmal hierher ziehen – die erste gilt als alte Fassung.
+
PDF, Word (.docx) oder Text · Dateien auch hierher ziehen
-
-
+
+
+
Kostenlos und Open Source (MIT) · Keine Werbung, kein Tracking, keine Cookies
@@ -4418,18 +4449,15 @@
Ihre Daten
pasteText: 'Text einfügen',
loadSample: 'Beispiel ansehen',
heroTitle: 'Dokumente einfach vergleichen.',
- heroSub: 'Zwei Fassungen als PDF, Word oder Text: Jede Änderung wird Wort für Wort markiert. Ihre Dateien verlassen dabei nie Ihr Gerät.',
- trustFree: 'Kostenlos & Open Source (MIT)',
- trustLocal: 'Keine Uploads – alles läuft lokal im Browser',
- trustNoTracking: 'Keine Werbung, kein Tracking, keine Cookies',
+ heroSub: 'Jede Änderung Wort für Wort markiert. Ihre Dateien verlassen nie Ihr Gerät.',
+ trustLine: 'Kostenlos und Open Source (MIT) · Keine Werbung, kein Tracking, keine Cookies',
slotLeftTitle: 'Alte Fassung',
slotRightTitle: 'Neue Fassung',
- slotHint: 'PDF (mit Text), Word (.docx), Text',
+ slotHint: 'PDF, Word (.docx) oder Text',
slotChoose: 'Datei wählen',
- slotDrag: 'oder hierher ziehen',
+ slotDrag: ' · Dateien auch hierher ziehen',
slotClearLeft: 'Alte Fassung entfernen',
slotClearRight: 'Neue Fassung entfernen',
- dropBothHint: 'Sie können auch beide Dateien auf einmal hierher ziehen – die erste gilt als alte Fassung.',
howTitle: 'So funktioniert’s',
how1Title: 'Zwei Fassungen wählen',
how1Text: 'Als PDF, Word-Datei oder eingefügten Text. Die Dateien werden nur in diesem Browser-Tab gelesen.',
@@ -4818,18 +4846,15 @@
Your data
pasteText: 'Paste text',
loadSample: 'View example',
heroTitle: 'Compare documents easily.',
- heroSub: 'Two versions as PDF, Word or text: every change is marked word by word. Your files never leave your device.',
- trustFree: 'Free & open source (MIT)',
- trustLocal: 'No uploads – everything runs locally in your browser',
- trustNoTracking: 'No ads, no tracking, no cookies',
+ heroSub: 'Every change marked word by word. Your files never leave your device.',
+ trustLine: 'Free and open source (MIT) · No ads, no tracking, no cookies',
slotLeftTitle: 'Old version',
slotRightTitle: 'New version',
- slotHint: 'PDF (with text), Word (.docx), text',
+ slotHint: 'PDF, Word (.docx) or text',
slotChoose: 'Choose file',
- slotDrag: 'or drop it here',
+ slotDrag: ' · or drop the files here',
slotClearLeft: 'Remove old version',
slotClearRight: 'Remove new version',
- dropBothHint: 'You can also drop both files here at once – the first one becomes the old version.',
howTitle: 'How it works',
how1Title: 'Choose two versions',
how1Text: 'As PDF, Word file or pasted text. The files are only read inside this browser tab.',
@@ -7426,8 +7451,8 @@
Your data
} else {
titleEl.setAttribute('data-i18n', titleKey);
titleEl.textContent = t(titleKey);
- hintEl.setAttribute('data-i18n', 'slotHint');
- hintEl.textContent = t('slotHint');
+ // The formats are named once, under both slots.
+ hintEl.textContent = '';
ctaEl.setAttribute('data-i18n', 'slotChoose');
ctaEl.textContent = t('slotChoose');
}
@@ -9476,6 +9501,8 @@
Your data
* One place only: a compact column of three icon buttons (accept, reject, edit) in the right
* margin of the current paragraph of the new version (or of the stacked view). Every
* paragraph there keeps that margin free, so selecting a paragraph never moves any text.
+ * On phones the margin would cost too much of the narrow text column: the buttons sit in a
+ * row just above the bottom bar instead, within reach of the thumb.
*/
// Created on first use (the module is also loaded without a DOM by the export tests).
@@ -9490,6 +9517,13 @@
Your data
reviewBar.addEventListener('dblclick', e => e.stopPropagation());
// Roving focus inside the group: one tab stop, arrow keys move between the buttons.
reviewBar.addEventListener('keydown', e => handleRovingKeys(e, reviewBar));
+ // In the panes, clicks are handled by the pane; above the bottom bar, here.
+ reviewBar.addEventListener('click', e => {
+ const btn = e.target.closest('[data-review-action]');
+ if (!btn || !reviewBar.closest('.bottom-bar')) return;
+ e.stopPropagation();
+ runReviewAction(btn.getAttribute('data-review-action'), false);
+ });
return reviewBar;
}
@@ -9501,11 +9535,15 @@
Your data
const ICON_X = '';
const ICON_EDIT = '';
-// Icon buttons: accept / reject for an open change, edit for any paragraph of the new version.
-function reviewActionsHtml(item) {
+// Icon buttons: accept / reject for an open change, edit for any paragraph of the new version;
+// with `withUndo` (phones, where the margin has no undo button) also undo for a reviewed one.
+function reviewActionsHtml(item, withUndo = false) {
const btn = (action, icon, label, title, key, cls) =>
- ``;
+ ``;
const edit = btn('edit', ICON_EDIT, t('editBtn'), t('editTitle'), 'E', 'ra-edit');
+ if (withUndo && item && item.status !== 'open') {
+ return rovingFirst(btn('reopen', ICON_UNDO, t('reopenBtn'), t('reopenTitle'), '', 'ra-reopen') + edit);
+ }
if (item && item.status === 'open') {
return rovingFirst(
btn('accept', ICON_CHECK, t('acceptBtn'), t('acceptTitle'), 'A', 'ra-accept') +
@@ -9553,15 +9591,18 @@
// Phones show one column only.
if (tier === 'phone' && state.viewMode !== 'inline') setViewMode('inline');
else if (!initial && prev === 'phone' && !state.viewModeChosen) setViewMode(initialInline && tier !== 'desktop' ? 'inline' : 'split');
- applySidebarState();
+ applySidebarState(); // The review buttons sit in the paragraph margin, or above the bottom bar on phones.
+ if (!initial && typeof placeReviewBar === 'function') placeReviewBar();
}
function applySidebarState() {
@@ -14495,7 +14537,7 @@
${escapeHtml(t('exportRedlineHeading'))}
* entry; version 1 files (no review status) still open.
*/
-const DOCDIFF_APP_VERSION = '1.6.0';
+const DOCDIFF_APP_VERSION = '1.6.1';
const SESSION_FORMAT_ID = 'docdiff-session';
const SESSION_FORMAT_VERSION = 2;
const SESSION_FILE_EXT = '.docdiff';
diff --git a/src/js/01-i18n.js b/src/js/01-i18n.js
index 053b4c2..9e92e5a 100644
--- a/src/js/01-i18n.js
+++ b/src/js/01-i18n.js
@@ -342,18 +342,15 @@ const I18N = {
pasteText: 'Text einfügen',
loadSample: 'Beispiel ansehen',
heroTitle: 'Dokumente einfach vergleichen.',
- heroSub: 'Zwei Fassungen als PDF, Word oder Text: Jede Änderung wird Wort für Wort markiert. Ihre Dateien verlassen dabei nie Ihr Gerät.',
- trustFree: 'Kostenlos & Open Source (MIT)',
- trustLocal: 'Keine Uploads – alles läuft lokal im Browser',
- trustNoTracking: 'Keine Werbung, kein Tracking, keine Cookies',
+ heroSub: 'Jede Änderung Wort für Wort markiert. Ihre Dateien verlassen nie Ihr Gerät.',
+ trustLine: 'Kostenlos und Open Source (MIT) · Keine Werbung, kein Tracking, keine Cookies',
slotLeftTitle: 'Alte Fassung',
slotRightTitle: 'Neue Fassung',
- slotHint: 'PDF (mit Text), Word (.docx), Text',
+ slotHint: 'PDF, Word (.docx) oder Text',
slotChoose: 'Datei wählen',
- slotDrag: 'oder hierher ziehen',
+ slotDrag: ' · Dateien auch hierher ziehen',
slotClearLeft: 'Alte Fassung entfernen',
slotClearRight: 'Neue Fassung entfernen',
- dropBothHint: 'Sie können auch beide Dateien auf einmal hierher ziehen – die erste gilt als alte Fassung.',
howTitle: 'So funktioniert’s',
how1Title: 'Zwei Fassungen wählen',
how1Text: 'Als PDF, Word-Datei oder eingefügten Text. Die Dateien werden nur in diesem Browser-Tab gelesen.',
@@ -742,18 +739,15 @@ const I18N = {
pasteText: 'Paste text',
loadSample: 'View example',
heroTitle: 'Compare documents easily.',
- heroSub: 'Two versions as PDF, Word or text: every change is marked word by word. Your files never leave your device.',
- trustFree: 'Free & open source (MIT)',
- trustLocal: 'No uploads – everything runs locally in your browser',
- trustNoTracking: 'No ads, no tracking, no cookies',
+ heroSub: 'Every change marked word by word. Your files never leave your device.',
+ trustLine: 'Free and open source (MIT) · No ads, no tracking, no cookies',
slotLeftTitle: 'Old version',
slotRightTitle: 'New version',
- slotHint: 'PDF (with text), Word (.docx), text',
+ slotHint: 'PDF, Word (.docx) or text',
slotChoose: 'Choose file',
- slotDrag: 'or drop it here',
+ slotDrag: ' · or drop the files here',
slotClearLeft: 'Remove old version',
slotClearRight: 'Remove new version',
- dropBothHint: 'You can also drop both files here at once – the first one becomes the old version.',
howTitle: 'How it works',
how1Title: 'Choose two versions',
how1Text: 'As PDF, Word file or pasted text. The files are only read inside this browser tab.',
diff --git a/src/js/05-render.js b/src/js/05-render.js
index bcd15e1..0692175 100644
--- a/src/js/05-render.js
+++ b/src/js/05-render.js
@@ -312,8 +312,8 @@ function updateLandingSlots(left, right) {
} else {
titleEl.setAttribute('data-i18n', titleKey);
titleEl.textContent = t(titleKey);
- hintEl.setAttribute('data-i18n', 'slotHint');
- hintEl.textContent = t('slotHint');
+ // The formats are named once, under both slots.
+ hintEl.textContent = '';
ctaEl.setAttribute('data-i18n', 'slotChoose');
ctaEl.textContent = t('slotChoose');
}
diff --git a/src/js/06d-review.js b/src/js/06d-review.js
index 9a8c8e4..7d9290e 100644
--- a/src/js/06d-review.js
+++ b/src/js/06d-review.js
@@ -436,6 +436,8 @@ function syncStatusTabStops() {
* One place only: a compact column of three icon buttons (accept, reject, edit) in the right
* margin of the current paragraph of the new version (or of the stacked view). Every
* paragraph there keeps that margin free, so selecting a paragraph never moves any text.
+ * On phones the margin would cost too much of the narrow text column: the buttons sit in a
+ * row just above the bottom bar instead, within reach of the thumb.
*/
// Created on first use (the module is also loaded without a DOM by the export tests).
@@ -450,6 +452,13 @@ function getReviewBar() {
reviewBar.addEventListener('dblclick', e => e.stopPropagation());
// Roving focus inside the group: one tab stop, arrow keys move between the buttons.
reviewBar.addEventListener('keydown', e => handleRovingKeys(e, reviewBar));
+ // In the panes, clicks are handled by the pane; above the bottom bar, here.
+ reviewBar.addEventListener('click', e => {
+ const btn = e.target.closest('[data-review-action]');
+ if (!btn || !reviewBar.closest('.bottom-bar')) return;
+ e.stopPropagation();
+ runReviewAction(btn.getAttribute('data-review-action'), false);
+ });
return reviewBar;
}
@@ -461,11 +470,15 @@ const ICON_CHECK = '