'use strict';
const Plugin = videojs.getPlugin( 'plugin' );
/**
* @extends Plugin
*/
class EndCard extends Plugin {
constructor( player, options ) {
super( player, options );
this.overlay = document.createElement( 'div' );
this.container = document.createElement( 'div' );
this.overlay.appendChild( this.container );
this.api = new mw.Api();
this.imageDetails = {};
this.userDismissed = false;
if ( options.customClass ) {
player.addClass( options.customClass );
}
// Add our ui element
player.ready( () => {
const title = player.options().mwTitle;
if ( !title ) {
return;
}
this.api.get( {
action: 'query',
prop: 'imageinfo',
titles: title.getPrefixedText(),
iiprop: 'url|size',
uselang: 'content',
formatversion: 2,
maxage: 300,
smaxage: 300
} ).then( ( result ) => {
if ( !result || !result.query || !result.query.pages || !result.query.pages[ 0 ] ) {
return;
}
this.imageDetails = result.query.pages[ 0 ].imageinfo[ 0 ] || {};
this.originalFilename = title.getFileNameTextWithoutExtension();
this.originalFilepage = this.imageDetails.descriptionurl || '';
if ( this.imageDetails.width === 0 ) {
return;
}
this.buildContent();
const controlBar = player.getChild( 'controlBar' );
this.overlay.className = 'vjs-mw-endcard';
controlBar.contentEl().parentNode.insertBefore(
this.overlay, controlBar.contentEl()
);
} );
} );
player.on( 'ended', () => {
this.userDismissed = false;
this.overlay.classList.add( 'vjs-mw-endcard-visible' );
} );
player.on( 'userinactive', () => {
if ( this.player.paused() && !this.player.ended() && !this.userDismissed ) {
this.overlay.classList.add( 'vjs-mw-endcard-visible' );
}
} );
player.on( [ 'play', 'seeked' ], () => {
this.userDismissed = false;
this.overlay.classList.remove( 'vjs-mw-endcard-visible' );
} );
player.on( 'useractive', () => {
if ( !this.player.paused() ) {
this.overlay.classList.remove( 'vjs-mw-endcard-visible' );
}
} );
this.overlay.addEventListener( 'click', ( e ) => {
if (
this.player.paused() &&
( e.target === this.overlay || e.target === this.container )
) {
this.userDismissed = true;
this.overlay.classList.remove( 'vjs-mw-endcard-visible' );
}
} );
// Esc is captured by the dialog, so intercept at document level for this specific case
this.onKeyDown = ( e ) => {
if ( e.key === 'Escape' && this.overlay.classList.contains( 'vjs-mw-endcard-visible' ) ) {
e.stopPropagation();
this.userDismissed = true;
this.overlay.classList.remove( 'vjs-mw-endcard-visible' );
}
};
document.addEventListener( 'keydown', this.onKeyDown, true );
}
dispose() {
document.removeEventListener( 'keydown', this.onKeyDown, true );
super.dispose();
}
buildContent() {
this.container.appendChild( this.buildShareSection() );
this.container.appendChild( this.buildEmbedSection() );
}
/**
* Helper to build a common widget container with a title and body.
*
* @private
* @param {string} titleMsgKey i18n message key for the widget title
* @param {string} widgetClass additional CSS class to add to the widget
* @return {{widget: HTMLDivElement, body: HTMLDivElement}}
*/
createWidget( titleMsgKey, widgetClass ) {
const widget = document.createElement( 'div' );
const body = document.createElement( 'div' );
const title = document.createElement( 'h6' );
title.textContent = mw.msg( titleMsgKey );
widget.appendChild( title );
widget.appendChild( body );
widget.setAttribute( 'role', 'region' );
widget.classList.add( 'tmh-widget' );
if ( widgetClass ) {
widget.classList.add( widgetClass );
}
return { widget, body };
}
/**
* @private
* @param {string} content
* @return {Promise<boolean>} true if copy succeeded, false otherwise
*/
async copyToClipboard( content ) {
// eslint-disable-next-line compat/compat
const hasFeature = navigator.clipboard && 'writeText' in navigator.clipboard;
if ( !hasFeature ) {
mw.notify( mw.msg( 'videojs-endcard-copy-failed' ), { type: 'error' } );
return false;
}
try {
// eslint-disable-next-line compat/compat
await navigator.clipboard.writeText( content );
} catch ( e ) {
mw.notify( mw.msg( 'videojs-endcard-copy-failed' ), { type: 'error' } );
return false;
}
mw.notify( mw.msg( 'videojs-endcard-copied' ) );
return true;
}
async triggerShareAction() {
const shareData = {
url: this.originalFilepage,
title: this.originalFilename,
files: []
};
if ( typeof navigator.canShare === 'function' && navigator.canShare( shareData ) ) {
await navigator.share( shareData );
} else {
mw.notify( mw.msg( 'videojs-endcard-share-not-possible' ), { type: 'warning' } );
}
}
buildShareSection() {
const copyLinkButton = new OO.ui.ButtonWidget();
copyLinkButton.setIcon( 'copy' )
.setTitle( mw.msg( 'videojs-endcard-copy-link' ) )
.setLabel( mw.msg( 'videojs-endcard-copy-link' ) )
.setFlags( 'progressive' );
copyLinkButton.on( 'click', () => this.copyToClipboard( this.originalFilepage ) );
const shareLinkButton = new OO.ui.ButtonWidget();
shareLinkButton.setIcon( 'share' )
.setTitle( mw.msg( 'videojs-endcard-share' ) )
.setLabel( mw.msg( 'videojs-endcard-share' ) )
.setFlags( 'progressive' );
if ( typeof navigator.canShare === 'function' ) {
shareLinkButton.on( 'click', () => this.triggerShareAction() );
}
const { widget: linkWidget, body: linkWidgetBody } = this.createWidget( 'videojs-endcard-share-title', 'link-widget' );
linkWidgetBody.appendChild( copyLinkButton.$element[ 0 ] );
linkWidgetBody.appendChild( shareLinkButton.$element[ 0 ] );
return linkWidget;
}
buildEmbedSection() {
const mediaWidth = this.imageDetails.width || 0;
const mediaHeight = this.imageDetails.height || 0;
const url = new URL( this.originalFilepage, document.baseURI );
const searchParams = new URLSearchParams( url.search );
searchParams.append( 'embedplayer', 'true' );
url.search = searchParams.toString();
const embedUrl = url.toString();
const embed = document.createElement( 'textarea' );
// only values that are safe can be used directly here
embed.value = `<iframe width="${ mediaWidth }" height="${ mediaHeight }" frameborder="0" loading="lazy" allow="autoplay; fullscreen; picture-in-picture; clipboard-write; web-share" allowfullscreen src="${ embedUrl }" />`;
embed.addEventListener( 'click', () => this.copyToClipboard( embed.value ) );
const { widget: embedWidget, body: embedWidgetBody } = this.createWidget( 'videojs-endcard-embed-title', 'embed-widget' );
const copyButton = new OO.ui.ButtonWidget();
copyButton.setIcon( 'copy' )
.setTitle( mw.msg( 'videojs-endcard-embed-copy' ) )
.setLabel( mw.msg( 'videojs-endcard-embed-copy' ) )
.setFlags( 'progressive' );
copyButton.on( 'click', () => this.copyToClipboard( embed.value ) );
embedWidgetBody.appendChild( embed );
embedWidgetBody.appendChild( copyButton.$element[ 0 ] );
return embedWidget;
}
}
// register the plugin
videojs.registerPlugin( 'endCard', EndCard );
module.exports = EndCard;