2018-12-12 18:37:19 +00:00
|
|
|
/**
|
|
|
|
* @module referencePreview
|
|
|
|
*/
|
2023-05-09 15:11:44 +00:00
|
|
|
import { isTrackingEnabled, LOGGING_SCHEMA } from '../../../instrumentation/referencePreviews';
|
2018-12-12 18:37:19 +00:00
|
|
|
import { renderPopup } from '../popup/popup';
|
2021-02-18 17:45:37 +00:00
|
|
|
import { createNodeFromTemplate, escapeHTML } from '../templateUtil';
|
2020-12-01 08:47:11 +00:00
|
|
|
|
|
|
|
const templateHTML = `
|
|
|
|
<div class="mwe-popups-container">
|
|
|
|
<div class="mwe-popups-extract">
|
|
|
|
<div class="mwe-popups-scroll">
|
|
|
|
<strong class="mwe-popups-title">
|
2023-08-15 18:19:56 +00:00
|
|
|
<span class="popups-icon"></span>
|
2020-12-01 08:47:11 +00:00
|
|
|
<span class="mwe-popups-title-placeholder"></span>
|
|
|
|
</strong>
|
2023-09-26 09:32:25 +00:00
|
|
|
<bdi><div class="mw-parser-output"></div></bdi>
|
2020-12-01 08:47:11 +00:00
|
|
|
</div>
|
2021-02-05 15:47:46 +00:00
|
|
|
<div class="mwe-popups-fade"></div>
|
2020-12-01 08:47:11 +00:00
|
|
|
</div>
|
2021-02-05 15:47:46 +00:00
|
|
|
<footer>
|
|
|
|
<div class="mwe-popups-settings"></div>
|
|
|
|
</footer>
|
2020-12-01 08:47:11 +00:00
|
|
|
</div>`;
|
2018-12-12 18:37:19 +00:00
|
|
|
|
2023-05-12 21:12:16 +00:00
|
|
|
/**
|
|
|
|
* @param {HTMLElement} node
|
|
|
|
* @param {HTMLElement|string} htmlOrOtherNode
|
|
|
|
*/
|
|
|
|
const replaceWith = ( node, htmlOrOtherNode ) => {
|
|
|
|
if ( typeof htmlOrOtherNode === 'string' ) {
|
|
|
|
node.insertAdjacentHTML( 'afterend', htmlOrOtherNode );
|
|
|
|
} else {
|
|
|
|
node.parentNode.appendChild( htmlOrOtherNode );
|
|
|
|
}
|
|
|
|
node.remove();
|
|
|
|
};
|
|
|
|
|
2018-12-12 18:37:19 +00:00
|
|
|
/**
|
2019-03-18 12:18:25 +00:00
|
|
|
* @param {ext.popups.ReferencePreviewModel} model
|
2023-11-17 16:12:20 +00:00
|
|
|
* @return {jQuery}
|
2018-12-12 18:37:19 +00:00
|
|
|
*/
|
|
|
|
export function renderReferencePreview(
|
|
|
|
model
|
|
|
|
) {
|
2021-02-18 17:45:37 +00:00
|
|
|
const type = model.referenceType || 'generic';
|
|
|
|
// The following messages are used here:
|
|
|
|
// * popups-refpreview-book
|
|
|
|
// * popups-refpreview-journal
|
|
|
|
// * popups-refpreview-news
|
|
|
|
// * popups-refpreview-note
|
|
|
|
// * popups-refpreview-web
|
2024-01-11 09:43:59 +00:00
|
|
|
let titleMsg = mw.message( `popups-refpreview-${ type }` );
|
2021-02-18 17:45:37 +00:00
|
|
|
if ( !titleMsg.exists() ) {
|
|
|
|
titleMsg = mw.message( 'popups-refpreview-reference' );
|
|
|
|
}
|
|
|
|
|
2023-05-12 21:12:16 +00:00
|
|
|
const el = renderPopup( model.type, createNodeFromTemplate( templateHTML ) );
|
|
|
|
replaceWith( el.querySelector( '.mwe-popups-title-placeholder' ), escapeHTML( titleMsg.text() ) );
|
2020-12-01 08:47:11 +00:00
|
|
|
// The following classes are used here:
|
2023-09-01 16:24:11 +00:00
|
|
|
// * popups-icon--reference-generic
|
|
|
|
// * popups-icon--reference-book
|
|
|
|
// * popups-icon--reference-journal
|
|
|
|
// * popups-icon--reference-news
|
|
|
|
// * popups-icon--reference-note
|
|
|
|
// * popups-icon--reference-web
|
2023-08-15 18:19:56 +00:00
|
|
|
el.querySelector( '.mwe-popups-title .popups-icon' )
|
2024-01-11 09:43:59 +00:00
|
|
|
.classList.add( `popups-icon--reference-${ type }` );
|
2023-05-12 21:12:16 +00:00
|
|
|
el.querySelector( '.mw-parser-output' )
|
|
|
|
.innerHTML = model.extract;
|
2019-01-26 09:56:08 +00:00
|
|
|
|
|
|
|
// Make sure to not destroy existing targets, if any
|
2023-05-12 21:12:16 +00:00
|
|
|
Array.prototype.forEach.call(
|
|
|
|
el.querySelectorAll( '.mwe-popups-extract a[href][class~="external"]:not([target])' ),
|
|
|
|
( a ) => {
|
|
|
|
a.target = '_blank';
|
|
|
|
// Don't let the external site access and possibly manipulate window.opener.location
|
2024-01-11 09:43:59 +00:00
|
|
|
a.rel = `${ a.rel ? `${ a.rel } ` : '' }noopener`;
|
2023-05-12 21:12:16 +00:00
|
|
|
}
|
|
|
|
);
|
2019-01-26 09:56:08 +00:00
|
|
|
|
2020-10-25 18:22:09 +00:00
|
|
|
// We assume elements that benefit from being collapsible are to large for the popup
|
2023-05-12 21:12:16 +00:00
|
|
|
Array.prototype.forEach.call( el.querySelectorAll( '.mw-collapsible' ), ( node ) => {
|
|
|
|
const otherNode = document.createElement( 'div' );
|
|
|
|
otherNode.classList.add( 'mwe-collapsible-placeholder' );
|
|
|
|
const icon = document.createElement( 'span' );
|
2023-09-01 16:24:11 +00:00
|
|
|
icon.classList.add( 'popups-icon', 'popups-icon--infoFilled' );
|
2023-08-15 18:19:56 +00:00
|
|
|
const label = document.createElement( 'span' );
|
2023-05-12 21:12:16 +00:00
|
|
|
label.classList.add( 'mwe-collapsible-placeholder-label' );
|
|
|
|
label.textContent = mw.msg( 'popups-refpreview-collapsible-placeholder' );
|
|
|
|
otherNode.appendChild( icon );
|
|
|
|
otherNode.appendChild( label );
|
|
|
|
replaceWith( node, otherNode );
|
|
|
|
} );
|
2020-10-25 18:22:09 +00:00
|
|
|
|
|
|
|
// Undo remaining effects from the jquery.tablesorter.js plugin
|
2023-05-12 21:12:16 +00:00
|
|
|
const undoHeaderSort = ( headerSort ) => {
|
|
|
|
headerSort.classList.remove( 'headerSort' );
|
|
|
|
headerSort.removeAttribute( 'tabindex' );
|
|
|
|
headerSort.removeAttribute( 'title' );
|
|
|
|
};
|
|
|
|
Array.prototype.forEach.call( el.querySelectorAll( 'table.sortable' ), ( node ) => {
|
|
|
|
node.classList.remove( 'sortable', 'jquery-tablesorter' );
|
|
|
|
Array.prototype.forEach.call( node.querySelectorAll( '.headerSort' ), undoHeaderSort );
|
|
|
|
} );
|
2020-10-25 18:22:09 +00:00
|
|
|
|
2023-11-06 08:48:29 +00:00
|
|
|
// TODO: Do not remove this but move it up into the templateHTML constant!
|
|
|
|
const settingsButton = document.createElement( 'a' );
|
|
|
|
settingsButton.classList.add( 'cdx-button', 'cdx-button--fake-button', 'cdx-button--fake-button--enabled', 'cdx-button--weight-quiet', 'cdx-button--icon-only', 'mwe-popups-settings-button' );
|
|
|
|
const settingsIcon = document.createElement( 'span' );
|
|
|
|
settingsIcon.classList.add( 'popups-icon', 'popups-icon--size-small', 'popups-icon--settings' );
|
|
|
|
const settingsButtonLabel = document.createElement( 'span' );
|
|
|
|
settingsButtonLabel.textContent = mw.msg( 'popups-settings-icon-gear-title' );
|
|
|
|
settingsButton.append( settingsIcon );
|
|
|
|
settingsButton.append( settingsButtonLabel );
|
|
|
|
el.querySelector( '.mwe-popups-settings' ).appendChild( settingsButton );
|
2021-02-05 15:47:46 +00:00
|
|
|
|
2023-05-09 15:11:44 +00:00
|
|
|
if ( isTrackingEnabled() ) {
|
2023-05-12 21:12:16 +00:00
|
|
|
el.querySelector( '.mw-parser-output' ).addEventListener( 'click', ( ev ) => {
|
|
|
|
if ( !ev.target.matches( 'a' ) ) {
|
|
|
|
return;
|
|
|
|
}
|
2019-10-24 20:47:43 +00:00
|
|
|
mw.track( LOGGING_SCHEMA, {
|
2019-09-23 15:26:58 +00:00
|
|
|
action: 'clickedReferencePreviewsContentLink'
|
|
|
|
} );
|
|
|
|
} );
|
|
|
|
}
|
|
|
|
|
2023-05-12 21:12:16 +00:00
|
|
|
el.querySelector( '.mwe-popups-scroll' ).addEventListener( 'scroll', function ( e ) {
|
2019-02-28 17:50:36 +00:00
|
|
|
const element = e.target,
|
2019-05-20 07:35:20 +00:00
|
|
|
// We are dealing with floating point numbers here when the page is zoomed!
|
|
|
|
scrolledToBottom = element.scrollTop >= element.scrollHeight - element.clientHeight - 1;
|
2019-02-28 17:50:36 +00:00
|
|
|
|
2023-05-09 15:11:44 +00:00
|
|
|
if ( isTrackingEnabled() ) {
|
2019-09-23 15:26:58 +00:00
|
|
|
if ( !element.isOpenRecorded ) {
|
2019-10-24 20:47:43 +00:00
|
|
|
mw.track( LOGGING_SCHEMA, {
|
2019-09-23 15:26:58 +00:00
|
|
|
action: 'poppedOpen',
|
|
|
|
scrollbarsPresent: element.scrollHeight > element.clientHeight
|
|
|
|
} );
|
|
|
|
element.isOpenRecorded = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
element.scrollTop > 0 &&
|
|
|
|
!element.isScrollRecorded
|
|
|
|
) {
|
2019-10-24 20:47:43 +00:00
|
|
|
mw.track( LOGGING_SCHEMA, {
|
2019-09-23 15:26:58 +00:00
|
|
|
action: 'scrolled'
|
|
|
|
} );
|
|
|
|
element.isScrollRecorded = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-02-28 17:50:36 +00:00
|
|
|
if ( !scrolledToBottom && element.isScrolling ) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-05-12 21:12:16 +00:00
|
|
|
const extract = element.parentNode,
|
2019-10-04 08:22:12 +00:00
|
|
|
hasHorizontalScroll = element.scrollWidth > element.clientWidth,
|
|
|
|
scrollbarHeight = element.offsetHeight - element.clientHeight,
|
|
|
|
hasVerticalScroll = element.scrollHeight > element.clientHeight,
|
|
|
|
scrollbarWidth = element.offsetWidth - element.clientWidth;
|
2023-05-12 21:12:16 +00:00
|
|
|
const fade = extract.querySelector( '.mwe-popups-fade' );
|
2024-01-11 09:43:59 +00:00
|
|
|
fade.style.bottom = hasHorizontalScroll ? `${ scrollbarHeight }px` : 0;
|
|
|
|
fade.style.right = hasVerticalScroll ? `${ scrollbarWidth }px` : 0;
|
2019-04-26 11:35:27 +00:00
|
|
|
|
2019-02-28 17:50:36 +00:00
|
|
|
element.isScrolling = !scrolledToBottom;
|
2023-05-12 21:12:16 +00:00
|
|
|
extract.classList.toggle( 'mwe-popups-fade-out', element.isScrolling );
|
2023-09-26 09:32:25 +00:00
|
|
|
extract.setAttribute( 'lang', mw.config.get( 'wgPageContentLanguage' ) );
|
2019-02-28 17:50:36 +00:00
|
|
|
} );
|
|
|
|
|
2023-05-12 21:12:16 +00:00
|
|
|
return el;
|
2018-12-12 18:37:19 +00:00
|
|
|
}
|