MediaWiki:Common.js: Difference between revisions
From Makerpedia
No edit summary Tag: Reverted |
No edit summary Tag: Reverted |
||
| Line 448: | Line 448: | ||
}); | }); | ||
// Fix: Use event delegation for dynamically created thumbnails | |||
$(".thumbnail" | $(document).on("click", ".thumbnail", function () { | ||
currentIndex = $(this).data("index"); | currentIndex = $(this).data("index"); | ||
updateCarousel(); | updateCarousel(); | ||
Revision as of 14:36, 9 March 2025
/* Any JavaScript here will be loaded for all users on every page load. */
if (document) window.µ = function (id, elem) {
var ret;
var root = ((elem) ? elem : document);
switch (id.charAt(0)) {
case '|':
ret = root;
break;
case '+':
ret = document.createElement(id.substring(1));
if (elem) elem.appendChild(ret);
break;
case '#':
ret = root.querySelector(id);
break;
default:
ret = Array.prototype.slice.call(root.querySelectorAll(id));
break;
}
return ret;
};
/* add additional edit button in bottom right corner -- styled in Common.css */
/* Takes them to visual editor if available/logged in; if not, takes them to normal edit mode; if they are not logged in at all, takes them to login page */
let editLinkQuery = document.querySelector("#ca-ve-edit a") ? "#ca-ve-edit a" : (document.querySelector("#ca-edit a") ? "#ca-edit a" : "#pt-login a");
document.getElementById("content").innerHTML += '<a href='+document.querySelector(editLinkQuery).href+'><button class="big-edit-button"><p>EDIT</p></button></a>';
/* looks at current url parameters of the form '?param1=value1¶m2=value2...' and returns a dictionary of the form {param1: value1, param2: value2, ...} */
function parseUrlParameters() {
let queryStr = window.location.search.substring(1).replaceAll("%20", " "); // start string after the question mark and replace '%20' with spaces
let queryDict = {};
queryStr.split("&").forEach(function(query) {
let pair = query.split("=");
queryDict[pair[0]] = pair[1];
})
return queryDict;
}
// Project Tutorials Gallery
mw.loader.using(['mediawiki.api', 'jquery'], function () {
$(document).ready(function () {
if (mw.config.get('wgPageName') !== 'GALLERY') return; // Ensure script runs only on the Gallery page
// get url parameters and automatically select filter to value of 'filter' parameter in url (eg www.wikititle/pagetitle?filter=Textiles)
let queryDict = parseUrlParameters();
var category = queryDict['filter'] ? queryDict['filter'] : "Projects"; // If no filter query in url, use default category Projects
var galleryContainer = $('#project-gallery');
/* add buttons to filter by category on Project Tutorials page
html text displayed can be anything, but value of data-filter should be the same format as value passed in url parameters and entry in categories
dictionary in loadGallery() function */
galleryContainer.before('<div id="category-filter">' +
'<button id="all-filter-btn" class="filter-btn" data-filter="Projects">Show All</button>' +
'<button id="crafts-filter-btn" class="filter-btn" data-filter="Crafts">Crafts</button>' +
'<button id="design-filter-btn" class="filter-btn" data-filter="Design">Design</button>' +
'<button id="fabrication-filter-btn" class="filter-btn" data-filter="Fabrication">Fabrication</button>' +
'<button id="cnc-laser-filter-btn" class="filter-btn" data-filter="CNC-Laser">Laser & CNC</button>' +
'<button id="metalworking-filter-btn" class="filter-btn" data-filter="Metalworking">Metalworking</button>' +
'<button id="textiles-filter-btn" class="filter-btn" data-filter="Textiles">Textiles</button>' +
'</div>');
// use insertImage function to query api and insert given image at given id, with given style (adds icons to Project Tutorials filter buttons)
insertImage("File:Airbrushing_icon_color.png", "#fabrication-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Button_icon_color.png", "#crafts-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Cassette_icon_color.png", "#crafts-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Crafting_icon_color.png", "#crafts-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Graphic_icon_color.png", "#design-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Handsewing_icon_color.png", "#textiles-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Laser_icon_color.png", "#cnc-laser-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Machining_icon_color.png", "#cnc-laser-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Modeling_icon_color.png", "#design-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Printing_icon_color.png", "#fabrication-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Programming_icon_color.png", "#design-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Soldering_icon_color.png", "#metalworking-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Technical_icon_color.png", "#design-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Textile_icon_color.png", "#textiles-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Welding_icon_color.png", "#metalworking-filter-btn", style="height:1.5em;padding-left:.4em");
insertImage("File:Woodworking_icon_color.png", "#fabrication-filter-btn", style="height:1.5em;padding-left:.4em");
// load gallery based on category
function loadGallery(category) {
galleryContainer.html('<div class="gallery-container"></div>');
let pageTitles = [];
// map larger umbrella categories to the skill badge categories they include
let categories = {
"Projects" : ["Projects"],
"Design" : ["Graphic Design", "3D Modeling", "Technical Design", "Programming"],
"Textiles" : ["Textiles", "Hand Sewing"],
"Crafts" : ["Crafting", "Button Pressing", "Cassette Making"],
"Fabrication" : ["Airbrushing", "3D Printing", "Woodworking"],
"CNC-Laser" : ["Laser Cutting", "Machining"],
"Metalworking" : ["Soldering", "Welding"]
};
// insert projects corresponding to each skill under broader category filter selected
categories[category].forEach(function(cat) {
new mw.Api().get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:' + cat,
cmlimit: 50,
format: 'json'
}).done(function (data) {
var pages = data.query.categorymembers;
var galleryHtml = '';
var requests = pages.map(function (page) {
return new mw.Api().get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: page.title,
format: 'json'
}).then(function (pageData) {
var pageId = Object.keys(pageData.query.pages)[0];
var content = pageData.query.pages[pageId].revisions[0]['*'];
var pageUrl = mw.util.getUrl(page.title);
var imgUrl = '';
// Try to find a direct image URL (img1=https://...)
var directMatch = content.match(/img1=(https:\/\/[^\s|}%]+)/);
if (directMatch) {
imgUrl = directMatch[1];
}
// If no direct URL, try to find a [[File:...]] entry
var fileMatch = content.match(/\[\[File:([^|\]]+)/);
if (!imgUrl && fileMatch) {
var fileName = fileMatch[1].trim();
// Fetch full image URL from MediaWiki API
return new mw.Api().get({
action: 'query',
titles: 'File:' + fileName,
prop: 'imageinfo',
iiprop: 'url',
format: 'json'
}).then(function (imageData) {
var imagePageId = Object.keys(imageData.query.pages)[0];
if (imageData.query.pages[imagePageId].imageinfo) {
imgUrl = imageData.query.pages[imagePageId].imageinfo[0].url;
}
galleryHtml += generateGalleryItem(pageUrl, page.title, imgUrl, category);
});
} else {
// Add the gallery item if the image was found and is not already being shown
if(!pageTitles.includes(page.title)) {
galleryHtml += generateGalleryItem(pageUrl, page.title, imgUrl, category);
pageTitles.push(page.title);
}
}
});
});
Promise.all(requests).then(function () {
$('.gallery-container').append(galleryHtml);
});
});
});
}
// generate a gallery
function generateGalleryItem(pageUrl, title, imgUrl, category) {
return `
<div class="gallery-item ${category}">
<a href="${pageUrl}">
${imgUrl ? `<img src="${imgUrl}" alt="${title}">` : `<img src="https://upload.wikimedia.org/wikipedia/commons/6/65/No-Image-Placeholder.svg" alt="No Image Available">`}
</a>
<div class="gallery-caption">
<a href="${pageUrl}">${title}</a>
</div>
</div>
`;
}
loadGallery(category);
// find filter button corresponding to selected category and add css class 'selected-filter'
$(".filter-btn").each(function () { $(this).data("filter") === category ? $(this).addClass('selected-filter') : null});
// when filter button is clicked, load projects for that category, deselect current filtered button, and select newly clicked filter button
$(".filter-btn").click(function () {
var selectedCategory = $(this).data("filter");
loadGallery(selectedCategory);
$('.filter-btn.selected-filter').each((i, elem) => $(elem).removeClass('selected-filter'));
$(this).addClass('selected-filter');
});
});
});
// TOOLS Gallery
mw.loader.using(['mediawiki.api', 'jquery'], function () {
$(document).ready(function () {
if (mw.config.get('wgPageName') !== 'TOOLS') return; // Ensure script runs only on the TOOLS page
// get url parameters and automatically select appropriate filter
let queryDict = parseUrlParameters();
var category = queryDict['filter'] ? queryDict['filter'] : "Tools"; // If no filter query in url, use default category
var galleryContainer = $('#tools-gallery');
// Add filter buttons before the gallery
galleryContainer.before('<div id="category-filter">' +
'<button class="filter-btn" data-filter="Tools">Show All</button>' +
'<button class="filter-btn" data-filter="Makerspace Tools">Makerspace Tools</button>' +
'<button class="filter-btn" data-filter="Machine Shop Tools">Machine Shop Tools</button>' +
'<button class="filter-btn" data-filter="Wood Shop Tools">Wood Shop Tools</button>' +
'<button class="filter-btn" data-filter="Instrument Shop Tools">Instrument Shop Tools</button>' +
'<button class="filter-btn" data-filter="Repair Lair Tools">Repair Lair Tools</button>' +
'</div>');
function loadGallery(category) {
galleryContainer.html('<div class="gallery-container">Loading gallery...</div>');
new mw.Api().get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:' + category,
cmlimit: 50,
format: 'json'
}).done(function (data) {
var pages = data.query.categorymembers;
var galleryHtml = '';
var requests = pages.map(function (page) {
return new mw.Api().get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: page.title,
format: 'json'
}).then(function (pageData) {
var pageId = Object.keys(pageData.query.pages)[0];
var content = pageData.query.pages[pageId].revisions[0]['*'];
var pageUrl = mw.util.getUrl(page.title);
var imgUrl = '';
// Try to find a [[File:...]] entry
var fileMatch = content.match(/\[\[File:([^|\]]+)/);
if (fileMatch) {
var fileName = fileMatch[1].trim();
// Fetch full image URL from MediaWiki API
return new mw.Api().get({
action: 'query',
titles: 'File:' + fileName,
prop: 'imageinfo',
iiprop: 'url',
format: 'json'
}).then(function (imageData) {
var imagePageId = Object.keys(imageData.query.pages)[0];
if (imageData.query.pages[imagePageId].imageinfo) {
imgUrl = imageData.query.pages[imagePageId].imageinfo[0].url;
}
// Add the gallery item
galleryHtml += generateGalleryItem(pageUrl, page.title, imgUrl, category);
});
} else {
// If no image is found, still display the title
galleryHtml += generateGalleryItem(pageUrl, page.title, imgUrl, category);
}
});
});
Promise.all(requests).then(function () {
// Append gallery items after loading them
galleryContainer.html('<div class="gallery-container">' + galleryHtml + '</div>');
});
});
}
function generateGalleryItem(pageUrl, title, imgUrl, category) {
return `
<div class="gallery-item ${category}">
<a href="${pageUrl}">
${imgUrl ? `<img src="${imgUrl}" alt="${title}">` : `<img src="https://upload.wikimedia.org/wikipedia/commons/6/65/No-Image-Placeholder.svg" alt="No Image Available">`}
</a>
<div class="gallery-caption">
<a href="${pageUrl}">${title}</a>
</div>
</div>
`;
}
loadGallery(category);
// find filter button corresponding to preselected category and add css class 'selected-filter'
$(".filter-btn").each(function () { $(this).data("filter") === category ? $(this).addClass('selected-filter') : null});
// when filter button is clicked, load tools for that category, deselect current filtered button, and select newly clicked filter button
$(".filter-btn").click(function () {
var selectedCategory = $(this).data("filter");
loadGallery(selectedCategory);
$('.filter-btn.selected-filter').each((i, elem) => $(elem).removeClass('selected-filter'));
$(this).addClass('selected-filter');
});
});
});
//CAROUSEL
function loadCarouselData() {
// Make the API query to fetch pages for carousel
var category = "SimpleProjects";
var carouselData = [];
new mw.Api().get({
action: 'query',
list: 'categorymembers',
cmtitle: 'Category:' + category,
cmlimit: 50,
format: 'json'
}).done(function (data) {
var pages = data.query.categorymembers;
var requests = pages.map(function (page) {
return new mw.Api().get({
action: 'query',
prop: 'revisions',
rvprop: 'content',
titles: page.title,
format: 'json'
}).then(function (pageData) {
var pageId = Object.keys(pageData.query.pages)[0];
var content = pageData.query.pages[pageId].revisions[0]['*'];
var pageUrl = mw.util.getUrl(page.title);
var imgUrl = '';
// Try to find a direct image URL (img1=https://...)
var directMatch = content.match(/img1=(https:\/\/[^\s|}%]+)/);
if (directMatch) {
imgUrl = directMatch[1];
}
// If no direct URL, try to find a [[File:...]] entry
var fileMatch = content.match(/\[\[File:([^|\]]+)/);
if (!imgUrl && fileMatch) {
var fileName = fileMatch[1].trim();
// Fetch full image URL from MediaWiki API
return new mw.Api().get({
action: 'query',
titles: 'File:' + fileName,
prop: 'imageinfo',
iiprop: 'url',
format: 'json'
}).then(function (imageData) {
var imagePageId = Object.keys(imageData.query.pages)[0];
if (imageData.query.pages[imagePageId].imageinfo) {
imgUrl = imageData.query.pages[imagePageId].imageinfo[0].url;
}
// Store carousel item data
if (imgUrl) {
carouselData.push({ pageUrl: pageUrl, imgUrl: imgUrl, title: page.title });
}
});
} else {
// Store carousel item data
if (imgUrl) {
carouselData.push({ pageUrl: pageUrl, imgUrl: imgUrl, title: page.title });
}
}
});
});
Promise.all(requests).then(function () {
// Call the function to display carousel
displayCarousel(carouselData);
});
});
}
function displayCarousel(carouselData) {
var carouselHtml = '';
carouselData.forEach(function (item) {
carouselHtml += `
<div class="carousel-item">
<a href="${item.pageUrl}">
<img src="${item.imgUrl}" alt="Carousel Image">
</a>
<div class="carousel-title">${item.title}</div>
</div>
`;
});
// Inject the HTML into the carousel container
var carouselContainer = $('.carousel-container');
carouselContainer.html(carouselHtml);
// Initialize the carousel behavior
initializeCarousel();
}
function initializeCarousel() {
let currentIndex = 0;
let $carouselItems = $(".carousel-item");
let totalItems = $carouselItems.length;
$carouselItems.hide().eq(currentIndex).show();
let $prevButton = $('<button class="carousel-prev"> 🞀 </button>');
let $nextButton = $('<button class="carousel-next"> 🞂 </button>');
$('.carousel-container').append($prevButton, $nextButton);
// Create thumbnails container
let $thumbnailsContainer = $('<div class="carousel-thumbnails"></div>');
$carouselContainer.append($thumbnailsContainer);
// Generate thumbnail images
$carouselItems.each(function (index) {
let thumbnailSrc = $(this).find("img").attr("src"); // Assuming images inside .carousel-item
let $thumbnail = $(`<img src="${thumbnailSrc}" class="thumbnail" data-index="${index}">`);
$thumbnailsContainer.append($thumbnail);
});
function nextSlide() {
currentIndex = (currentIndex + 1) % totalItems;
updateCarousel();
}
function resetInterval() {
clearInterval(autoSlide);
autoSlide = setInterval(nextSlide, 10000);
}
let autoSlide = setInterval(nextSlide, 10000); //auto slide every 10 seconds
$nextButton.click(function () {
nextSlide();
resetInterval();
});
$prevButton.click(function () {
currentIndex = (currentIndex - 1 + totalItems) % totalItems;
updateCarousel();
resetInterval();
});
// Fix: Use event delegation for dynamically created thumbnails
$(document).on("click", ".thumbnail", function () {
currentIndex = $(this).data("index");
updateCarousel();
resetInterval();
});
function updateCarousel() {
$carouselItems.hide().eq(currentIndex).show();
$(".thumbnail").removeClass("active");
$(".thumbnail").eq(currentIndex).addClass("active");
}
// Set initial active thumbnail
$(".thumbnail").eq(currentIndex).addClass("active");
}
$(document).ready(function () {
loadCarouselData(); // Load the carousel data on page load
});
// Individual Project pages (and potentially tools): if page is part of a category, insert any corresponding skill badges at top of page
if(document.getElementById("catlinks") != null) {
document.querySelectorAll("#catlinks li a").forEach((cat) => {
let query = "#firstHeading";
if(cat.title.includes("Category:Airbrushing")) {
insertImage('File:Airbrushing_badge_small.png', query, "padding-left:.3em;", "Fabrication", true, "Airbrushing");
}
if(cat.title.includes("Category:Button Pressing")) {
insertImage('File:Button_badge_small.png', query, "padding-left:.3em;", "Crafts", true, "Button Pressing");
}
if(cat.title.includes("Category:Cassette Making")) {
insertImage('File:Cassette_badge_small.png', query, "padding-left:.3em;", "Crafts", true, "Cassette Making");
}
if(cat.title.includes("Category:Crafting")) {
insertImage('File:Crafting_badge_small.png', query, "padding-left:.3em;", "Crafts", true, "Crafting");
}
if(cat.title.includes("Category:Graphic Design")) {
insertImage('File:Graphic_badge_small.png', query, "padding-left:.3em;", "Design", true, "Graphic Design");
}
if(cat.title.includes("Category:Hand Sewing")) {
insertImage('File:Handsewing_badge_small.png', query, "padding-left:.3em;", "Textiles", true, "Hand Sewing");
}
if(cat.title.includes("Category:Laser Cutting")) {
insertImage('File:Laser_badge_small.png', query, "padding-left:.3em;", "CNC-Laser", true, "Laser Cutting");
}
if(cat.title.includes("Category:Machining")) {
insertImage('File:Machining_badge_small.png', query, "padding-left:.3em;", "CNC-Laser", true, "Machining");
}
if(cat.title.includes("Category:3D Modeling")) {
insertImage('File:Modeling_badge_small.png', query, "padding-left:.3em;", "Design", true, "3D Modeling");
}
if(cat.title.includes("Category:3D Printing")) {
insertImage('File:Printing_badge_small.png', query, "padding-left:.3em;", "Fabrication", true, "3D Printing");
}
if(cat.title.includes("Category:Programming")) {
insertImage('File:Programming_badge_small.png', query, "padding-left:.3em;", "Design", true, "Programming");
}
if(cat.title.includes("Category:Soldering")) {
insertImage('File:Soldering_badge_small.png', query, "padding-left:.3em;", "Metalworking", true, "Soldering");
}
if(cat.title.includes("Category:Technical Design")) {
insertImage('File:Technical_badge_small.png', query, "padding-left:.3em;", "Design", true, "Technical Design");
}
if(cat.title.includes("Category:Textiles")) {
insertImage('File:Textile_badge_small.png', query, "padding-left:.3em;", "Textiles", true, "Textiles");
}
if(cat.title.includes("Category:Welding")) {
insertImage('File:Welding_badge_small.png', query, "padding-left:.3em;", "Metalworking", true, "Welding");
}
if(cat.title.includes("Category:Woodworking")) {
insertImage('File:Woodworking_badge_small.png', query, "padding-left:.3em;", "Fabrication", true, "Woodworking");
}
})
}
// uses api to insert image at given selector element in html, using image in mediawiki format (eg [[File:filename...]])
// optional style parameter gets applied
// if asLinkedSkillBadge is true, styles as skill badge (image wrapped in <a> link)
function insertImage(image, selector, style="", linkFilter="", asLinkedSkillBadge=false, tooltip=image) {
$.getJSON(
mw.util.wikiScript( 'api' ), {
format: 'json',
action: 'query',
titles: image,
prop: 'imageinfo',
iiprop: 'url'
},
function( obj ) {
let pages = obj.query.pages;
let result1 = pages[Object.keys(pages)[0]];
let url = result1.imageinfo[0].url;
let alt = result1.title; // set alt text to image title
let urlSub = url.substring(url.indexOf("/")); // get just later part of url as link to image (after first slash)
if(asLinkedSkillBadge) { // insert as linked skill badge (image wrapped in <a> with link to prefiltered Project Tutorials page)
document.querySelector(selector).innerHTML += '<a href="./GALLERY?title=GALLERY&filter='+linkFilter+'" title="'+tooltip+'"><img class="skill-badge-img" src="'+urlSub+'" alt="'+alt+'" style="'+style+'"></a>';
}
else { // insert as regular image
document.querySelector(selector).innerHTML += '<img src="'+urlSub+'" alt="'+alt+'" style="'+style+'">';
}
}
);
}
/* like button. Only appears on "Main" Namespace pages that aren't sidebar pages (should be restricted to content pages like articles).*/
if (mw.config.get('wgNamespaceNumber') == 0 && mw.config.get('wgPageName') !== 'GALLERY' && mw.config.get('wgPageName') !== 'TOOLS' && mw.config.get('wgPageName') !== 'Spaces_New' && mw.config.get('wgPageName') !== 'Home_New') {
// if page is favorited we should show unfavorite button instead. Direct to login if not logged in.
if (document.querySelector("#ca-favorite")) {
document.getElementById("firstHeading").innerHTML += '<a href=/wiki/index.php?title='+mw.config.get('wgPageName')+'&action=favorite&returnto='+mw.config.get('wgPageName')+'><button class="favorite"></button></a>';
} else if (document.querySelector("#ca-unfavorite")) {
document.getElementById("firstHeading").innerHTML += '<a href=/wiki/index.php?title='+mw.config.get('wgPageName')+'&action=unfavorite&returnto='+mw.config.get('wgPageName')+'><button class="unfavorite"></button></a>';
} else {
document.getElementById("firstHeading").innerHTML += '<a href='+document.querySelector("#pt-login a").href+'><button class="favorite"></button></a>';
};
};
// open any <a> wrapped in <u class="plainlinks"> in same tab (generally internal links that must be treated as external links in order to give url parameters)
$('u.plainlinks a').each(function() { $(this).attr('target', "_self")});