/*********************************************************************************************************************************
* Currently still in development, this is designed to provide a custom list of Quick Links to Wikipedia pages.
* If you encounter any problems using this script, please tell User:Fred_Gandt on either my talk page or this script's talk page.
*
*********************************************************************************************************************************/
/* TODO: Handle #sections */
/* TODO: Reduce API calls */
( function() {
"use strict";
var eByTn = function( p, n, i, nl ) { nl = p.getElementsByTagName( n ); return i !== undefined ? nl[ i ] : nl; },
eById = function( id ) { return document.getElementById( id ); },
cE = function( e ) { return document.createElement( e ); },
nl2a = function( nl ) { return [].slice.call( nl ); },
WG_pagename = mw.config.get( "wgPageName" ),
BASE = "fg-quick-links",
EXT = BASE + "-",
SWITCH = EXT + "switch",
VIEW = EXT + "view",
EMPTY = EXT + "empty",
OPEN = EXT + "open",
TITLE = EXT + "title",
STORAGE = EXT + "storage",
QL = EXT + "ql",
QLE,
NPT,
namespace = l => /^(?:([^\:]+)\:)?(.+)$/.exec( l ),
toggleBase = e => ( e || ql.ui ).classList.toggle( BASE ),
underspace = ( s, b ) => b ? s.replace( /_/g, " " ) : s.replace( / /g, "_" ),
gotIt = v => ql.ui.querySelector( 'a[title="' + underspace( v || WG_pagename, true ).replace( /\"/g, "\\\"" ) + '"]' ),
ql = {
optnnm: { local: EXT + mw.config.get( "wgUserName" ).replace( / /g, "-" ), global: "userjs-" + BASE },
optnvlu: [ { "Mainspace": [] }, { "Mainspace talk": [] } ],
alss: { undefined: "Mainspace", "talk": "Mainspace talk" },
ui: cE( "li" )
},
initOptionValue = function() {
var fns = mw.config.get( "wgFormattedNamespaces" ),
nsi = mw.config.get( "wgNamespaceIds" ),
ns, cns, cnsi, tmp;
for ( ns in nsi ) {
cnsi = nsi[ ns ];
cns = fns[ cnsi ];
if ( underspace( cns ).toLowerCase() === ns && cnsi !== 0 && cnsi !== 1 ) {
tmp = {};
tmp[ cns ] = [];
ql.optnvlu.push( tmp );
} else {
ql.alss[ ns ] = cns;
}
}
return ql.optnvlu;
},
linkify = function( v, d ) {
v = v.replace( /^Mainspace(?:[ _]{1}talk)?\:/i, "" );
var u = underspace( v, true );
if ( d ) {
u = u.replace( /[\.\%]{1}(2[1-9a-c]{1}|[357][b-e]{1}|[23]f|[46]0|c2[\.\%]{1}a([01]{1}))/gi, function( m, g1, g2 ) {
if ( g2 ) {
return { "0": " ", "1": "¡" }[ g2.toLowerCase() ];
}
return { "21": "!", "22": """, "23": "#", "24": "$", "25": "%", "26": "&", "27": "'", "28": "(", "29": ")", "2a": "*", "2b": "+", "2c": ",", "2f": "/",
"3b": ";", "3c": "<", "3d": "=", "3e": ">", "3f": "?", "5b": "[", "5c": "\", "5d": "]", "5e": "^", "7b": "{", "7c": "|", "7d": "}", "7e": "~",
"40": "@", "60": "`" }[ g1.toLowerCase() ];
} );
}
return '<a href="/wiki/' + mw.util.wikiUrlencode( v ) + '" title="' + u.replace( /\"/g, """ ) + '">' + namespace( u )[ 2 ] + '</a>';
},
quickLinks = function() {
var vlus = ql.optnvlu, o = [], vlu, qls, on, oa, ok,
iterate = function( a ) {
var i = [], v;
for ( v in a ) {
i.push( linkify( a[ v ] ) );
}
return i.join( '</li><li>' );
},
filler = function( a ) {
if ( a.length ) {
return '<li>' + iterate( a ) + '</li>';
}
return "";
},
brynner = function( t, c, f ) {
var u = cE( "ul" );
u.id = underspace( EXT + t );
if ( c ) {
u.setAttribute( "class", c );
}
u.innerHTML = '<li class="' + TITLE + '">' + t + '</li>' + f;
return u.outerHTML;
};
for ( vlu in vlus ) {
qls = vlus[ vlu ];
ok = Object.keys( qls );
on = ok[ 0 ];
oa = qls[ on ];
o.push( brynner( on, !oa.length ? EMPTY : ( ok[ 1 ] ? OPEN : false ), filler( oa ) ) );
}
return o.join( "" );
},
switchSwitch = function( t ) {
var s = eById( SWITCH );
if ( t ) {
toggleBase( s );
} else {
s.classList.toggle( BASE, gotIt() );
}
},
save = function( ss ) {
var uls = nl2a( eByTn( ql.ui, "ul" ) ), tmpoptnvlu = [],
ul, la, tmp, cul,
titleArray = function( a ) {
var l, ta = [];
for ( l in a ) {
ta.push( underspace( eByTn( a[ l ], "a", 0 ).title ) );
}
return ta.sort();
},
showError = function( e ) {
alert( "Something went wrong:\n\n" + e );
};
for ( ul in uls ) {
tmp = {};
cul = uls[ ul ];
la = nl2a( eByTn( cul, "li" ) );
tmp[ la[ 0 ].textContent ] = titleArray( la.slice( 1 ) );
if ( cul.classList.contains( OPEN ) ) {
tmp.open = true;
}
tmpoptnvlu.push( tmp );
}
$.ajax( {
type: "POST",
url: "/w/api.php",
dataType: "json",
data: {
format: "json",
action: "options",
token: mw.user.tokens.values.csrfToken,
optionname: ql.optnnm.global,
optionvalue: JSON.stringify( tmpoptnvlu )
},
success: function( data ) {
if ( !data.error ) {
localStorage[ STORAGE ] = JSON.stringify( QLE.innerHTML );
ql.optnvlu = tmpoptnvlu;
switchSwitch( ss );
} else {
QLE.innerHTML = quickLinks();
showError( data.error.info );
}
},
error: function( something, went, wrong ) {
QLE.innerHTML = quickLinks();
console.error( something );
showError( went + ":\n\n" + wrong );
}
} );
},
addThis = function( v, d ) {
var alias = function( q ) {
return ql.alss[ q ? q.toLowerCase() : q ] || q;
},
li, ul = eById( EXT + underspace( alias( namespace( v )[ 1 ] ) ) );
if ( ul ) {
li = cE( "li" );
li.innerHTML = linkify( v, d );
ul.appendChild( li );
ul.classList.remove( EMPTY );
return li;
}
return false;
},
removeThis = function( t ) {
var tp = t.parentElement, tpp = tp.parentElement;
tpp.removeChild( tp );
tpp.classList.toggle( EMPTY, nl2a( eByTn( tpp, "li" ) ).length < 2 );
},
setListeners = function() {
var prepText = function( txt ) {
return ( /(?:^.*w(?:iki)?\/(?:.+title\=)?)?([^&]+)/ ).exec( txt.trim() )[ 1 ];
},
processText = function( vlu, d ) {
if ( vlu && !gotIt( vlu ) ) {
if ( addThis( underspace( vlu ), d ) ) {
save();
NPT.value = "";
} else if ( !confirm( "Something about that value isn't correct.\nModify it and try again?" ) ) {
NPT.value = "";
}
}
};
ql.ui.addEventListener( "click", evt => {
var trg = evt.target, nn = trg.nodeName.toLowerCase(), ths = gotIt();
if ( nn === "button" ) {
toggleBase();
} else if ( nn === "a" ) {
if ( trg.id === SWITCH ) {
evt.preventDefault();
if ( !ths ) {
addThis( WG_pagename );
} else {
removeThis( ths );
}
save( true );
} else if ( trg.id === VIEW ) {
evt.preventDefault();
toggleBase();
}
} else if ( nn === "li" ) {
if ( !trg.classList.contains( TITLE ) ) {
removeThis( eByTn( trg, "a", 0 ) );
} else {
trg.parentElement.classList.toggle( OPEN );
}
save();
}
}, false );
ql.ui.addEventListener( "dragover", evt => evt.preventDefault() );
ql.ui.addEventListener( "drop", evt => {
evt.preventDefault();
processText( prepText( evt.dataTransfer.getData( "text" ) ), true );
} );
NPT.addEventListener( "paste", evt => {
evt.preventDefault();
processText( prepText( evt.clipboardData.getData( "text" ) ), true );
}, false );
NPT.addEventListener( "change", evt => processText( NPT.value ) );
window.addEventListener( "storage", evt => {
var k = evt.key, nv = evt.newValue;
if ( k && k === STORAGE && nv ) {
QLE.innerHTML = JSON.parse( nv );
switchSwitch();
delete localStorage[ STORAGE ];
}
}, false );
};
ql.optnvlu = JSON.parse( mw.user.options.values[ ql.optnnm.global ] || JSON.stringify( initOptionValue() ) );
$( document ).ready( () => {
ql.ui.id = BASE;
ql.ui.innerHTML = `<span><a id="${SWITCH}" href="#"></a><span><a id="${VIEW}" href="#"></a><div><input type="text" placeholder="Add a new link"><div id="${QL}">${quickLinks()}</div><button>Close</button></div></span></span>`;
const STYLE_SHEET = new CSSStyleSheet();
document.adoptedStyleSheets = [ ...document.adoptedStyleSheets, STYLE_SHEET ];
STYLE_SHEET.replaceSync( `#fg-quick-links-switch {
text-decoration: none;
padding: .5em .2em;
font-size: 1.7em;
background: none;
height: 1.46em;
color: #ffbc41;
width: 1em;
}
#fg-quick-links-switch::before {
content: "☆";
}
#fg-quick-links-switch.fg-quick-links::before {
content: "★";
}
#fg-quick-links-view {
text-decoration: none;
padding: .8em .3em;
background: none;
font-size: 1.1em;
height: 2.3em;
color: unset;
opacity: .5;
width: 2em;
}
#fg-quick-links-view::before {
content: "🔍";
}
#fg-quick-links span > span {
display: inline;
}
#fg-quick-links span > div {
display: none;
position: absolute;
min-width: 300px;
background: #fff;
z-index: 2000;
margin-top: 2.2em;
padding: 1em;
border: 1px solid #a7d7f9;
border-radius: 3px;
box-shadow: 2px 2px 15px -2px rgba(0, 0, 0, 0.5);
}
#fg-quick-links-ql {
max-height: calc( 80vh - 13em );
padding-right: 2em;
overflow: auto;
overflow-x: hidden;
overscroll-behavior: contain;
}
#fg-quick-links-ql ul {
float: none !important;
background: none;
}
#fg-quick-links-ql ul.fg-quick-links-empty {
display: none;
}
#fg-quick-links-ql li {
float: none !important;
height: auto;
background: none;
}
#fg-quick-links-ql li.fg-quick-links-title {
font-weight: bold;
color: #666;
cursor: pointer;
}
#fg-quick-links-ql li:not( .fg-quick-links-title ) {
display: none;
margin-left: 1.3em;
}
#fg-quick-links-ql li a {
padding: 0;
float: none;
height: auto;
display: block;
margin-left: 1.3em;
background-image: none;
}
#fg-quick-links-ql ul li.fg-quick-links-title::before {
content: "► ";
float: left;
color: #aaa;
}
#fg-quick-links-ql ul.fg-quick-links-open li.fg-quick-links-title::before {
content: "▼ ";
}
#fg-quick-links-ql li:not( [class=fg-quick-links-title] )::before {
content: "x";
float: left;
color: #fff;
background: rgba( 255, 0, 0, 0.5 );
border-radius: 100%;
padding: 1px 3px;
font-size: 10px;
line-height: 10px;
margin-top: 2px;
cursor: pointer;
}
#fg-quick-links input {
margin-bottom: 1em;
width: calc( 100% - 2em - 2px );
padding: 0.5em 1em 0.6em;
border: 1px solid #aaa;
border-radius: 3px;
}
#fg-quick-links button {
display: none;
margin-top: 1em;
}
#p-views,
#fg-quick-links span:hover > div,
#fg-quick-links.fg-quick-links button,
#fg-quick-links.fg-quick-links span > div,
#fg-quick-links-ql ul.fg-quick-links-open li {
display: block;
}` );
eByTn( eById( "p-views" ), "ul", 0 ).append( ql.ui );
NPT = eByTn( ql.ui, "input", 0 );
QLE = eById( QL );
switchSwitch();
setListeners();
}, { once: true } );
} () );