Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions bundlesize.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,23 @@
},
{
"path": "static/build/css/page-book.css",
"maxSize": "16KB"
"maxSize": "17KB"
},
{
"path": "static/build/css/page-edit.css",
"maxSize": "26KB"
"maxSize": "27KB"
},
{
"path": "static/build/css/page-form.css",
"maxSize": "26KB"
"maxSize": "27KB"
},
{
"path": "static/build/css/page-home.css",
"maxSize": "8KB"
},
{
"path": "static/build/css/page-plain.css",
"maxSize": "26KB"
"maxSize": "27KB"
},
{
"path": "static/build/css/page-subject.css",
Expand Down
38 changes: 33 additions & 5 deletions openlibrary/i18n/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -711,12 +711,25 @@ msgstr ""
msgid "Preview"
msgstr ""

#: history.html
#, python-format
msgid "Compare revisions %(a)s and %(b)s"
msgstr ""

#: history.html
msgid "History of edits to"
msgstr ""

#: history.html
msgid "Revision"
msgid "Revision comparison"
msgstr ""

#: history.html
msgid "Clear comparison"
msgstr ""

#: history.html
msgid "Rev"
msgstr ""

#: RecentChanges.html RecentChangesAdmin.html RecentChangesUsers.html
Expand All @@ -741,12 +754,18 @@ msgstr ""
msgid "Diff"
msgstr ""

#. Column header labeling the "A" side (the earlier/left revision) of a
#. revision comparison on a page's edit-history table. Keep extremely short; a
#. single letter is ideal. Pairs with "B".
#: history.html
msgid "Compare"
msgid "A"
msgstr ""

#. Column header labeling the "B" side (the later/right revision) of a revision
#. comparison on a page's edit-history table. Keep extremely short; a single
#. letter is ideal. Pairs with "A".
#: history.html
msgid "See Diff"
msgid "B"
msgstr ""

#: history.html lib/history.html
Expand All @@ -755,11 +774,20 @@ msgid "View revision %s"
msgstr ""

#: history.html
msgid "Compare this version..."
msgid "Changes"
msgstr ""

#: history.html
msgid "Compare this revision with the current version"
msgstr ""

#: history.html
msgid "...to this version"
msgid "vs current"
msgstr ""

#: history.html
#, python-format
msgid "Pick revision %s as A or B for comparison"
msgstr ""

#: import_preview.html
Expand Down
144 changes: 144 additions & 0 deletions openlibrary/plugins/openlibrary/js/history.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* History page revision comparison.
*
* The A/B selection lives in the URL query string (?a=&b=), not the DOM, so it
* survives pagination: <ol-pagination> rebuilds its links from the current URL,
* carrying a/b across pages. A sticky bar reflects the selection even when the
* picked revision sits on another page, and provides the Compare action.
*
* @param {HTMLElement} pageHistoryElement - the #pageHistory table
*/
export function initHistory(pageHistoryElement) {
const i18n = JSON.parse(pageHistoryElement.dataset.i18n);
const bar = document.getElementById('pageHistoryCompare');
if (!bar) return;

const compareLink = bar.querySelector('[data-compare-link]');
const clearButton = bar.querySelector('[data-compare-clear]');

// Slots ('a'/'b') in the order the user picked them, so the button reads
// "Compare revisions <first> and <second>" matching selection order.
let pickOrder = [];

/**
* Read the current selection from the URL.
* @returns {{a: ?number, b: ?number}}
*/
function readSelection() {
const params = new URLSearchParams(window.location.search);
const parse = (key) => {
const value = parseInt(params.get(key), 10);
return Number.isInteger(value) && value > 0 ? value : null;
};
return { a: parse('a'), b: parse('b') };
}

/**
* Persist the selection to the URL without navigating, then nudge the
* pagination controls to rebuild their links so a page click carries a/b.
* @param {{a: ?number, b: ?number}} selection
*/
function writeSelection({ a, b }) {
const url = new URL(window.location.href);
const setOrDelete = (key, value) => {
if (value) {
url.searchParams.set(key, value);
} else {
url.searchParams.delete(key);
}
};
setOrDelete('a', a);
setOrDelete('b', b);
window.history.replaceState(null, '', url);
// <ol-pagination> derives its hrefs from window.location.href at render
// time, so force a re-render now that the URL has changed.
document.querySelectorAll('ol-pagination')
.forEach((el) => el.requestUpdate && el.requestUpdate());
}

/**
* Reflect the selection in this page's radios and row highlights. Revisions
* that live on another page simply have no radio here and are skipped.
* @param {{a: ?number, b: ?number}} selection
*/
function syncRows({ a, b }) {
pageHistoryElement.querySelectorAll('input[name="a"], input[name="b"]')
.forEach((radio) => {
const value = parseInt(radio.value, 10);
radio.checked = (radio.name === 'a' && value === a) ||
(radio.name === 'b' && value === b);
});
pageHistoryElement.querySelectorAll('tr.pick-ab-selected')
.forEach((row) => row.classList.remove('pick-ab-selected'));
[a, b].forEach((value) => {
if (!value) return;
const radio = pageHistoryElement.querySelector(`input[value="${value}"]`);
if (radio) radio.closest('tr').classList.add('pick-ab-selected');
});
}

/**
* Render the sticky comparison bar from the selection. The bar is hidden
* until at least one revision is picked. Its single button reads
* "Compare revisions <first> and ?" while one revision is chosen and is
* disabled; once a second, distinct revision is picked it fills in the "?"
* and links to the diff.
* @param {{a: ?number, b: ?number}} selection
*/
function renderBar(selection) {
const [first, second] = pickOrder.map((slot) => selection[slot]).filter(Boolean);
if (!first) {
bar.hidden = true;
return;
}
bar.hidden = false;
compareLink.textContent = i18n.compare_label
.replace('%(a)s', first)
.replace('%(b)s', second || '?');

if (second && first !== second) {
const [lower, higher] = first < second ? [first, second] : [second, first];
compareLink.href = `?m=diff&a=${lower}&b=${higher}`;
compareLink.removeAttribute('aria-disabled');
compareLink.removeAttribute('tabindex');
compareLink.classList.remove('is-disabled');
} else {
compareLink.removeAttribute('href');
compareLink.setAttribute('aria-disabled', 'true');
compareLink.setAttribute('tabindex', '-1');
compareLink.classList.add('is-disabled');
}
}

/**
* @param {{a: ?number, b: ?number}} selection
*/
function update(selection) {
syncRows(selection);
renderBar(selection);
}

// A radio change updates only its own side, merging with the opposite side
// already stored in the URL (which may point to an off-page revision).
pageHistoryElement.addEventListener('change', (event) => {
const { name, value } = event.target;
if (name !== 'a' && name !== 'b') return;
const selection = readSelection();
selection[name] = parseInt(value, 10);
if (!pickOrder.includes(name)) pickOrder.push(name);
writeSelection(selection);
update(selection);
});

clearButton.addEventListener('click', () => {
pickOrder = [];
const cleared = { a: null, b: null };
writeSelection(cleared);
update(cleared);
});

// Restore from the URL on load.
const initial = readSelection();
pickOrder = ['a', 'b'].filter((slot) => initial[slot]);
update(initial);
}
7 changes: 7 additions & 0 deletions openlibrary/plugins/openlibrary/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -598,4 +598,11 @@ jQuery(function() {
import(/* webpackChunkName: "stats" */ './stats')
.then(module => module.initUniqueLoginCounts(monthlyLoginStats));
}

// History page comparison
const pageHistory = document.querySelector('#pageHistory');
if (pageHistory) {
import(/* webpackChunkName: "history" */ './history')
.then(module => module.initHistory(pageHistory));
}
});
93 changes: 52 additions & 41 deletions openlibrary/templates/history.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@

