MediaWiki:Gadget-Download.js
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.
const Download = {
init: function ( $content ) {
$content.find( '.download-button' ).on( 'click', Download.downloadFile );
},
/**
* Prevent the default download of a file, get it behind scenes and then download it.
* We do this because sometimes the file generation takes several seconds to complete
* so intercepting it this way lets us hint the user that something is happening.
* Also, in the case of PDF booklets, we turn the PDF into a booklet before downloading
*/
downloadFile: async function ( event ) {
event.preventDefault();
// Disable the button to prevent further clicks
this.style.pointerEvents = 'none';
const button = this.querySelector( '.cdx-button' );
button.classList.remove( 'cdx-button--fake-button--enabled' );
button.classList.add( 'cdx-button--fake-button--disabled' );
const buttonText = button.textContent; // Save the text to restore it later
button.textContent = 'Loading...';
// Get the file
const link = this.querySelector( 'a' );
const result = await fetch( link.href );
const bytes = await result.arrayBuffer();
const type = result.headers.get( 'Content-Type' );
const contentDisposition = result.headers.get( 'Content-Disposition' );
const matches = contentDisposition.match( /filename *= *([^;]+)/ );
const name = matches[1];
let file = { bytes: bytes, type: type, name: name };
// Convert the file into a booklet
if ( this.classList.contains( 'booklet' ) ) {
file = await Download.makeBooklet( file );
}
// Download the file
const blob = new Blob( [ file.bytes ], { type: file.type } );
const url = URL.createObjectURL( blob );
const a = document.createElement( 'a' );
a.href = url;
a.download = file.name;
document.body.appendChild( a );
a.click();
document.body.removeChild( a );
URL.revokeObjectURL( url );
// Re-enable the button
this.style.pointerEvents = '';
button.textContent = buttonText;
button.classList.remove( 'cdx-button--fake-button--disabled' );
button.classList.add( 'cdx-button--fake-button--enabled' );
},
/**
* This method transforms a PDF into a booklet
* The code is based on bookletize.js by Jeffrey Yoo Warren (https://jywarren.github.io/bookletize.js)
* which uses PDFLib.js (https://pdf-lib.js.org)
*/
makeBooklet: async function ( file ) {
await mw.loader.getScript( 'https://cdnjs.cloudflare.com/ajax/libs/pdf-lib/1.17.1/pdf-lib.min.js' );
const bookletDoc = await PDFLib.PDFDocument.create();
const fileDoc = await PDFLib.PDFDocument.load( file.bytes );
const { width, height } = await fileDoc.getPages()[0].getSize();
// Pad the file PDF to a multiple of 4 by adding empty pages
let pageCount = await fileDoc.getPageCount();
while ( pageCount % 4 ) {
let page = fileDoc.addPage( [ width, height ] );
page.drawText( '' );
pageCount++;
}
// Iterate through, plucking out 4 pages at a time and inserting into new sheet
const filePages = await fileDoc.getPages();
for ( let p = 0; p < pageCount; p += 4 ) {
// Double width, same height
const bookletPage = bookletDoc.addPage( [ width * 2, height ] );
const bookletPage2 = bookletDoc.addPage( [ width * 2, height ] );
// If the file is more than 16 pages long, we make a perfect binding, else a saddle stitch
if ( pageCount > 16 ) {
await Download.getPage( 3, { x: 0, y: 0 }, filePages, bookletDoc, bookletPage );
await Download.getPage( 0, { x: width, y: 0 }, filePages, bookletDoc, bookletPage );
await Download.getPage( 0, { x: 0, y: 0 }, filePages, bookletDoc, bookletPage2 );
await Download.getPage( 0, { x: width, y: 0 }, filePages, bookletDoc, bookletPage2 );
} else {
await Download.getPage( filePages.length - 1, { x: 0, y: 0 }, filePages, bookletDoc, bookletPage );
await Download.getPage( 0, { x: width, y: 0 }, filePages, bookletDoc, bookletPage );
await Download.getPage( 0, { x: 0, y: 0 }, filePages, bookletDoc, bookletPage2 );
await Download.getPage( filePages.length - 1, { x: width, y: 0 }, filePages, bookletDoc, bookletPage2 );
}
}
const bookletBytes = await bookletDoc.save();
return { bytes: bookletBytes, type: file.type, name: file.name };
},
/**
* Helper method to fetch the next page from the file stack and remove it
*/
getPage: async function ( filePosition, placement, filePages, _bookletDoc, _bookletPage ) {
const filePage = filePages.splice( filePosition, 1 )[0];
const embeddedPage = await _bookletDoc.embedPage( filePage );
_bookletPage.drawPage( embeddedPage, placement );
},
};
mw.hook( 'wikipage.content' ).add( Download.init );