Twinkle mobile test adapter v4: pre-create p-cactions in overflow menu
Twinkle mobile test adapter v5: on-screen panel like English Wikipedia
Line 1: Line 1:
// Twinkle mobile TEST adapter v4 - personal test only
// Twinkle mobile TEST adapter v5 - personal test only
// Page: User:Bharatwiki Socrates/minerva.js (Minerva skin only)
// Page: User:Bharatwiki Socrates/minerva.js (Minerva skin only)
// Root cause (found in Twinkle's own code + v3 census): on any skin
// v4 worked (Twinkle item + inline sub-list inside the More menu),
// other than Vector/Timeless, Twinkle does NOT build its own menu.
// but the user wants the English Wikipedia behaviour: tap "Twinkle"
// It sends every module link to a portlet called "p-cactions", which
// and the options open as a panel ON the screen, not buried inside
// Minerva does not render, so the links go nowhere.
// the scrolling menu.
// v4 fix: BEFORE loading Twinkle, create a real #p-cactions element
// v5 change: the #p-cactions portlet Twinkle needs is now a floating
// inside Minerva's overflow (More) menu list (ul#p-tb), as a "Twinkle"
// panel (fixed position, hidden). The "Twinkle" entry in the More
// item with a collapsible sub-list. Twinkle's links then land in that
// menu only toggles that panel: tapping it closes the menu and opens
// sub-list, where they can be seen and tapped.
// the panel with all Twinkle options. Tapping an option closes the
// panel and opens that module's dialog.
// All user-facing strings in English.
// All user-facing strings in English.


Line 41: Line 42:
'.morebits-dialog-content { overflow-x: auto; }' +
'.morebits-dialog-content { overflow-x: auto; }' +
'.select2-container { max-width: 90vw; }' +
'.select2-container { max-width: 90vw; }' +
'#p-cactions .twinkle-mobile-sub { list-style: none;' +
'#p-cactions.twinkle-mobile-panel { position: fixed;' +
' margin: 0; padding: 0 0 0 16px; }' +
' top: 52px; left: 2vw; width: 96vw; max-height: 72vh;' +
'#p-cactions .twinkle-mobile-sub li a { display: block;' +
' overflow-y: auto; z-index: 999; background: #fff;' +
' padding: 10px 8px; color: #202122; text-decoration: none; }' +
' border: 1px solid #a2a9b1; border-radius: 4px;' +
'#twinkle-mobile-panel { position: fixed; top: 56px; left: 2vw;' +
' box-shadow: 0 4px 16px rgba(0,0,0,0.35); padding: 0 0 6px; }' +
' width: 96vw; max-height: 70vh; overflow-y: auto; z-index: 999;' +
'#p-cactions.twinkle-mobile-panel ul { list-style: none;' +
' background: #fff; border: 1px solid #a2a9b1; border-radius: 4px;' +
' margin: 0; padding: 0; }' +
' box-shadow: 0 4px 16px rgba(0,0,0,0.3); padding: 6px 0; }' +
'#p-cactions.twinkle-mobile-panel li a { display: block;' +
'#twinkle-mobile-panel ul { list-style: none; margin: 0;' +
' padding: 12px 16px; color: #202122; text-decoration: none;' +
' padding: 0; }' +
' font-size: 1em; border-top: 1px solid #eaecf0; }' +
'#twinkle-mobile-panel li a { display: block;' +
'#p-cactions.twinkle-mobile-panel li a:active {' +
' padding: 10px 16px; color: #202122; text-decoration: none; }'
' 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; }'
);
);
rawCss( 'MediaWiki:Gadget-Twinkle.css' );
rawCss( 'MediaWiki:Gadget-Twinkle.css' );
rawCss( 'MediaWiki:Gadget-morebits.css' );
rawCss( 'MediaWiki:Gadget-morebits.css' );
rawCss( 'MediaWiki:Gadget-select2.min.css' );
rawCss( 'MediaWiki:Gadget-select2.min.css' );
var panel = null;
function togglePanel( show ) {
if ( !panel ) {
return;
}
panel.style.display = show ? 'block' : 'none';
}
function closeOverflow() {
var cb = document.getElementById(
'page-actions-overflow-checkbox' );
if ( cb ) {
cb.checked = false;
}
}


