Gadget-ResourcePage.js: Difference between revisions
From Dune Awakening DB
Created page with "→* * ResourcePage Gadget * Handles recipe popups and search functionality for resource pages * * @requires jquery * @requires mediawiki.api * @requires mediawiki.util: (function($, mw) { 'use strict'; // Only run on pages with resource page elements if (!$('.crafting-section, .recipe-table').length) { return; } // Cache for storing recipe data var recipeCache = { craftedWith: null, craftedFrom: null..." |
mNo edit summary |
||
| Line 1: | Line 1: | ||
/** | /** | ||
* ResourcePage Gadget | * ResourcePage Gadget - Complete Fixed Version | ||
* Handles recipe popups and search functionality for resource pages | * Handles recipe popups and search functionality for resource pages | ||
* | * | ||
| Line 15: | Line 15: | ||
return; | return; | ||
} | } | ||
console.log('[ResourcePage] Gadget loading...'); | |||
// Cache for storing recipe data | // Cache for storing recipe data | ||
| Line 24: | Line 26: | ||
// Initialize module | // Initialize module | ||
function init() { | function init() { | ||
console.log('[ResourcePage] Initializing...'); | |||
// Add required styles | |||
injectRequiredStyles(); | |||
// Add table search functionality | // Add table search functionality | ||
initTableSearch(); | initTableSearch(); | ||
// Add popup handlers | // Add popup handlers - FIXED VERSION | ||
initPopupHandlers(); | initPopupHandlers(); | ||
// Format tables | // Format tables | ||
formatTables(); | formatTables(); | ||
} | |||
// Inject required CSS | |||
function injectRequiredStyles() { | |||
if ($('#resourcePageGadgetStyles').length === 0) { | |||
var styles = `<style id="resourcePageGadgetStyles"> | |||
/* Span button styles */ | |||
.dune-action-button { | |||
display: inline-block !important; | |||
cursor: pointer !important; | |||
user-select: none !important; | |||
-webkit-user-select: none !important; | |||
-moz-user-select: none !important; | |||
-ms-user-select: none !important; | |||
} | |||
/* Loading spinner */ | |||
.loading-spinner { | |||
font-size: 48px; | |||
animation: spin 1s linear infinite; | |||
} | |||
@keyframes spin { | |||
from { transform: rotate(0deg); } | |||
to { transform: rotate(360deg); } | |||
} | |||
/* Sort indicators */ | |||
.recipe-popup-table th { | |||
cursor: pointer; | |||
user-select: none; | |||
} | |||
.recipe-popup-table th.sort-asc::after { | |||
content: " ↑"; | |||
color: #fce7c8; | |||
} | |||
.recipe-popup-table th.sort-desc::after { | |||
content: " ↓"; | |||
color: #fce7c8; | |||
} | |||
/* Popup note */ | |||
.popup-note { | |||
background: rgba(252,231,200,.1); | |||
border: 1px solid rgba(252,231,200,.3); | |||
padding: 10px; | |||
margin-bottom: 15px; | |||
color: #E3BB7A; | |||
text-align: center; | |||
} | |||
/* Recipe count */ | |||
.recipe-count { | |||
position: absolute; | |||
right: 60px; | |||
top: 50%; | |||
transform: translateY(-50%); | |||
color: #E3BB7A; | |||
font-size: 14px; | |||
} | |||
</style>`; | |||
$('head').append(styles); | |||
} | |||
} | } | ||
| Line 77: | Line 147: | ||
} | } | ||
// Initialize popup handlers | // FIXED: Initialize popup handlers with event delegation | ||
function initPopupHandlers() { | function initPopupHandlers() { | ||
// View All Crafted With | console.log('[ResourcePage] Setting up popup handlers...'); | ||
// View All Crafted With - using event delegation | |||
$(document).on('click', '.view-all-crafted-with', function(e) { | $(document).on('click', '.view-all-crafted-with', function(e) { | ||
e.preventDefault(); | e.preventDefault(); | ||
var resourceName = $( | e.stopPropagation(); | ||
console.log('[ResourcePage] View All Crafted With clicked'); | |||
var $element = $(this); | |||
var resourceName = $element.data('resource') || $element.attr('data-resource'); | |||
if (!resourceName) { | |||
console.error('[ResourcePage] No resource name found!'); | |||
return false; | |||
} | |||
showRecipePopup('craftedWith', resourceName); | showRecipePopup('craftedWith', resourceName); | ||
return false; | |||
}); | }); | ||
// View All Crafted From | // View All Crafted From - using event delegation | ||
$(document).on('click', '.view-all-crafted-from', function(e) { | $(document).on('click', '.view-all-crafted-from', function(e) { | ||
e.preventDefault(); | e.preventDefault(); | ||
var resourceName = $( | e.stopPropagation(); | ||
console.log('[ResourcePage] View All Crafted From clicked'); | |||
var $element = $(this); | |||
var resourceName = $element.data('resource') || $element.attr('data-resource'); | |||
if (!resourceName) { | |||
console.error('[ResourcePage] No resource name found!'); | |||
return false; | |||
} | |||
showRecipePopup('craftedFrom', resourceName); | showRecipePopup('craftedFrom', resourceName); | ||
return false; | |||
}); | }); | ||
// Close popup | // Close popup - overlay click | ||
$(document).on('click', '.recipe-popup-overlay | $(document).on('click', '.recipe-popup-overlay', function(e) { | ||
if (e.target === this | if (e.target === this) { | ||
console.log('[ResourcePage] Overlay clicked - closing popup'); | |||
closeRecipePopup(); | closeRecipePopup(); | ||
} | } | ||
}); | |||
// Close popup - close button | |||
$(document).on('click', '.popup-close', function(e) { | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
console.log('[ResourcePage] Close button clicked'); | |||
closeRecipePopup(); | |||
return false; | |||
}); | }); | ||
// Escape key to close popup | // Escape key to close popup | ||
$(document).on('keydown', function(e) { | $(document).on('keydown', function(e) { | ||
if (e.key === 'Escape' && $('.recipe-popup-overlay'). | if (e.key === 'Escape' && $('.recipe-popup-overlay').length > 0) { | ||
console.log('[ResourcePage] Escape key pressed - closing popup'); | |||
closeRecipePopup(); | closeRecipePopup(); | ||
} | } | ||
}); | |||
// Prevent text selection on span buttons | |||
$(document).on('selectstart', '.dune-action-button', function(e) { | |||
e.preventDefault(); | |||
return false; | |||
}); | }); | ||
} | } | ||
| Line 110: | Line 221: | ||
// Show recipe popup | // Show recipe popup | ||
function showRecipePopup(type, resourceName) { | function showRecipePopup(type, resourceName) { | ||
console.log('[ResourcePage] Showing popup for:', type, resourceName); | |||
var title = type === 'craftedWith' | var title = type === 'craftedWith' | ||
? 'All Recipes Using ' + resourceName | ? 'All Recipes Using ' + resourceName | ||
| Line 118: | Line 231: | ||
// First, check for full data in hidden elements | // First, check for full data in hidden elements | ||
var | var dataSelector = '.' + type.replace('craftedWith', 'crafted-with').replace('craftedFrom', 'crafted-from') + '-full-data'; | ||
var $fullDataElement = $(dataSelector); | |||
console.log('[ResourcePage] Looking for data in:', dataSelector); | |||
if ($fullDataElement.length && $fullDataElement.text().trim()) { | if ($fullDataElement.length && $fullDataElement.text().trim()) { | ||
try { | try { | ||
// Parse JSON data from hidden element | // Parse JSON data from hidden element | ||
var | var jsonText = $fullDataElement.text().trim(); | ||
var rows = jsonData.results || []; | console.log('[ResourcePage] Found JSON data, parsing...'); | ||
var jsonData = JSON.parse(jsonText); | |||
var rows = jsonData.results || jsonData || []; | |||
console.log('[ResourcePage] Parsed', rows.length, 'rows'); | |||
// Transform to expected format | // Transform to expected format | ||
| Line 130: | Line 251: | ||
return { | return { | ||
item: row.OutputItem || row.output_item || '', | item: row.OutputItem || row.output_item || '', | ||
resources: row.Resources || row.resources || '', | resources: formatResourceList(row.Resources || row.resources || ''), | ||
stations: row.Stations || row.station || '', | stations: row.Stations || row.station || row.stations || '', | ||
water: row.WaterML || row.water_ml || 'N/A', | water: row.WaterML || row.water_ml || 'N/A', | ||
craftTime: row.CraftTime || row.craft_time || 'N/A', | craftTime: row.CraftTime || row.craft_time || 'N/A', | ||
| Line 141: | Line 262: | ||
return; | return; | ||
} catch (e) { | } catch (e) { | ||
console. | console.error('[ResourcePage] Failed to parse full recipe data:', e); | ||
} | } | ||
} | } | ||
// Fallback to | // Fallback to API call | ||
var $ | console.log('[ResourcePage] No cached data found, making API call...'); | ||
fetchRecipeDataViaAPI(type, resourceName, title); | |||
} | |||
// Format resource list for display | |||
function formatResourceList(resources) { | |||
if (!resources) return ''; | |||
// If it's already HTML formatted, return as is | |||
if (resources.indexOf('<br') > -1) { | |||
return resources; | |||
} | |||
// Otherwise, split by semicolons and format | |||
return resources.split(';').map(function(r) { | |||
return r.trim(); | |||
}).filter(Boolean).join('<br>'); | |||
} | |||
// Fetch recipe data via API | |||
function fetchRecipeDataViaAPI(type, resourceName, title) { | |||
$.ajax({ | |||
url: mw.util.wikiScript('api'), | |||
data: { | |||
action: 'parse', | |||
format: 'json', | |||
page: mw.config.get('wgPageName'), | |||
prop: 'text' | |||
}, | |||
success: function(response) { | |||
if (response.parse && response.parse.text) { | |||
var html = response.parse.text['*']; | |||
var $html = $('<div>').html(html); | |||
// Find the appropriate table | |||
var tableId = type === 'craftedWith' ? '#craftedWithTable' : '#craftedFromTable'; | |||
var $table = $html.find(tableId); | |||
var rows = []; | |||
$table.find('tbody tr').each(function() { | |||
var $cells = $(this).find('td'); | |||
if ($cells.length >= 3) { | |||
rows.push({ | |||
item: $cells.eq(0).html(), | |||
resources: $cells.eq(1).html(), | |||
stations: $cells.eq(2).html(), | |||
water: 'N/A', | |||
craftTime: 'N/A', | |||
schematic: 'N/A' | |||
}); | |||
} | |||
}); | |||
console.log('[ResourcePage] Found', rows.length, 'rows via API'); | |||
displayPopup(title, rows, false); | |||
} | |||
}, | |||
error: function() { | |||
console.error('[ResourcePage] API call failed, using visible rows'); | |||
var rows = getVisibleRows(type); | |||
displayPopup(title, rows, true); | |||
} | |||
}); | |||
} | } | ||
| Line 209: | Line 390: | ||
// Note if data is limited | // Note if data is limited | ||
var limitedNote = isLimited ? | var limitedNote = isLimited ? | ||
'<div class="popup-note"> | '<div class="popup-note">Showing only visible recipes. Full data may require page reload.</div>' : | ||
''; | ''; | ||
| Line 217: | Line 398: | ||
'<div class="popup-header">' + | '<div class="popup-header">' + | ||
'<h3 class="popup-title">' + title + '</h3>' + | '<h3 class="popup-title">' + title + '</h3>' + | ||
'<span class="recipe-count">' + rows.length + ' recipes</span>' + | |||
'<button class="popup-close">×</button>' + | '<button class="popup-close">×</button>' + | ||
'</div>' + | '</div>' + | ||
| Line 314: | Line 496: | ||
}); | }); | ||
}); | }); | ||
// Focus search input | |||
$('#popupSearchInput').focus(); | |||
} | } | ||
| Line 323: | Line 508: | ||
$('.recipe-popup-overlay').remove(); | $('.recipe-popup-overlay').remove(); | ||
}, 300); | }, 300); | ||
} | } | ||
// Initialize when DOM is ready | // Initialize when DOM is ready | ||
$(init); | $(function() { | ||
init(); | |||
}); | |||
})(jQuery, mediaWiki); | })(jQuery, mediaWiki); | ||
Revision as of 01:45, 4 June 2025
/**
* ResourcePage Gadget - Complete Fixed Version
* Handles recipe popups and search functionality for resource pages
*
* @requires jquery
* @requires mediawiki.api
* @requires mediawiki.util
*/
(function($, mw) {
'use strict';
// Only run on pages with resource page elements
if (!$('.crafting-section, .recipe-table').length) {
return;
}
console.log('[ResourcePage] Gadget loading...');
// Cache for storing recipe data
var recipeCache = {
craftedWith: null,
craftedFrom: null
};
// Initialize module
function init() {
console.log('[ResourcePage] Initializing...');
// Add required styles
injectRequiredStyles();
// Add table search functionality
initTableSearch();
// Add popup handlers - FIXED VERSION
initPopupHandlers();
// Format tables
formatTables();
}
// Inject required CSS
function injectRequiredStyles() {
if ($('#resourcePageGadgetStyles').length === 0) {
var styles = `<style id="resourcePageGadgetStyles">
/* Span button styles */
.dune-action-button {
display: inline-block !important;
cursor: pointer !important;
user-select: none !important;
-webkit-user-select: none !important;
-moz-user-select: none !important;
-ms-user-select: none !important;
}
/* Loading spinner */
.loading-spinner {
font-size: 48px;
animation: spin 1s linear infinite;
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Sort indicators */
.recipe-popup-table th {
cursor: pointer;
user-select: none;
}
.recipe-popup-table th.sort-asc::after {
content: " ↑";
color: #fce7c8;
}
.recipe-popup-table th.sort-desc::after {
content: " ↓";
color: #fce7c8;
}
/* Popup note */
.popup-note {
background: rgba(252,231,200,.1);
border: 1px solid rgba(252,231,200,.3);
padding: 10px;
margin-bottom: 15px;
color: #E3BB7A;
text-align: center;
}
/* Recipe count */
.recipe-count {
position: absolute;
right: 60px;
top: 50%;
transform: translateY(-50%);
color: #E3BB7A;
font-size: 14px;
}
</style>`;
$('head').append(styles);
}
}
// Format tables to ensure proper styling
function formatTables() {
$('.recipe-table').each(function() {
var $table = $(this);
// Ensure proper header styling
$table.find('thead tr').addClass('tr-dark');
});
}
// Initialize table search functionality
function initTableSearch() {
// Crafted With search
$('#craftedWithSearch').on('input', function() {
filterTable('#craftedWithTable', $(this).val());
});
// Crafted From search
$('#craftedFromSearch').on('input', function() {
filterTable('#craftedFromTable', $(this).val());
});
}
// Filter table rows based on search input
function filterTable(tableId, searchTerm) {
var $table = $(tableId);
var term = searchTerm.toLowerCase();
$table.find('tbody tr').each(function() {
var $row = $(this);
var text = $row.text().toLowerCase();
if (text.indexOf(term) > -1) {
$row.show();
} else {
$row.hide();
}
});
}
// FIXED: Initialize popup handlers with event delegation
function initPopupHandlers() {
console.log('[ResourcePage] Setting up popup handlers...');
// View All Crafted With - using event delegation
$(document).on('click', '.view-all-crafted-with', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('[ResourcePage] View All Crafted With clicked');
var $element = $(this);
var resourceName = $element.data('resource') || $element.attr('data-resource');
if (!resourceName) {
console.error('[ResourcePage] No resource name found!');
return false;
}
showRecipePopup('craftedWith', resourceName);
return false;
});
// View All Crafted From - using event delegation
$(document).on('click', '.view-all-crafted-from', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('[ResourcePage] View All Crafted From clicked');
var $element = $(this);
var resourceName = $element.data('resource') || $element.attr('data-resource');
if (!resourceName) {
console.error('[ResourcePage] No resource name found!');
return false;
}
showRecipePopup('craftedFrom', resourceName);
return false;
});
// Close popup - overlay click
$(document).on('click', '.recipe-popup-overlay', function(e) {
if (e.target === this) {
console.log('[ResourcePage] Overlay clicked - closing popup');
closeRecipePopup();
}
});
// Close popup - close button
$(document).on('click', '.popup-close', function(e) {
e.preventDefault();
e.stopPropagation();
console.log('[ResourcePage] Close button clicked');
closeRecipePopup();
return false;
});
// Escape key to close popup
$(document).on('keydown', function(e) {
if (e.key === 'Escape' && $('.recipe-popup-overlay').length > 0) {
console.log('[ResourcePage] Escape key pressed - closing popup');
closeRecipePopup();
}
});
// Prevent text selection on span buttons
$(document).on('selectstart', '.dune-action-button', function(e) {
e.preventDefault();
return false;
});
}
// Show recipe popup
function showRecipePopup(type, resourceName) {
console.log('[ResourcePage] Showing popup for:', type, resourceName);
var title = type === 'craftedWith'
? 'All Recipes Using ' + resourceName
: 'All Recipes Creating ' + resourceName;
// Show loading state
showLoadingPopup();
// First, check for full data in hidden elements
var dataSelector = '.' + type.replace('craftedWith', 'crafted-with').replace('craftedFrom', 'crafted-from') + '-full-data';
var $fullDataElement = $(dataSelector);
console.log('[ResourcePage] Looking for data in:', dataSelector);
if ($fullDataElement.length && $fullDataElement.text().trim()) {
try {
// Parse JSON data from hidden element
var jsonText = $fullDataElement.text().trim();
console.log('[ResourcePage] Found JSON data, parsing...');
var jsonData = JSON.parse(jsonText);
var rows = jsonData.results || jsonData || [];
console.log('[ResourcePage] Parsed', rows.length, 'rows');
// Transform to expected format
var formattedRows = rows.map(function(row) {
return {
item: row.OutputItem || row.output_item || '',
resources: formatResourceList(row.Resources || row.resources || ''),
stations: row.Stations || row.station || row.stations || '',
water: row.WaterML || row.water_ml || 'N/A',
craftTime: row.CraftTime || row.craft_time || 'N/A',
schematic: row.Schematic || row.schematic || 'N/A'
};
});
displayPopup(title, formattedRows, false);
return;
} catch (e) {
console.error('[ResourcePage] Failed to parse full recipe data:', e);
}
}
// Fallback to API call
console.log('[ResourcePage] No cached data found, making API call...');
fetchRecipeDataViaAPI(type, resourceName, title);
}
// Format resource list for display
function formatResourceList(resources) {
if (!resources) return '';
// If it's already HTML formatted, return as is
if (resources.indexOf('<br') > -1) {
return resources;
}
// Otherwise, split by semicolons and format
return resources.split(';').map(function(r) {
return r.trim();
}).filter(Boolean).join('<br>');
}
// Fetch recipe data via API
function fetchRecipeDataViaAPI(type, resourceName, title) {
$.ajax({
url: mw.util.wikiScript('api'),
data: {
action: 'parse',
format: 'json',
page: mw.config.get('wgPageName'),
prop: 'text'
},
success: function(response) {
if (response.parse && response.parse.text) {
var html = response.parse.text['*'];
var $html = $('<div>').html(html);
// Find the appropriate table
var tableId = type === 'craftedWith' ? '#craftedWithTable' : '#craftedFromTable';
var $table = $html.find(tableId);
var rows = [];
$table.find('tbody tr').each(function() {
var $cells = $(this).find('td');
if ($cells.length >= 3) {
rows.push({
item: $cells.eq(0).html(),
resources: $cells.eq(1).html(),
stations: $cells.eq(2).html(),
water: 'N/A',
craftTime: 'N/A',
schematic: 'N/A'
});
}
});
console.log('[ResourcePage] Found', rows.length, 'rows via API');
displayPopup(title, rows, false);
}
},
error: function() {
console.error('[ResourcePage] API call failed, using visible rows');
var rows = getVisibleRows(type);
displayPopup(title, rows, true);
}
});
}
// Get visible rows as fallback
function getVisibleRows(type) {
var $table = $(type === 'craftedWith' ? '#craftedWithTable' : '#craftedFromTable');
var rows = [];
$table.find('tbody tr:visible').each(function() {
var $row = $(this);
var $cells = $row.find('td');
if ($cells.length >= 3) {
rows.push({
item: $cells.eq(0).html(),
resources: $cells.eq(1).html(),
stations: $cells.eq(2).html(),
water: 'N/A',
craftTime: 'N/A',
schematic: 'N/A'
});
}
});
return rows;
}
// Show loading popup
function showLoadingPopup() {
var loadingHtml = '<div class="recipe-popup-overlay active">' +
'<div class="recipe-popup">' +
'<div class="popup-content" style="text-align: center; padding: 60px;">' +
'<div style="font-size: 24px; color: #fce7c8; margin-bottom: 20px;">Loading recipes...</div>' +
'<div class="loading-spinner">⟳</div>' +
'</div>' +
'</div>' +
'</div>';
$('body').append(loadingHtml);
}
// Display popup with data
function displayPopup(title, rows, isLimited) {
// Remove any existing popup
$('.recipe-popup-overlay').remove();
// Build table rows HTML
var tableRowsHtml = '';
rows.forEach(function(row) {
tableRowsHtml += '<tr>' +
'<td>' + row.item + '</td>' +
'<td>' + row.resources + '</td>' +
'<td>' + row.stations + '</td>' +
'<td>' + row.water + '</td>' +
'<td>' + row.craftTime + '</td>' +
'<td>' + row.schematic + '</td>' +
'</tr>';
});
// Note if data is limited
var limitedNote = isLimited ?
'<div class="popup-note">Showing only visible recipes. Full data may require page reload.</div>' :
'';
// Build popup HTML
var popupHtml = '<div class="recipe-popup-overlay">' +
'<div class="recipe-popup">' +
'<div class="popup-header">' +
'<h3 class="popup-title">' + title + '</h3>' +
'<span class="recipe-count">' + rows.length + ' recipes</span>' +
'<button class="popup-close">×</button>' +
'</div>' +
'<div class="popup-content">' +
limitedNote +
'<div class="popup-search-container">' +
'<input type="text" class="popup-search-input" placeholder="Search recipes..." id="popupSearchInput">' +
'</div>' +
'<div class="popup-table-wrapper">' +
'<table class="infobox-dune-standard-table recipe-popup-table" id="popupRecipeTable">' +
'<thead>' +
'<tr class="tr-dark">' +
'<th data-sort="item">Item Created</th>' +
'<th data-sort="resources">Resources Needed</th>' +
'<th data-sort="stations">Stations</th>' +
'<th data-sort="water">Water (mL)</th>' +
'<th data-sort="time">Craft Time</th>' +
'<th data-sort="schematic">Schematic</th>' +
'</tr>' +
'</thead>' +
'<tbody>' + tableRowsHtml + '</tbody>' +
'</table>' +
'</div>' +
'</div>' +
'</div>' +
'</div>';
// Add to page
$('body').append(popupHtml);
// Activate popup
setTimeout(function() {
$('.recipe-popup-overlay').addClass('active');
}, 10);
// Initialize popup functionality
initPopupFunctionality();
}
// Initialize popup functionality (search and sort)
function initPopupFunctionality() {
// Search functionality
$('#popupSearchInput').on('input', function() {
var searchTerm = $(this).val().toLowerCase();
$('#popupRecipeTable tbody tr').each(function() {
var $row = $(this);
var text = $row.text().toLowerCase();
if (text.indexOf(searchTerm) > -1) {
$row.show();
} else {
$row.hide();
}
});
});
// Sort functionality
$('#popupRecipeTable thead th').on('click', function() {
var $th = $(this);
var sortKey = $th.data('sort');
var $tbody = $('#popupRecipeTable tbody');
var rows = $tbody.find('tr').toArray();
// Determine sort direction
var ascending = !$th.hasClass('sort-desc');
// Remove sort classes from all headers
$('#popupRecipeTable thead th').removeClass('sort-asc sort-desc');
// Add appropriate class to clicked header
$th.addClass(ascending ? 'sort-asc' : 'sort-desc');
// Sort rows
rows.sort(function(a, b) {
var aText = $(a).find('td').eq($th.index()).text().trim();
var bText = $(b).find('td').eq($th.index()).text().trim();
// Try to parse as number
var aNum = parseFloat(aText);
var bNum = parseFloat(bText);
if (!isNaN(aNum) && !isNaN(bNum)) {
return ascending ? aNum - bNum : bNum - aNum;
}
// Sort as text
return ascending
? aText.localeCompare(bText)
: bText.localeCompare(aText);
});
// Re-append sorted rows
$tbody.empty();
rows.forEach(function(row) {
$tbody.append(row);
});
});
// Focus search input
$('#popupSearchInput').focus();
}
// Close recipe popup
function closeRecipePopup() {
$('.recipe-popup-overlay').removeClass('active');
setTimeout(function() {
$('.recipe-popup-overlay').remove();
}, 300);
}
// Initialize when DOM is ready
$(function() {
init();
});
})(jQuery, mediaWiki);