MediaWiki:ResourcePage.js
From Dune Awakening DB
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.
/**
* ResourcePage.js - ResourceLoader Module
* This file should be created as MediaWiki:ResourcePage.js
*
* Dependencies: jquery, mediawiki.api, mediawiki.util
*/
( function ( $, mw ) {
'use strict';
/**
* Resource Page Enhancement Module
* Provides interactive features for resource pages including
* search functionality and popup displays for recipes
*/
var ResourcePage = {
/**
* Configuration
*/
config: {
selectors: {
craftedWithSearch: '#craftedWithSearch',
craftedFromSearch: '#craftedFromSearch',
craftedWithTable: '#craftedWithTable',
craftedFromTable: '#craftedFromTable',
viewAllCraftedWith: '.view-all-crafted-with',
viewAllCraftedFrom: '.view-all-crafted-from',
popupOverlay: '.recipe-popup-overlay',
popupClose: '.popup-close',
popupSearchInput: '.popup-search-input',
popupTable: '.recipe-popup-table'
},
api: new mw.Api(),
animationDuration: 300
},
/**
* Initialize the module
*/
init: function () {
// Only initialize if we're on a resource page
if ( !this.isResourcePage() ) {
return;
}
mw.log( 'ResourcePage: Initializing' );
this.bindEvents();
this.initializeSearch();
// Fire a custom event to signal initialization
mw.hook( 'resourcePage.initialized' ).fire();
},
/**
* Check if current page is a resource page
* @return {boolean}
*/
isResourcePage: function () {
return $( '.crafting-section' ).length > 0 ||
$( '[class*="crafted-with"], [class*="crafted-from"]' ).length > 0;
},
/**
* Bind event handlers
*/
bindEvents: function () {
var self = this;
// View All buttons
$( document )
.on( 'click', this.config.selectors.viewAllCraftedWith, function ( e ) {
e.preventDefault();
var resourceName = $( this ).data( 'resource' );
self.showCraftedWithPopup( resourceName );
} )
.on( 'click', this.config.selectors.viewAllCraftedFrom, function ( e ) {
e.preventDefault();
var resourceName = $( this ).data( 'resource' );
self.showCraftedFromPopup( resourceName );
} );
// Popup interactions
$( document )
.on( 'click', this.config.selectors.popupClose + ', ' + this.config.selectors.popupOverlay, function ( e ) {
if ( e.target === this || $( e.target ).hasClass( 'popup-close' ) ) {
self.closePopup();
}
} )
.on( 'input', this.config.selectors.popupSearchInput, function () {
var searchTerm = $( this ).val().toLowerCase();
self.filterPopupTable( searchTerm );
} );
// Keyboard shortcuts
$( document ).on( 'keydown', function ( e ) {
if ( e.key === 'Escape' ) {
self.closePopup();
}
} );
},
/**
* Initialize search functionality for preview tables
*/
initializeSearch: function () {
var self = this;
// Crafted With search
$( this.config.selectors.craftedWithSearch ).on( 'input', function () {
var searchTerm = $( this ).val().toLowerCase();
self.filterTable( self.config.selectors.craftedWithTable, searchTerm );
} );
// Crafted From search
$( this.config.selectors.craftedFromSearch ).on( 'input', function () {
var searchTerm = $( this ).val().toLowerCase();
self.filterTable( self.config.selectors.craftedFromTable, searchTerm );
} );
},
/**
* Filter table rows based on search term
* @param {string} tableSelector - Table selector
* @param {string} searchTerm - Search term
*/
filterTable: function ( tableSelector, searchTerm ) {
var $table = $( tableSelector ),
$rows = $table.find( 'tbody tr' );
if ( !searchTerm ) {
$rows.removeClass( 'hidden' ).show();
return;
}
$rows.each( function () {
var $row = $( this ),
text = $row.text().toLowerCase();
if ( text.indexOf( searchTerm ) > -1 ) {
$row.removeClass( 'hidden' ).show();
} else {
$row.addClass( 'hidden' ).hide();
}
} );
},
/**
* Show popup with recipes that use this resource
* @param {string} resourceName - Name of the resource
*/
showCraftedWithPopup: function ( resourceName ) {
var self = this;
this.showLoadingPopup( 'Loading recipes that use ' + resourceName + '...' );
this.config.api.parse(
'{{ResourceCraftedWith|resource=' + resourceName + '|full=yes}}',
{
contentmodel: 'wikitext',
disablelimitreport: true,
wrapoutputclass: ''
}
).done( function ( html ) {
self.displayRecipePopup(
'Recipes Using ' + resourceName,
html
);
} ).fail( function () {
self.showErrorPopup( 'Failed to load recipe data' );
} );
},
/**
* Show popup with recipes that produce this resource
* @param {string} resourceName - Name of the resource
*/
showCraftedFromPopup: function ( resourceName ) {
var self = this;
this.showLoadingPopup( 'Loading recipes that produce ' + resourceName + '...' );
this.config.api.parse(
'{{ResourceCraftedFrom|resource=' + resourceName + '|full=yes}}',
{
contentmodel: 'wikitext',
disablelimitreport: true,
wrapoutputclass: ''
}
).done( function ( html ) {
self.displayRecipePopup(
'Recipes Producing ' + resourceName,
html
);
} ).fail( function () {
self.showErrorPopup( 'Failed to load recipe data' );
} );
},
/**
* Show loading popup
* @param {string} message - Loading message
*/
showLoadingPopup: function ( message ) {
var $popup = $( '<div>' ).addClass( 'recipe-popup-overlay active' ).append(
$( '<div>' ).addClass( 'recipe-popup loading' ).append(
$( '<div>' ).addClass( 'popup-content' ).css( {
'text-align': 'center',
'padding': '50px'
} ).append(
$( '<div>' ).addClass( 'tech-loader' ),
$( '<p>' ).css( {
'margin-top': '20px',
'color': '#fce7c8'
} ).text( message )
)
)
);
$( 'body' ).append( $popup );
},
/**
* Show error popup
* @param {string} message - Error message
*/
showErrorPopup: function ( message ) {
$( this.config.selectors.popupOverlay ).remove();
var $popup = $( '<div>' ).addClass( 'recipe-popup-overlay active' ).append(
$( '<div>' ).addClass( 'recipe-popup error' ).append(
$( '<div>' ).addClass( 'popup-header' ).append(
$( '<h3>' ).addClass( 'popup-title' ).text( 'Error' ),
$( '<button>' ).addClass( 'popup-close' ).html( '×' )
),
$( '<div>' ).addClass( 'popup-content' ).css( {
'text-align': 'center',
'padding': '50px'
} ).append(
$( '<p>' ).css( 'color', '#ff6b6b' ).text( message )
)
)
);
$( 'body' ).append( $popup );
},
/**
* Display recipe popup with content
* @param {string} title - Popup title
* @param {string} content - HTML content
*/
displayRecipePopup: function ( title, content ) {
var self = this;
// Remove any existing popups
$( this.config.selectors.popupOverlay ).remove();
// Create popup structure
var $popup = $( '<div>' ).addClass( 'recipe-popup-overlay' ).append(
$( '<div>' ).addClass( 'recipe-popup' ).append(
$( '<div>' ).addClass( 'popup-header' ).append(
$( '<h3>' ).addClass( 'popup-title' ).text( title ),
$( '<button>' ).addClass( 'popup-close' ).html( '×' )
),
$( '<div>' ).addClass( 'popup-content' ).append(
$( '<div>' ).addClass( 'popup-search-container' ).append(
$( '<input>' )
.attr( 'type', 'text' )
.addClass( 'popup-search-input' )
.attr( 'placeholder', 'Search recipes...' )
),
$( '<div>' ).addClass( 'popup-table-wrapper' ).html( content )
)
)
);
$( 'body' ).append( $popup );
// Activate popup with animation
setTimeout( function () {
$popup.addClass( 'active' );
// Focus search input
$popup.find( '.popup-search-input' ).focus();
}, 10 );
},
/**
* Filter popup table based on search
* @param {string} searchTerm - Search term
*/
filterPopupTable: function ( searchTerm ) {
var $table = $( this.config.selectors.popupTable ),
$rows = $table.find( 'tbody tr' ),
$noResults = $table.find( '.no-results' );
// Remove any existing no-results message
$noResults.remove();
if ( !searchTerm ) {
$rows.show();
return;
}
var visibleCount = 0;
$rows.each( function () {
var $row = $( this ),
text = $row.text().toLowerCase();
if ( text.indexOf( searchTerm ) > -1 ) {
$row.show();
visibleCount++;
} else {
$row.hide();
}
} );
// Show no results message
if ( visibleCount === 0 ) {
$table.find( 'tbody' ).append(
$( '<tr>' ).addClass( 'no-results' ).append(
$( '<td>' )
.attr( 'colspan', '6' )
.css( {
'text-align': 'center',
'color': '#666',
'padding': '20px'
} )
.text( 'No recipes found matching "' + searchTerm + '"' )
)
);
}
},
/**
* Close popup
*/
closePopup: function () {
var self = this,
$popup = $( this.config.selectors.popupOverlay );
$popup.removeClass( 'active' );
setTimeout( function () {
$popup.remove();
}, self.config.animationDuration );
}
};
// Initialize when DOM is ready
$( function () {
// Use mw.hook to allow other scripts to know when we're ready
mw.hook( 'wikipage.content' ).add( function () {
ResourcePage.init();
} );
} );
// Export for debugging
mw.duneResourcePage = ResourcePage;
}( jQuery, mediaWiki ) );