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.
// TwinkleMobile adapter
// Makes Twinkle usable in the mobile view (Minerva skin).
//
// Why this is needed: Twinkle adds its menu links to the portlet
// "p-cactions". Desktop skins render that portlet, but Minerva does
// not render it at all, so on mobile the links used to have nowhere
// to appear and Twinkle seemed missing.
//
// What this does (Minerva skin only):
//  * Creates the missing "p-cactions" portlet as a floating panel.
//  * Adds a "Twinkle" entry to the page actions ("More") menu.
//    Tapping it closes the menu and opens the panel on screen with
//    all Twinkle options (the same behaviour as the desktop menu).
//  * On pages without that menu, a floating "TW" button opens the
//    panel instead.
//  * Sizes Twinkle dialogs so they fit a phone screen.
// On every other skin this script does nothing.

( function () {
	if ( mw.config.get( 'skin' ) !== 'minerva' ) {
		return;
	}

	mw.util.addCSS(
		'.ui-dialog { max-width: 96vw !important; left: 2vw !important; }' +
		'.morebits-dialog { max-width: 96vw !important; }' +
		'.morebits-dialog-content { overflow-x: auto; }' +
		'.select2-container { max-width: 90vw; }' +
		'#p-cactions.twinkle-mobile-panel { position: fixed;' +
		' top: 52px; left: 2vw; width: 96vw; max-height: 72vh;' +
		' overflow-y: auto; z-index: 999; background: #fff;' +
		' border: 1px solid #a2a9b1; border-radius: 4px;' +
		' box-shadow: 0 4px 16px rgba(0,0,0,0.35); padding: 0 0 6px; }' +
		'#p-cactions.twinkle-mobile-panel ul { list-style: none;' +
		' margin: 0; padding: 0; }' +
		'#p-cactions.twinkle-mobile-panel li a { display: block;' +
		' padding: 12px 16px; color: #202122; text-decoration: none;' +
		' font-size: 1em; border-top: 1px solid #eaecf0; }' +
		'#p-cactions.twinkle-mobile-panel li a:active {' +
		' background: #eaecf0; }' +
		'.twinkle-panel-head { display: flex;' +
		' justify-content: space-between; align-items: center;' +
		' padding: 10px 16px; font-weight: bold; }' +
		'.twinkle-panel-close { background: none; border: 0;' +
		' font-size: 20px; line-height: 1; padding: 4px 8px;' +
		' cursor: pointer; }'
	);

	var panel = null;
	function togglePanel( show ) {
		if ( panel ) {
			panel.style.display = show ? 'block' : 'none';
		}
	}
	function closeOverflow() {
		var cb = document.getElementById(
			'page-actions-overflow-checkbox' );
		if ( cb ) {
			cb.checked = false;
		}
	}

	function createPortlet() {
		var existing = document.getElementById( 'p-cactions' );
		if ( existing ) {
			panel = existing;
			return;
		}
		if ( !document.body ) {
			return;
		}
		// The floating panel IS the p-cactions portlet.
		panel = document.createElement( 'div' );
		panel.id = 'p-cactions';
		panel.className = 'twinkle-mobile-panel';
		panel.style.display = 'none';
		var head = document.createElement( 'div' );
		head.className = 'twinkle-panel-head';
		var title = document.createElement( 'span' );
		title.textContent = 'Twinkle';
		var closeBtn = document.createElement( 'button' );
		closeBtn.className = 'twinkle-panel-close';
		closeBtn.textContent = '×';
		closeBtn.addEventListener( 'click', function () {
			togglePanel( false );
		} );
		head.appendChild( title );
		head.appendChild( closeBtn );
		panel.appendChild( head );
		panel.appendChild( document.createElement( 'ul' ) );
		document.body.appendChild( panel );
		// Tapping a module link closes the panel (its dialog opens).
		panel.addEventListener( 'click', function ( e ) {
			if ( e.target.closest( 'li a' ) &&
				!e.target.closest( '.twinkle-panel-head' ) ) {
				togglePanel( false );
			}
		} );

		// Entry inside the More (overflow) menu toggles the panel.
		var overflow = document.querySelector( 'ul#p-tb' ) ||
			document.querySelector( '.page-actions-overflow-list' );
		if ( overflow ) {
			var li = document.createElement( 'li' );
			li.className = 'toggle-list-item';
			var a = document.createElement( 'a' );
			a.className = 'toggle-list-item__anchor';
			a.href = '#';
			var label = document.createElement( 'span' );
			label.className = 'toggle-list-item__label';
			label.textContent = 'Twinkle';
			a.appendChild( label );
			a.addEventListener( 'click', function ( e ) {
				e.preventDefault();
				closeOverflow();
				togglePanel( true );
			} );
			li.appendChild( a );
			overflow.appendChild( li );
		} else {
			// Fallback: floating TW button toggles the panel.
			var btn = document.createElement( 'button' );
			btn.textContent = 'TW';
			btn.style.cssText = 'position:fixed;right:12px;' +
				'bottom:64px;z-index:998;padding:10px 14px;' +
				'border-radius:20px;background:#202122;color:#fff;' +
				'border:0;';
			btn.addEventListener( 'click', function () {
				togglePanel( panel.style.display === 'none' );
			} );
			document.body.appendChild( btn );
		}
	}

	// Run immediately and again when the DOM is ready, whichever
	// comes first wins; the portlet must exist before Twinkle adds
	// its links.
	createPortlet();
	$( createPortlet );
}() );