MediaWiki:VerseDrawer.js
Appearance
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* File 02: Vanisource verse drawer — BG 2.13 dev pilot */
(function () {
'use strict';
if (location.hostname !== 'dev.vanisource.org') return;
if (mw.config.get('wgPageName') !== 'BG_2.13') return;
if (mw.config.get('wgAction') !== 'view') return;
mw.loader.using([
'mediawiki.api',
'mediawiki.util'
]).then(function () {
mw.hook('wikipage.content').add(function ($content) {
var verse = $content[0].querySelector('.verse');
if (!verse || document.getElementById('vsd-open')) return;
var api = new mw.Api();
var loaded = false;
var busy = false;
var continuation = null;
var scanned = 0;
var matches = new Map();
var requestId = 0;
var style = document.createElement('style');
style.textContent = `
#vsd-open,
.vsd button {
font: inherit;
cursor: pointer;
border: 1px solid #9d7860;
border-radius: 6px;
padding: 8px 12px;
background: white;
color: #51331f;
}
#vsd-open {
margin: 10px 0 18px;
}
.vsd[hidden] {
display: none !important;
}
.vsd {
position: fixed;
inset: 0 0 0 auto;
width: min(520px, 95vw);
background: #fffdf8;
color: #29231d;
box-shadow: -5px 0 24px #0003;
z-index: 10001;
display: flex;
flex-direction: column;
font: 16px/1.6 sans-serif;
}
.vsd header {
display: flex;
gap: 14px;
align-items: center;
padding: 18px 20px;
border-bottom: 1px solid #ddcdbb;
background: #f5ebdf;
}
.vsd h2 {
font-size: 21px;
margin: 0;
flex: 1;
color: #51331f;
}
.vsd-main {
overflow: auto;
padding: 20px;
flex: 1;
min-height: 0;
}
.vsd-note {
font-size: 13px;
color: #655b50;
}
.vsd-card {
border-bottom: 1px solid #ddcdbb;
padding: 16px 0;
}
.vsd-card button {
text-align: left;
width: 100%;
margin-bottom: 8px;
}
.vsd a {
color: #754325;
}
.vsd-text {
white-space: pre-wrap;
font-family: Georgia, serif;
line-height: 1.8;
}
.vsd-tools {
display: flex;
flex-wrap: wrap;
gap: 12px;
margin-bottom: 18px;
}
.vsd-selected {
outline: 2px solid #9d7860;
outline-offset: 7px;
cursor: pointer;
}
@media (min-width: 1200px) {
body.vsd-is-open {
padding-right: 520px;
box-sizing: border-box;
}
}
`;
document.head.appendChild(style);
function element(tag, text, className) {
var node = document.createElement(tag);
if (text !== undefined) node.textContent = text;
if (className) node.className = className;
return node;
}
var openButton = element(
'button',
'Lectures citing BG 2.13'
);
openButton.id = 'vsd-open';
openButton.type = 'button';
openButton.setAttribute('aria-expanded', 'false');
openButton.setAttribute('aria-controls', 'vsd-panel');
verse.after(openButton);
verse.title = 'Explore lectures citing BG 2.13';
var panel = element('aside', undefined, 'vsd');
panel.id = 'vsd-panel';
panel.hidden = true;
panel.setAttribute('aria-labelledby', 'vsd-title');
var header = element('header');
var heading = element('h2', 'BG 2.13 · Lectures');
heading.id = 'vsd-title';
var closeButton = element('button', 'Close');
closeButton.type = 'button';
var main = element('div', undefined, 'vsd-main');
header.append(heading, closeButton);
panel.append(header, main);
document.body.append(panel);
function hide() {
panel.hidden = true;
document.body.classList.remove('vsd-is-open');
verse.classList.remove('vsd-selected');
openButton.setAttribute('aria-expanded', 'false');
openButton.focus();
}
closeButton.onclick = hide;
document.addEventListener('keydown', function (event) {
if (event.key === 'Escape' && !panel.hidden) hide();
});
function show() {
panel.hidden = false;
document.body.classList.add('vsd-is-open');
verse.classList.add('vsd-selected');
openButton.setAttribute('aria-expanded', 'true');
closeButton.focus();
if (!loaded && !busy) load();
}
openButton.onclick = show;
verse.addEventListener('click', function (event) {
if (
!event.target.closest('a') &&
!window.getSelection().toString()
) {
show();
}
});
function sourceLink(title) {
var link = element('a', 'Open full page');
link.href = mw.util.getUrl(title);
link.target = '_blank';
link.rel = 'noopener';
return link;
}
function render(message) {
requestId++;
heading.textContent = 'BG 2.13 · Lectures';
main.replaceChildren();
main.append(element(
'p',
'Pilot: existing links filtered by lecture titles ' +
'and categories. Unlinked quotations are not included.',
'vsd-note'
));
main.append(element(
'p',
matches.size + ' lecture pages found · ' +
scanned + ' linked pages checked.',
'vsd-note'
));
if (message) main.append(element('p', message));
Array.from(matches.values())
.sort(function (a, b) {
return a.title.localeCompare(b.title);
})
.forEach(function (page) {
var card = element(
'div',
undefined,
'vsd-card'
);
var readButton = element('button', page.title);
readButton.type = 'button';
readButton.onclick = function () {
transcript(page.title);
};
card.append(readButton, sourceLink(page.title));
main.append(card);
});
if (!busy && (!loaded || continuation)) {
var moreButton = element(
'button',
loaded ? 'Check more linked pages' : 'Retry'
);
moreButton.type = 'button';
moreButton.onclick = load;
main.append(moreButton);
}
if (loaded && !continuation && !matches.size) {
main.append(element(
'p',
'No lecture pages were identified through these ' +
'links. This does not mean the verse was never cited.'
));
}
}
async function load() {
if (busy) return;
busy = true;
render('Checking linked pages…');
var parameters = {
action: 'query',
formatversion: 2,
generator: 'backlinks',
gbltitle: 'BG 2.13',
gblnamespace: 0,
gbllimit: 50,
prop: 'categories',
cllimit: 'max'
};
var next = continuation;
var pending = new Map();
try {
do {
var data = await api.get(
Object.assign({}, parameters, next || {})
);
var pages = (
data.query && data.query.pages
) || [];
pages.forEach(function (page) {
var previous = pending.get(page.pageid);
if (previous) {
page.categories = (
previous.categories || []
).concat(page.categories || []);
}
pending.set(page.pageid, page);
});
next = data.continue || null;
} while (next && next.clcontinue);
pending.forEach(function (page) {
var isLecture = /\blecture\b/i.test(page.title);
if (!isLecture) {
isLecture = (page.categories || []).some(
function (category) {
return /\blectures?\b/i.test(
category.title
);
}
);
}
if (isLecture && !page.redirect) {
matches.set(page.pageid, page);
}
});
scanned += pending.size;
continuation = next;
loaded = true;
busy = false;
render();
} catch (error) {
busy = false;
render('The API request failed. Please retry.');
}
}
async function transcript(title) {
var ownRequest = ++requestId;
heading.textContent = 'Lecture';
main.replaceChildren();
main.scrollTop = 0;
var tools = element('div', undefined, 'vsd-tools');
var backButton = element(
'button',
'Back to lecture list'
);
backButton.type = 'button';
backButton.onclick = function () {
render();
};
tools.append(backButton, sourceLink(title));
main.append(tools, element('h3', title));
var status = element('p', 'Loading transcript…');
main.append(status);
try {
var data = await api.get({
action: 'parse',
page: title,
prop: 'text',
formatversion: 2,
disableeditsection: 1
});
if (ownRequest !== requestId) return;
var doc = new DOMParser().parseFromString(
data.parse.text,
'text/html'
);
doc.querySelectorAll(
'script,style,iframe,nav,' +
'.mw-editsection,.printfooter'
).forEach(function (node) {
node.remove();
});
var root = (
doc.querySelector('.mw-parser-output') ||
doc.body
);
root.querySelectorAll(
'p,div,h2,h3,h4,li,br'
).forEach(function (node) {
node.appendChild(doc.createTextNode('\n'));
});
var text = root.textContent
.replace(/\n[ \t]+/g, '\n')
.replace(/\n{3,}/g, '\n\n')
.trim();
status.remove();
var box = element(
'div',
undefined,
'vsd-text'
);
var index = text.toLowerCase().search(
/dehino|bg\s*2\.13/
);
if (index >= 0) {
var start = Math.max(0, index - 180);
var end = Math.min(text.length, index + 600);
box.append(document.createTextNode(
text.slice(0, start)
));
var mark = element(
'mark',
text.slice(start, end)
);
box.append(
mark,
document.createTextNode(text.slice(end))
);
main.append(box);
requestAnimationFrame(function () {
if (ownRequest !== requestId) return;
var offset = (
mark.getBoundingClientRect().top -
main.getBoundingClientRect().top
);
main.scrollTop += offset - 100;
});
} else {
box.textContent = text;
main.append(box);
}
} catch (error) {
if (ownRequest === requestId) {
status.textContent =
'Could not load the transcript. ' +
'Use Open full page.';
}
}
}
});
});
}());