Jump to content

MediaWiki:Gadget-Download.js

From Appropedia

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 );
Cookies help us deliver our services. By using our services, you agree to our use of cookies.