function createPortlet() {
function createPortlet() {
if ( document.getElementById( 'p-cactions' ) ) {
if ( document.getElementById( 'p-cactions' ) ) {
panel = document.getElementById( 'p-cactions' );
return true;
return true;
}
}
// 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 );
var list = document.createElement( 'ul' );
panel.appendChild( list );
document.body.appendChild( panel );
// Any tap on a module link closes the panel (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' ) ||
var overflow = document.querySelector( 'ul#p-tb' ) ||
document.querySelector( '.page-actions-overflow-list' );
document.querySelector( '.page-actions-overflow-list' );
Line 67: Line 120:
var li = document.createElement( 'li' );
var li = document.createElement( 'li' );
li.className = 'toggle-list-item';
li.className = 'toggle-list-item';
li.id = 'p-cactions';
var a = document.createElement( 'a' );
var head = document.createElement( 'a' );
a.className = 'toggle-list-item__anchor';
head.className = 'toggle-list-item__anchor';
a.href = '#';
head.href = '#';
var label = document.createElement( 'span' );
var label = document.createElement( 'span' );
label.className = 'toggle-list-item__label';
label.className = 'toggle-list-item__label';
label.textContent = 'Twinkle';
label.textContent = 'Twinkle';
head.appendChild( label );
a.appendChild( label );
var sub = document.createElement( 'ul' );
a.addEventListener( 'click', function ( e ) {
sub.className = 'twinkle-mobile-sub';
sub.style.display = 'none';
head.addEventListener( 'click', function ( e ) {
e.preventDefault();
e.preventDefault();
sub.style.display =
closeOverflow();
sub.style.display === 'none' ? 'block' : 'none';
togglePanel( true );
} );
} );
li.appendChild( head );
li.appendChild( a );
li.appendChild( sub );
overflow.appendChild( li );
overflow.appendChild( li );
return true;
} 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 );
}
}
// Fallback for pages without an overflow menu:
// a panel that Twinkle links can fill, opened by a TW button.
var panel = document.createElement( 'div' );
panel.id = 'twinkle-mobile-panel';
panel.style.display = 'none';
var wrap = document.createElement( 'div' );
wrap.id = 'p-cactions';
var list = document.createElement( 'ul' );
wrap.appendChild( list );
panel.appendChild( wrap );
document.body.appendChild( 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 () {
panel.style.display =
panel.style.display === 'none' ? 'block' : 'none';
} );
document.body.appendChild( btn );
return true;
return true;
}
}
Line 124: Line 162:
return;
return;
}
}
mw.notify( 'Twinkle loaded but its links did not arrive (v4)',
mw.notify( 'Twinkle loaded but its links did not arrive (v5)',
{ type: 'error' } );
{ type: 'error' } );
}
}

Revision as of 05:26, 1 November 2026

// Twinkle mobile TEST adapter v5 - personal test only
// Page: User:Bharatwiki Socrates/minerva.js (Minerva skin only)
// v4 worked (Twinkle item + inline sub-list inside the More menu),
// but the user wants the English Wikipedia behaviour: tap "Twinkle"
// and the options open as a panel ON the screen, not buried inside
// the scrolling menu.
// v5 change: the #p-cactions portlet Twinkle needs is now a floating
// panel (fixed position, hidden). The "Twinkle" entry in the More
// menu only toggles that panel: tapping it closes the menu and opens
// the panel with all Twinkle options. Tapping an option closes the
// panel and opens that module's dialog.
// All user-facing strings in English.

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

	var indexPhp = mw.util.wikiScript( 'index' );
	function rawUrl( title ) {
		return indexPhp + '?title=' + encodeURIComponent( title ) +
			'&action=raw&ctype=text/javascript';
	}
	function rawCss( title ) {
		var link = document.createElement( 'link' );
		link.rel = 'stylesheet';
		link.href = indexPhp + '?title=' + encodeURIComponent( title ) +
			'&action=raw&ctype=text/css';
		document.head.appendChild( link );
	}
	function loadScript( title ) {
		return $.getScript( rawUrl( title ) );
	}

	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; }'
	);
	rawCss( 'MediaWiki:Gadget-Twinkle.css' );
	rawCss( 'MediaWiki:Gadget-morebits.css' );
	rawCss( 'MediaWiki:Gadget-select2.min.css' );

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

	function createPortlet() {
		if ( document.getElementById( 'p-cactions' ) ) {
			panel = document.getElementById( 'p-cactions' );
			return true;
		}
		// 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 );
		var list = document.createElement( 'ul' );
		panel.appendChild( list );
		document.body.appendChild( panel );
		// Any tap on a module link closes the panel (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 );
		}
		return true;
	}

	function checkResult( tries ) {
		var landed = document.querySelectorAll(
			'#p-cactions li[id^="twinkle-"]' ).length;
		if ( landed ) {
			mw.notify( 'Twinkle is ready: open the More menu and tap ' +
				'Twinkle (' + landed + ' options)', { type: 'info' } );
			return;
		}
		if ( tries > 0 ) {
			setTimeout( function () { checkResult( tries - 1 ); }, 2000 );
			return;
		}
		mw.notify( 'Twinkle loaded but its links did not arrive (v5)',
			{ type: 'error' } );
	}

	function start() {
		createPortlet();
		mw.loader.using( [
			'mediawiki.util', 'mediawiki.api', 'mediawiki.storage',
			'mediawiki.Title', 'mediawiki.user', 'jquery.ui'
		] ).then( function () {
			return loadScript( 'MediaWiki:Gadget-select2.min.js' );
		} ).then( function () {
			return loadScript( 'MediaWiki:Gadget-morebits.js' );
		} ).then( function () {
			return loadScript( 'MediaWiki:Gadget-Twinkle.js' );
		} ).then( function () {
			checkResult( 10 );
		} ).catch( function ( err ) {
			mw.notify( 'Twinkle mobile test load failed: ' + err,
				{ type: 'error' } );
		} );
	}

	if ( document.readyState === 'loading' ) {
		document.addEventListener( 'DOMContentLoaded', start );
	} else {
		start();
	}
}() );