MediaWiki:Gadget-tagautocomplete.js: Difference between revisions
MediaWiki interface page
More actions
Created page with "β============================================================================= Gadget-tagautocomplete.js Adds tokenized tag input with autocomplete on Submit Project / Submit Tutorial forms. Pulls existing tags from the Projects and Tutorials Cargo tables. =============================================================================: ( function () { var FORM_PAGES = [ 'Form:Submit_Project', 'Form:Submit_Tutorial' ]; if ( FORM_PAGES.indexOf( mw.co..." Β |
No edit summary |
||
| Line 6: | Line 6: | ||
( function () { | ( function () { | ||
Β Β Β var | Β Β Β var pageName = mw.config.get( 'wgPageName' ) || ''; | ||
Β Β Β if ( | Β Β Β if ( pageName.indexOf( 'Special:FormEdit' ) === -1 && | ||
Β Β Β Β pageName.indexOf( 'Special:RunQuery' ) === -1 ) { return; } | |||
Β Β Β $( function () { | Β Β Β $( function () { | ||
Revision as of 09:11, 5 April 2026
/* =============================================================================
Gadget-tagautocomplete.js
Adds tokenized tag input with autocomplete on Submit Project / Submit Tutorial
forms. Pulls existing tags from the Projects and Tutorials Cargo tables.
============================================================================= */
( function () {
var pageName = mw.config.get( 'wgPageName' ) || '';
if ( pageName.indexOf( 'Special:FormEdit' ) === -1 &&
pageName.indexOf( 'Special:RunQuery' ) === -1 ) { return; }
$( function () {
// ββ Find the tags input ββββββββββββββββββββββββββββββββββββββββββββββ
// Page Forms renders inputs near their label; match by placeholder text.
var $raw = $( 'input[type="text"]' ).filter( function () {
var ph = ( $( this ).attr( 'placeholder' ) || '' ).toLowerCase();
return ph.indexOf( 'tag' ) !== -1;
} );
if ( !$raw.length ) { return; }
// ββ Load all existing tags from Cargo ββββββββββββββββββββββββββββββββ
var allTags = [];
function fetchTags( table, field ) {
return $.getJSON( mw.util.wikiScript( 'api' ), {
action : 'cargoquery',
tables : table,
fields : field,
limit : 500,
format : 'json'
} ).then( function ( data ) {
( data.cargoquery || [] ).forEach( function ( r ) {
( r.title[ field ] || '' ).split( ',' ).forEach( function ( t ) {
t = t.trim().toLowerCase();
if ( t && allTags.indexOf( t ) === -1 ) {
allTags.push( t );
}
} );
} );
} );
}
$.when(
fetchTags( 'Projects', 'tags' ),
fetchTags( 'Tutorials', 'tags' )
).then( function () {
allTags.sort();
$raw.each( function () {
initTokenizer( $( this ) );
} );
} );
// ββ Tokenizer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
function initTokenizer( $input ) {
// Build wrapper to sit alongside the original (now hidden) input
$input.hide();
var $wrap = $( '<div>' ).addClass( 'tag-tokenizer' ).insertAfter( $input );
var $box = $( '<div>' ).addClass( 'tag-tokenizer-box' ).appendTo( $wrap );
var $entry = $( '<input>' ).attr( {
type : 'text',
placeholder : 'Type a tag and press Enter or comma\u2026',
autocomplete: 'off'
} ).addClass( 'tag-tokenizer-entry' ).appendTo( $box );
var $dropdown = $( '<ul>' ).addClass( 'tag-tokenizer-dropdown' ).hide().appendTo( $wrap );
// Seed from any value already in the hidden input (edit mode)
var existing = ( $input.val() || '' ).split( ',' ).map( function ( t ) { return t.trim(); } ).filter( Boolean );
existing.forEach( function ( tag ) { addToken( tag ); } );
syncHidden();
// ββ Token rendering ββββββββββββββββββββββββββββββββββββββββββββββ
function addToken( tag ) {
tag = tag.trim().toLowerCase().replace( /\s+/g, '-' );
if ( !tag ) { return; }
// Prevent duplicates
if ( $box.find( '.tag-token' ).filter( function () {
return $( this ).data( 'tag' ) === tag;
} ).length ) { return; }
var $token = $( '<span>' ).addClass( 'tag-token' ).text( tag ).attr( 'data-tag', tag );
var $x = $( '<button>' ).attr( 'type', 'button' ).addClass( 'tag-token-remove' ).text( '\u00d7' );
$token.append( $x );
$x.on( 'click', function () {
$token.remove();
syncHidden();
} );
$token.insertBefore( $entry );
syncHidden();
}
function syncHidden() {
var tags = [];
$box.find( '.tag-token' ).each( function () {
tags.push( $( this ).data( 'tag' ) );
} );
$input.val( tags.join( ', ' ) );
}
// ββ Autocomplete dropdown ββββββββββββββββββββββββββββββββββββββββ
function showDropdown( query ) {
var existing = [];
$box.find( '.tag-token' ).each( function () {
existing.push( $( this ).data( 'tag' ) );
} );
var matches = allTags.filter( function ( t ) {
return t.indexOf( query ) === 0 && existing.indexOf( t ) === -1;
} ).slice( 0, 8 );
$dropdown.empty();
if ( !matches.length ) { $dropdown.hide(); return; }
matches.forEach( function ( tag ) {
$( '<li>' ).text( tag ).on( 'mousedown', function ( e ) {
e.preventDefault(); // keep focus in $entry
addToken( tag );
$entry.val( '' );
$dropdown.hide();
} ).appendTo( $dropdown );
} );
$dropdown.show();
}
// ββ Events βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
$entry.on( 'input', function () {
var val = $entry.val();
// Comma or space after a word = commit tag
if ( /[,]$/.test( val ) ) {
addToken( val.replace( /,$/, '' ) );
$entry.val( '' );
$dropdown.hide();
return;
}
var q = val.trim().toLowerCase();
if ( q.length >= 1 ) { showDropdown( q ); } else { $dropdown.hide(); }
} );
$entry.on( 'keydown', function ( e ) {
// Enter = commit current text or first suggestion
if ( e.key === 'Enter' ) {
e.preventDefault();
var first = $dropdown.find( 'li' ).first().text();
addToken( first || $entry.val() );
$entry.val( '' );
$dropdown.hide();
}
// Backspace on empty entry = remove last token
if ( e.key === 'Backspace' && $entry.val() === '' ) {
$box.find( '.tag-token' ).last().remove();
syncHidden();
}
} );
$entry.on( 'blur', function () {
setTimeout( function () { $dropdown.hide(); }, 150 );
// Commit anything partially typed on blur
var val = $entry.val().trim();
if ( val ) { addToken( val ); $entry.val( '' ); }
} );
$box.on( 'click', function () { $entry.trigger( 'focus' ); } );
}
} );
}() );