2018-12-12 18:37:19 +00:00
|
|
|
/**
|
|
|
|
* @module gateway/reference
|
|
|
|
*/
|
|
|
|
|
2024-02-02 18:29:27 +00:00
|
|
|
const { TYPE_REFERENCE } = require( './constants.js' );
|
2019-01-24 15:33:29 +00:00
|
|
|
|
2018-12-12 18:37:19 +00:00
|
|
|
/**
|
|
|
|
* @return {Gateway}
|
|
|
|
*/
|
2024-02-02 18:29:27 +00:00
|
|
|
module.exports = function createReferenceGateway() {
|
2019-02-20 10:35:34 +00:00
|
|
|
|
2023-05-12 21:12:16 +00:00
|
|
|
/**
|
|
|
|
* @param {string} id
|
|
|
|
* @return {HTMLElement}
|
|
|
|
*/
|
2019-02-20 10:35:34 +00:00
|
|
|
function scrapeReferenceText( id ) {
|
2024-01-11 09:43:59 +00:00
|
|
|
const idSelector = `#${ CSS.escape( id ) }`;
|
2019-02-20 10:35:34 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Same alternative selectors with and without mw-… as in the RESTbased endpoint.
|
2020-06-22 21:49:14 +00:00
|
|
|
*
|
2019-02-20 10:35:34 +00:00
|
|
|
* @see https://phabricator.wikimedia.org/diffusion/GMOA/browse/master/lib/transformations/references/structureReferenceListContent.js$138
|
|
|
|
*/
|
2024-01-11 09:43:59 +00:00
|
|
|
return document.querySelector( `${ idSelector } .mw-reference-text, ${ idSelector } .reference-text` );
|
2019-02-20 10:35:34 +00:00
|
|
|
}
|
|
|
|
|
2019-02-19 16:01:16 +00:00
|
|
|
/**
|
2021-02-18 17:45:37 +00:00
|
|
|
* Attempts to find a single reference type identifier, limited to a list of known types.
|
|
|
|
* - When a `class="…"` attribute mentions multiple known types, the last one is used, following
|
|
|
|
* CSS semantics.
|
|
|
|
* - When there are multiple <cite> tags, the first with a known type is used.
|
2019-02-19 16:01:16 +00:00
|
|
|
*
|
2023-05-12 21:12:16 +00:00
|
|
|
* @param {HTMLElement} referenceText
|
2019-08-05 12:35:21 +00:00
|
|
|
* @return {string|null}
|
2019-02-19 16:01:16 +00:00
|
|
|
*/
|
2023-05-12 21:12:16 +00:00
|
|
|
function scrapeReferenceType( referenceText ) {
|
2021-02-18 17:45:37 +00:00
|
|
|
const KNOWN_TYPES = [ 'book', 'journal', 'news', 'note', 'web' ];
|
2019-02-20 16:15:34 +00:00
|
|
|
let type = null;
|
2023-05-12 21:12:16 +00:00
|
|
|
const citeTags = referenceText.querySelectorAll( 'cite[class]' );
|
|
|
|
Array.prototype.forEach.call( citeTags, ( element ) => {
|
|
|
|
// don't need to keep scanning if one is found.
|
|
|
|
if ( type ) {
|
|
|
|
return;
|
|
|
|
}
|
2021-02-18 17:45:37 +00:00
|
|
|
const classNames = element.className.split( /\s+/ );
|
|
|
|
for ( let i = classNames.length; i--; ) {
|
|
|
|
if ( KNOWN_TYPES.indexOf( classNames[ i ] ) !== -1 ) {
|
|
|
|
type = classNames[ i ];
|
|
|
|
return false;
|
|
|
|
}
|
2019-02-20 16:15:34 +00:00
|
|
|
}
|
|
|
|
} );
|
|
|
|
return type;
|
2019-02-19 16:01:16 +00:00
|
|
|
}
|
|
|
|
|
2018-12-12 18:37:19 +00:00
|
|
|
/**
|
|
|
|
* @param {mw.Title} title
|
2021-05-07 09:30:48 +00:00
|
|
|
* @param {HTMLAnchorElement} el
|
2019-08-05 12:35:21 +00:00
|
|
|
* @return {AbortPromise<ReferencePreviewModel>}
|
2018-12-12 18:37:19 +00:00
|
|
|
*/
|
2019-02-04 12:08:36 +00:00
|
|
|
function fetchPreviewForTitle( title, el ) {
|
2019-01-24 10:37:30 +00:00
|
|
|
// Need to encode the fragment again as mw.Title returns it as decoded text
|
2019-01-25 19:44:48 +00:00
|
|
|
const id = title.getFragment().replace( / /g, '_' ),
|
2023-05-12 21:12:16 +00:00
|
|
|
referenceNode = scrapeReferenceText( id );
|
2019-01-25 19:44:48 +00:00
|
|
|
|
2023-05-12 21:12:16 +00:00
|
|
|
if ( !referenceNode ||
|
2019-12-12 11:16:43 +00:00
|
|
|
// Skip references that don't contain anything but whitespace, e.g. a single
|
2023-05-12 21:12:16 +00:00
|
|
|
( !referenceNode.textContent.trim() && !referenceNode.children.length )
|
2019-12-12 11:16:43 +00:00
|
|
|
) {
|
2023-04-14 17:54:55 +00:00
|
|
|
return Promise.reject(
|
2019-01-25 19:44:48 +00:00
|
|
|
// Required to set `showNullPreview` to false and not open an error popup
|
2023-04-14 17:54:55 +00:00
|
|
|
{ textStatus: 'abort', textContext: 'Footnote not found or empty', xhr: { readyState: 0 } }
|
|
|
|
);
|
2019-01-25 19:44:48 +00:00
|
|
|
}
|
2018-12-12 18:37:19 +00:00
|
|
|
|
2019-02-16 09:13:23 +00:00
|
|
|
const model = {
|
2024-01-11 09:43:59 +00:00
|
|
|
url: `#${ id }`,
|
2023-05-12 21:12:16 +00:00
|
|
|
extract: referenceNode.innerHTML,
|
2024-01-03 01:00:34 +00:00
|
|
|
type: TYPE_REFERENCE,
|
2023-05-12 21:12:16 +00:00
|
|
|
referenceType: scrapeReferenceType( referenceNode ),
|
2021-05-07 10:09:32 +00:00
|
|
|
// Note: Even the top-most HTMLHtmlElement is guaranteed to have a parent.
|
|
|
|
sourceElementId: el.parentNode.id
|
2019-02-16 09:13:23 +00:00
|
|
|
};
|
|
|
|
|
2024-01-12 20:08:54 +00:00
|
|
|
// Make promise abortable.
|
|
|
|
const promise = Promise.resolve( model );
|
|
|
|
promise.abort = () => {};
|
|
|
|
return promise;
|
2018-12-12 18:37:19 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
2019-01-23 16:50:19 +00:00
|
|
|
fetchPreviewForTitle
|
2018-12-12 18:37:19 +00:00
|
|
|
};
|
|
|
|
}
|