The official discord link if you wish to join the discord: https://discord.gg/j5RKwCvAFu
Support the wiki on our official Ko-Fi page or Patreon page!
MediaWiki:Timeless.js
From The Codex
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)
- Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
- Opera: Press Ctrl-F5.
/* All JavaScript here will be loaded for users of the Timeless skin */
if (typeof rcwidget !== 'undefined') {
// Clean up existing instance
if (rcwidget.cleanup) {
rcwidget.cleanup();
}
}
var rcwidget = (function() {
// Private variables
let fetchInterval = null;
let updateInterval = null;
// Configuration
const config = {
onlyshowores: (typeof onlyshowores !== 'undefined') ? onlyshowores : false,
orestolerance: (typeof orestolerance !== 'undefined') ? orestolerance : 0.70,
fetchtime: (typeof rcfetchtime !== 'undefined') ? rcfetchtime : 0.5
};
// Utility functions
function generateTimeAgo(hours, minutes, seconds) {
if (hours > 0) {
return `${hours}${hours === 1 ? ' hour' : ' hours'} ago`;
} else if (minutes > 0) {
return `${minutes}${minutes === 1 ? ' minute' : ' minutes'} ago`;
} else if (seconds > 0) {
return `${seconds}${seconds === 1 ? ' second' : ' seconds'} ago`;
}
return 'just now';
}
function createChangeHTML(change) {
try {
const baseUrl = mw.config.get("wgScript");
let html = '<li>';
// Handle ORES scoring
const shouldBold = change.type === "edit" &&
change.oresscores?.damaging?.true >= config.orestolerance;
if (shouldBold) {
html += '<b>';
}
// User link
html += `<a href="${baseUrl}/User_talk:${encodeURIComponent(change.user)}">${change.user}</a> `;
// Change type specific content
if (change.type === "edit") {
if (change.tags.includes("mw-undo")) {
html += `<a href="${baseUrl}/Project:Undo">undid</a> an `;
} else if (change.tags.includes("mw-rollback")) {
html += `<a href="${baseUrl}/Project:Rollback">rolled back</a> `;
}
html += `<a href="${baseUrl}/Special:Diff/${change.revid}">edit</a> to `;
}
// Page link
if (change.ns === 3) {
html += `left a message for <a href="${baseUrl}/${encodeURIComponent(change.title)}">${change.title.replace("User talk:", "")}</a>`;
} else if (change.type === "new") {
html += `created the page <a href="${baseUrl}/${encodeURIComponent(change.title)}">${change.title}</a>`;
} else {
html += `<a href="${baseUrl}/${encodeURIComponent(change.title)}">${change.title}</a>`;
}
if (shouldBold) {
html += '</b>';
}
// Timestamp
const changeDate = new Date(change.timestamp);
const currDate = new Date();
const mildate = new Date(currDate - changeDate);
html += `<br><small class="rcwidget-date" data-revtimestamp="${change.timestamp}">` +
generateTimeAgo(mildate.getUTCHours(), mildate.getUTCMinutes(), mildate.getUTCSeconds()) +
'</small></li>';
return html;
} catch (error) {
console.error('Error generating change HTML:', error);
return '';
}
}
// Main widget functions
function addToSidebar(text) {
try {
const skin = mw.config.get("skin");
// Clean up any existing widgets
$('.rcwidget-instance').remove();
const widget = $('<div>')
.addClass('rcwidget-instance')
.html(text);
if (skin === "timeless") {
widget.addClass("sidebar-chunk")
.appendTo("#mw-related-navigation");
$("#catlinks-sidebar, #other-languages").appendTo("#mw-related-navigation");
} else if (skin === "vector" || skin === "vector-2022") {
widget.addClass("mw-portlet mw-portlet-navigation vector-menu vector-menu-portal")
.prependTo("#mw-panel");
} else {
widget.addClass("portlet")
.prependTo("#mw_portlets");
}
// Handle navigation elements
$("#p-navigation").prependTo("#mw-panel");
$("#p-search").prependTo("#quickbar");
$('#p-logo').prependTo("#mw-site-navigation, #mw-panel, #sidebar, #mw_portlets");
$('ul.hlist:first').appendTo('#mw-mf-page-left');
} catch (error) {
console.error('Error adding sidebar:', error);
}
}
function fetch() {
if (!document.hasFocus()) {
return;
}
$.get(mw.config.get("wgScriptPath") + "/api.php", {
action: "query",
format: "json",
list: "recentchanges",
rcnamespace: "0|3",
rcprop: "title|timestamp|flags|loginfo|oresscores|parsedcomment|user|ids|tags",
rcshow: "!bot" + (config.onlyshowores ? "|oresreview" : ""),
rctoponly: true,
rclimit: "50",
rctype: "edit|new"
})
.done(function(result) {
try {
if (result.error) {
throw new Error(result.error.info);
}
if (!result.query?.recentchanges) {
throw new Error('Invalid API response structure');
}
const changes = result.query.recentchanges
.filter(change => {
if (!config.onlyshowores) return true;
return change.oresscores?.damaging?.true >= config.orestolerance;
})
.map(createChangeHTML)
.filter(html => html);
let html = '<ul>' + changes.join('');
// Add "View all" link
html += `<li><a href="${mw.config.get("wgScript")}/Special:RecentChanges">View all recent changes</a></li></ul>`;
const $content = $("#rcwidget-content");
$content.html(changes.length ? html : "<ul><li>No recent changes found.</li></ul>");
} catch (error) {
console.error('Error processing recent changes:', error);
$("#rcwidget-content").html("<ul><li>Error loading recent changes.</li></ul>");
}
})
.fail(function(jqXHR, textStatus, errorThrown) {
console.error('API request failed:', textStatus, errorThrown);
$("#rcwidget-content").html("<ul><li>Error loading recent changes.</li></ul>");
});
}
function updateTimestamps() {
$('.rcwidget-date').each(function() {
try {
const $this = $(this);
const changeDate = new Date($this.data('revtimestamp'));
const currDate = new Date();
const mildate = new Date(currDate - changeDate);
$this.html(generateTimeAgo(
mildate.getUTCHours(),
mildate.getUTCMinutes(),
mildate.getUTCSeconds()
));
} catch (error) {
console.error('Error updating timestamp:', error);
}
});
}
function init() {
if (!window.jQuery || !window.mw) {
console.error('Required dependencies (jQuery or MediaWiki) not found');
return;
}
try {
const baseUrl = mw.config.get("wgScript");
const skin = mw.config.get('skin');
const widgetContent = (skin === 'vector' || skin === 'vector-2022') ?
`<div id="rcwidget-label" lang="en" dir="ltr">
<span><a href="${baseUrl}/Special:RecentChanges">Recent changes</a></span>
</div>
<div class="mw-portlet-body body pBody" id="rcwidget-content" style="height:250px;overflow:hidden;">
Loading...
</div>` :
`<h3 id="rcwidget-label" lang="en" dir="ltr">
<span><a href="${baseUrl}/Special:RecentChanges">Recent changes</a></span>
</h3>
<div class="mw-portlet-body body pBody" id="rcwidget-content" style="height:250px;overflow:hidden;">
Loading...
</div>`;
addToSidebar(widgetContent);
// Set up intervals
fetch();
fetchInterval = window.setInterval(fetch, config.fetchtime * 1000);
updateInterval = window.setInterval(updateTimestamps, config.fetchtime * 1000);
} catch (error) {
console.error('Error initializing widget:', error);
}
}
function cleanup() {
try {
if (fetchInterval) {
clearInterval(fetchInterval);
}
if (updateInterval) {
clearInterval(updateInterval);
}
$('.rcwidget-instance').remove();
} catch (error) {
console.error('Error cleaning up widget:', error);
}
}
// Public API
return {
init: init,
cleanup: cleanup,
fetch: fetch
};
})();
// Initialize widget when ready
$(document).ready(rcwidget.init);