MediaWiki:Gadget-SpecImporter.js
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.
/**
* RTC Wiki Spec Sheet Importer
*
* Gadget that searches EveryMac.com (via server-side proxy),
* fetches specs for a given Mac model, and pre-fills the
* Infobox Generator form or directly generates infobox wikitext.
*
* Requires: SpecImporter.php deployed on the wiki server.
* Works with: InfoboxGenerator gadget (fills its form fields).
*/
( function ( mw, $ ) {
'use strict';
var API_BASE = '/rtc-tools/SpecImporter.php';
// ============================================================
// Known EveryMac URL patterns for quick lookup
// ============================================================
var FAMILY_PATTERNS = {
// Compact Macs
'Macintosh 128K': '/systems/apple/mac_128k/specs/mac_128k.html',
'Macintosh 512K': '/systems/apple/mac_512k/specs/mac_512k.html',
'Macintosh 512Ke': '/systems/apple/mac_512ke/specs/mac_512ke.html',
'Macintosh Plus': '/systems/apple/mac_plus/specs/mac_plus.html',
'Macintosh SE': '/systems/apple/mac_se/specs/mac_se.html',
'Macintosh SE/30': '/systems/apple/mac_se/specs/mac_se30.html',
'Macintosh Classic': '/systems/apple/mac_classic/specs/mac_classic.html',
'Macintosh Classic II': '/systems/apple/mac_classic/specs/mac_classic_ii.html',
'Macintosh Color Classic': '/systems/apple/mac_color_classic/specs/mac_color_classic.html',
// Mac II series
'Macintosh II': '/systems/apple/mac_ii/specs/mac_ii.html',
'Macintosh IIx': '/systems/apple/mac_ii/specs/mac_iix.html',
'Macintosh IIcx': '/systems/apple/mac_ii/specs/mac_iicx.html',
'Macintosh IIci': '/systems/apple/mac_ii/specs/mac_iici.html',
'Macintosh IIsi': '/systems/apple/mac_ii/specs/mac_iisi.html',
'Macintosh IIfx': '/systems/apple/mac_ii/specs/mac_iifx.html',
'Macintosh IIvi': '/systems/apple/mac_ii/specs/mac_iivi.html',
'Macintosh IIvx': '/systems/apple/mac_ii/specs/mac_iivx.html'
};
// ============================================================
// Search EveryMac via proxy
// ============================================================
function searchModels( query ) {
return $.ajax( {
url: API_BASE,
data: { action: 'search', q: query },
dataType: 'json',
timeout: 15000
} );
}
// ============================================================
// Fetch specs from EveryMac via proxy
// ============================================================
function fetchSpecs( urlPath ) {
return $.ajax( {
url: API_BASE,
data: { action: 'fetch', url: urlPath },
dataType: 'json',
timeout: 15000
} );
}
// ============================================================
// Build the Spec Importer UI
// ============================================================
function buildUI( $container ) {
$container.empty();
var $wrapper = $( '<div>' )
.attr( 'id', 'rtc-spec-importer' )
.css( {
'max-width': '900px',
'margin': '0 auto',
'font-family': 'sans-serif'
} );
$wrapper.append(
$( '<h2>' ).text( 'Spec Sheet Importer' ),
$( '<p>' ).css( 'color', '#555' ).text(
'Search for a Mac model on EveryMac.com, import its specs, and generate infobox wikitext.'
)
);
// Search bar
var $searchRow = $( '<div>' ).css( {
'display': 'flex',
'gap': '10px',
'margin-bottom': '20px'
} );
var $searchInput = $( '<input>' )
.attr( {
type: 'text',
id: 'rtc-spec-search-input',
placeholder: 'e.g. Macintosh IIsi, LC 475, Quadra 700...'
} )
.css( {
'flex': '1',
'padding': '10px 14px',
'font-size': '15px',
'border': '1px solid #ccc',
'border-radius': '4px'
} );
var $searchBtn = $( '<button>' )
.text( 'Search EveryMac' )
.attr( 'id', 'rtc-spec-search-btn' )
.css( {
'padding': '10px 24px',
'font-size': '14px',
'font-weight': 'bold',
'background-color': '#3366cc',
'color': '#fff',
'border': 'none',
'border-radius': '4px',
'cursor': 'pointer'
} );
$searchRow.append( $searchInput, $searchBtn );
$wrapper.append( $searchRow );
// Quick-select for known models
var $quickSelect = $( '<div>' ).css( { 'margin-bottom': '20px' } );
$quickSelect.append(
$( '<label>' ).css( 'font-weight', 'bold' ).text( 'Quick select: ' ),
$( '<select>' )
.attr( 'id', 'rtc-spec-quick-select' )
.css( { 'padding': '6px', 'font-size': '14px', 'margin-left': '8px' } )
);
var $qSelect = $quickSelect.find( 'select' );
$qSelect.append( $( '<option>' ).val( '' ).text( '— Choose a known model —' ) );
Object.keys( FAMILY_PATTERNS ).sort().forEach( function ( name ) {
$qSelect.append( $( '<option>' ).val( FAMILY_PATTERNS[ name ] ).text( name ) );
} );
$wrapper.append( $quickSelect );
// Status / loading
var $status = $( '<div>' )
.attr( 'id', 'rtc-spec-status' )
.css( { 'margin-bottom': '15px', 'color': '#666', 'display': 'none' } );
$wrapper.append( $status );
// Search results
var $results = $( '<div>' )
.attr( 'id', 'rtc-spec-results' )
.css( { 'display': 'none', 'margin-bottom': '20px' } );
$wrapper.append( $results );
// Spec display area
var $specDisplay = $( '<div>' )
.attr( 'id', 'rtc-spec-display' )
.css( { 'display': 'none' } );
$wrapper.append( $specDisplay );
$container.append( $wrapper );
// ========== Events ==========
// Search
$searchBtn.on( 'click', function () {
var query = $searchInput.val().trim();
if ( !query ) {
return;
}
// Check if it's a known model first
if ( FAMILY_PATTERNS[ query ] ) {
doFetch( FAMILY_PATTERNS[ query ] );
return;
}
showStatus( 'Searching EveryMac for "' + query + '"...' );
$results.hide();
$specDisplay.hide();
searchModels( query ).done( function ( data ) {
if ( data.results && data.results.length > 0 ) {
showResults( data.results );
} else {
showStatus( 'No results found. Try a different search term.' );
}
} ).fail( function () {
showStatus( 'Search failed. You can try entering an EveryMac URL directly.' );
} );
} );
// Enter key to search
$searchInput.on( 'keypress', function ( e ) {
if ( e.which === 13 ) {
$searchBtn.trigger( 'click' );
}
} );
// Quick select
$qSelect.on( 'change', function () {
var url = $( this ).val();
if ( url ) {
doFetch( url );
}
} );
}
// ============================================================
// Show status message
// ============================================================
function showStatus( msg ) {
$( '#rtc-spec-status' ).text( msg ).show();
}
// ============================================================
// Show search results
// ============================================================
function showResults( results ) {
var $results = $( '#rtc-spec-results' ).empty().show();
$( '#rtc-spec-status' ).hide();
$results.append( $( '<h3>' ).text( 'Search Results (' + results.length + ')' ) );
var $list = $( '<ul>' ).css( { 'list-style': 'none', 'padding': '0' } );
results.forEach( function ( r ) {
var $li = $( '<li>' ).css( { 'margin-bottom': '8px' } );
var $link = $( '<a>' )
.text( r.name )
.attr( 'href', '#' )
.css( { 'color': '#3366cc', 'text-decoration': 'none', 'font-size': '14px' } )
.on( 'click', function ( e ) {
e.preventDefault();
doFetch( r.url );
} );
$li.append( $link );
$li.append( $( '<span>' ).css( 'color', '#888' ).text( ' — ' + r.url ) );
$list.append( $li );
} );
$results.append( $list );
}
// ============================================================
// Fetch and display specs
// ============================================================
function doFetch( urlPath ) {
showStatus( 'Fetching specs from EveryMac...' );
$( '#rtc-spec-results' ).hide();
$( '#rtc-spec-display' ).hide();
fetchSpecs( urlPath ).done( function ( data ) {
if ( data.success ) {
showSpecs( data );
} else {
showStatus( 'Error: ' + ( data.error || 'Unknown error' ) );
}
} ).fail( function ( xhr ) {
var err = 'Fetch failed.';
try {
err = JSON.parse( xhr.responseText ).error || err;
} catch ( e ) {}
showStatus( err );
} );
}
// ============================================================
// Display fetched specs with edit capability
// ============================================================
function showSpecs( data ) {
$( '#rtc-spec-status' ).hide();
var $display = $( '#rtc-spec-display' ).empty().show();
var specs = data.raw_specs || {};
var infobox = data.infobox_fields || {};
// Header
$display.append(
$( '<h3>' ).text( 'Imported Specs' ),
$( '<p>' ).html(
'<strong>Source:</strong> <a href="' + data.source_url +
'" target="_blank">' + data.source_url + '</a>'
)
);
// Two columns: raw specs + infobox fields
var $columns = $( '<div>' ).css( {
'display': 'flex',
'gap': '30px',
'flex-wrap': 'wrap'
} );
// Left: Editable infobox fields
var $left = $( '<div>' ).css( { 'flex': '1', 'min-width': '350px' } );
$left.append( $( '<h4>' ).text( 'Infobox Fields (editable)' ) );
var infoboxFields = [
{ name: 'name', label: 'Model Name' },
{ name: 'manufacturer', label: 'Manufacturer' },
{ name: 'type', label: 'Type' },
{ name: 'release_date', label: 'Release Date' },
{ name: 'discontinued', label: 'Discontinued' },
{ name: 'price', label: 'Price' },
{ name: 'cpu', label: 'CPU' },
{ name: 'memory', label: 'Memory' },
{ name: 'storage', label: 'Storage' },
{ name: 'display', label: 'Display' },
{ name: 'sound', label: 'Sound' },
{ name: 'dimensions', label: 'Dimensions' },
{ name: 'weight', label: 'Weight' },
{ name: 'os', label: 'OS' },
{ name: 'model', label: 'Model Number' },
{ name: 'codename', label: 'Codename' },
{ name: 'predecessor', label: 'Predecessor' },
{ name: 'successor', label: 'Successor' },
{ name: 'image', label: 'Image filename' }
];
infoboxFields.forEach( function ( field ) {
var $row = $( '<div>' ).css( {
'margin-bottom': '8px',
'display': 'flex',
'align-items': 'center'
} );
$row.append(
$( '<label>' ).text( field.label ).css( {
'width': '130px',
'flex-shrink': '0',
'font-size': '13px'
} ),
$( '<input>' )
.attr( { type: 'text', 'data-infobox': field.name } )
.val( infobox[ field.name ] || '' )
.css( {
'flex': '1',
'padding': '5px 8px',
'font-size': '13px',
'border': '1px solid #ccc',
'border-radius': '3px'
} )
);
$left.append( $row );
} );
$columns.append( $left );
// Right: Raw specs reference
var $right = $( '<div>' ).css( {
'flex': '1',
'min-width': '300px',
'max-height': '600px',
'overflow-y': 'auto'
} );
$right.append( $( '<h4>' ).text( 'All EveryMac Specs (reference)' ) );
var $specTable = $( '<table>' ).css( {
'width': '100%',
'border-collapse': 'collapse',
'font-size': '12px'
} );
Object.keys( specs ).forEach( function ( key ) {
if ( key.charAt( 0 ) === '_' ) { return; }
var $tr = $( '<tr>' );
$tr.append(
$( '<td>' ).text( key ).css( {
'padding': '3px 6px',
'border-bottom': '1px solid #eee',
'font-weight': 'bold',
'white-space': 'nowrap',
'vertical-align': 'top'
} ),
$( '<td>' ).text( specs[ key ] ).css( {
'padding': '3px 6px',
'border-bottom': '1px solid #eee'
} )
);
$specTable.append( $tr );
} );
$right.append( $specTable );
$columns.append( $right );
$display.append( $columns );
// Buttons
var $buttons = $( '<div>' ).css( { 'margin-top': '20px' } );
var $genBtn = $( '<button>' )
.text( 'Generate Infobox Wikitext' )
.css( {
'padding': '10px 24px',
'font-size': '14px',
'font-weight': 'bold',
'background-color': '#3366cc',
'color': '#fff',
'border': 'none',
'border-radius': '4px',
'cursor': 'pointer',
'margin-right': '10px'
} );
var $fillBtn = $( '<button>' )
.text( 'Fill Infobox Generator Form' )
.css( {
'padding': '10px 24px',
'font-size': '14px',
'background-color': '#2d8e36',
'color': '#fff',
'border': 'none',
'border-radius': '4px',
'cursor': 'pointer',
'margin-right': '10px'
} );
var $insertBtn = $( '<button>' )
.text( 'Insert into Editor' )
.css( {
'padding': '10px 24px',
'font-size': '14px',
'background-color': '#6b3fa0',
'color': '#fff',
'border': 'none',
'border-radius': '4px',
'cursor': 'pointer'
} );
$buttons.append( $genBtn, $fillBtn, $insertBtn );
$display.append( $buttons );
// Output
var $output = $( '<div>' ).attr( 'id', 'rtc-spec-output' ).css( {
'margin-top': '15px',
'display': 'none'
} );
$output.append(
$( '<h4>' ).text( 'Generated Wikitext' ),
$( '<textarea>' )
.attr( { 'id': 'rtc-spec-wikitext', 'rows': 20, 'readonly': true } )
.css( {
'width': '100%',
'font-family': 'monospace',
'font-size': '13px',
'padding': '10px',
'background': '#f5f5f5',
'border': '1px solid #ccc',
'border-radius': '4px',
'box-sizing': 'border-box'
} )
);
$display.append( $output );
// Events
$genBtn.on( 'click', function () {
var values = collectEditedValues();
var wikitext = buildInfoboxWikitext( values );
$( '#rtc-spec-wikitext' ).val( wikitext );
$output.show();
navigator.clipboard.writeText( wikitext ).then( function () {
mw.notify( 'Infobox wikitext copied to clipboard!', { type: 'success' } );
} );
} );
$fillBtn.on( 'click', function () {
// Navigate to the Infobox Generator page with pre-filled data
var values = collectEditedValues();
var params = new URLSearchParams();
params.set( 'prefill', JSON.stringify( values ) );
window.location.href = mw.util.getUrl( 'Special:BlankPage/InfoboxGenerator' ) + '?' + params.toString();
} );
$insertBtn.on( 'click', function () {
var values = collectEditedValues();
var wikitext = buildInfoboxWikitext( values );
var $textarea = $( '#wpTextbox1' );
if ( $textarea.length ) {
$textarea.val( wikitext + '\n\n' + $textarea.val() );
$textarea.trigger( 'change' );
mw.notify( 'Infobox inserted at the top of the article.', { type: 'success' } );
} else {
navigator.clipboard.writeText( wikitext ).then( function () {
mw.notify( 'Copied to clipboard. Open an article editor and paste.', { type: 'success' } );
} );
}
} );
}
// ============================================================
// Collect edited values from the import form
// ============================================================
function collectEditedValues() {
var values = {};
$( '#rtc-spec-display input[data-infobox]' ).each( function () {
var name = $( this ).attr( 'data-infobox' );
var val = $( this ).val().trim();
if ( val ) {
values[ name ] = val;
}
} );
return values;
}
// ============================================================
// Build infobox wikitext from field values
// ============================================================
function buildInfoboxWikitext( values ) {
var lines = [ '{{Infobox computer' ];
var fieldOrder = [
'name', 'image', 'caption', 'logo', 'developer', 'manufacturer', 'type',
'release_date', 'discontinued', 'price', 'units_sold',
'cpu', 'memory', 'storage', 'display', 'sound',
'dimensions', 'weight', 'os',
'predecessor', 'successor', 'codename', 'model'
];
fieldOrder.forEach( function ( key ) {
var val = values[ key ];
if ( !val ) { return; }
// Format image
if ( key === 'image' ) {
if ( val.indexOf( '[[' ) === -1 ) {
val = '[[File:' + val + '|250px]]';
}
}
// Format predecessor/successor as links
if ( ( key === 'predecessor' || key === 'successor' ) && val.indexOf( '[[' ) === -1 ) {
val = '[[' + val + ']]';
}
var paramName = ( key === 'release_date' ) ? 'release date' : key;
var pad = ' '.substring( 0, Math.max( 1, 14 - paramName.length ) );
lines.push( '| ' + paramName + pad + '= ' + val );
} );
lines.push( '}}' );
return lines.join( '\n' );
}
// ============================================================
// Special page init
// ============================================================
function initSpecialPage() {
if (
mw.config.get( 'wgCanonicalSpecialPageName' ) === 'Blankpage' &&
window.location.href.indexOf( 'SpecImporter' ) !== -1
) {
var $content = $( '#mw-content-text' );
$content.empty();
document.title = 'Spec Sheet Importer – ' + mw.config.get( 'wgSiteName' );
$( '#firstHeading' ).text( 'Spec Sheet Importer' );
buildUI( $content );
}
}
// ============================================================
// Sidebar link
// ============================================================
function addSidebarLink() {
mw.util.addPortletLink(
'p-tb',
mw.util.getUrl( 'Special:BlankPage/SpecImporter' ),
'Spec Importer',
't-spec-importer',
'Import specs from EveryMac.com'
);
}
// ============================================================
// Init
// ============================================================
$( function () {
addSidebarLink();
initSpecialPage();
} );
}( mediaWiki, jQuery ) );