$ rev = page.latest_revision or page.revision

$ i18n_strings = {
$ "compare_label": _("Compare revisions %(a)s and %(b)s"),
$ }

<div id="contentHead">
$:macros.databarHistory(page)
<span class="lightgreen smallest sansserif">$_("History of edits to")</span>
Expand All @@ -22,60 +26,67 @@ <h1><a href="$page.key">$name</a></h1>
<div id="contentBody">
$:macros.OlPagination(page_num, total_pages)

<form action="">
<table id="pageHistory">
<div id="pageHistoryCompare" class="page-history-compare" role="region" aria-label="$_('Revision comparison')" aria-live="polite" hidden>
<a class="page-history-compare__submit is-disabled" data-compare-link aria-disabled="true" tabindex="-1"></a>
<button type="button" class="page-history-compare__clear" data-compare-clear aria-label="$_('Clear comparison')">&times;</button>
</div>

<table id="pageHistory" data-i18n="$json_encode(i18n_strings)">
<thead>
<tr>
<th class="numberhead" style="text-align:center;">$_("Revision")</th>
<th class="numberhead" style="text-align:center;">$_("Rev")</th>
<th class="historyheader">$_("When")</th>
<th class="historyheader">$_("Who")</th>
<th class="historyheader">$_("Comment")</th>
<th class="historyheader" colspan="2" style="text-align:center;">$_("Diff")</th>
<th class="historyheader" style="text-align:center;">$_("Diff")</th>
<th class="historyheader pick-ab-header">
$code: pass # NOTE: Column header labeling the "A" side (the earlier/left revision) of a revision comparison on a page's edit-history table. Keep extremely short; a single letter is ideal. Pairs with "B".
<span class="pick-ab__option">$_("A")</span>
$code: pass # NOTE: Column header labeling the "B" side (the later/right revision) of a revision comparison on a page's edit-history table. Keep extremely short; a single letter is ideal. Pairs with "A".
<span class="pick-ab__option">$_("B")</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="4"></td>
<td colspan="1" style="text-align:center;">
<input type="submit" value="$_('Compare')" name="_compare" title="$_('See Diff')"/>
</td>
</tr>
$for v in h:
<tr>
<td class="number">$v.revision</td>
<td class="history"><a href="$page.get_url(v=v.revision)" title="$_('View revision %s', v.revision)">$datestr(v.created)</a></td>
$if v.author:
<td class="displayname" style="vertical-align: top;"><div class="truncatedisplayname"><a href="$homepath()$v.author.key" class="truncate">$v.author.displayname</a></div></td>
$elif v.ip and v.ip != '127.0.0.1':
<td class="history" style="vertical-align: top;"><a href="/recentchanges?ip=$v.ip">$v.ip</a></td>
$else:
<td class="history" style="vertical-align: top;">$v.ip</td>
<td class="comment">$:render_template("history/comment", v)</td>
<td class="compare" style="text-align:center;">
<input type="radio" id="a$v.revision" name="a" title="$_('Compare this version...')" value="$v.revision"
$if v.revision == rev - 1:
checked
$elif v.revision == rev:
checked
/>
&nbsp;&nbsp;&nbsp;
<input type="radio" id="b$v.revision" name="b" title="$_('...to this version')" value="$v.revision"
$if v.revision == rev:
checked
/>
$ row_class = "history-current-row" if v.revision == rev else ""
$ comment_class = "comment" if v.comment else "comment comment--empty"
<tr class="$row_class">
<td class="number">
<a href="$page.get_url(v=v.revision)" title="$_('View revision %s', v.revision)">$v.revision</a>
</td>
<td class="when">
$datestr(v.created)
</td>
<td class="who">
$if v.author:
<a href="$homepath()$v.author.key">$v.author.displayname</a>
$elif v.ip and v.ip != '127.0.0.1':
<a href="/recentchanges?ip=$v.ip">$v.ip</a>
$else:
$v.ip
</td>
<td class="$comment_class">
$if v.comment:
$:render_template("history/comment", v)
</td>
<td class="compare-against">
<a href="$page.key?m=diff&b=$v.revision">$_("Changes")</a>
$if v.revision != rev:
<a href="$page.key?m=diff&b=$rev&a=$v.revision" title="$_('Compare this revision with the current version')">$_("vs current")</a>
</td>
<td class="pick-ab">
<fieldset class="pick-ab__radios">
<legend class="shift">
$_("Pick revision %s as A or B for comparison", v.revision)
</legend>
<label class="pick-ab__option"><input type="radio" name="a" value="$v.revision" /><span class="shift">$_("A")</span></label>
<label class="pick-ab__option"><input type="radio" name="b" value="$v.revision" /><span class="shift">$_("B")</span></label>
</fieldset>
</td>
</tr>
<tr>
<td colspan="4"></td>
<td colspan="1" style="text-align:center;">
<input type="submit" value="$_('Compare')" name="_compare" title="$_('See Diff')"/>
</td>
</tr>
</tbody>
</table>
<input type="hidden" name="m" value="diff"/>

</form>

$:macros.OlPagination(page_num, total_pages)

Expand Down
Loading