MediaWiki:Common.js: Difference between revisions
Appearance
No edit summary Tag: Reverted |
No edit summary Tag: Reverted |
||
| Line 338: | Line 338: | ||
/* ============================================================ | /* ============================================================ | ||
* Filter | * Sidebar Category Filter — Aggressive Mode | ||
* ============================================================ */ | * ============================================================ */ | ||
(function () { | ( function ( $ ) { | ||
'use strict'; | 'use strict'; | ||
var ALLOWED_SUFFIXES = [ 'Datasheet', 'User Guide', 'Video' ]; | var ALLOWED_SUFFIXES = [ 'Datasheet', 'User Guide', 'Video' ]; | ||
var PRODUCT_PREFIXES = [ 'LIGO AIR', 'LIGO PRO BLE AF', 'LIGO PRO BLE', 'LIGO PRO' ]; | |||
function shouldHide( text ) { | |||
text = text.trim(); | |||
for ( var i = 0; i < PRODUCT_PREFIXES.length; i++ ) { | |||
var prefix = PRODUCT_PREFIXES[ i ]; | |||
if ( text.indexOf( prefix + ' ' ) === 0 ) { | |||
var suffix = text.substring( prefix.length ).trim(); | |||
return ALLOWED_SUFFIXES.indexOf( suffix ) === -1; | |||
} | |||
} | |||
return false; | |||
} | |||
function | function filterSidebar() { | ||
var | // Target every link inside the left nav panel | ||
'#mw-panel, #vector-main-menu, .vector-main-menu, #p-categories' | var $candidates = $( | ||
'#mw-panel a, ' + | |||
'#vector-main-menu a, ' + | |||
'#vector-main-menu-panel a, ' + | |||
'.vector-main-menu a, ' + | |||
'#p-categories a, ' + | |||
'.mw-portlet-Categories a, ' + | |||
'.sidebar-chunk a' | |||
); | ); | ||
var link = | $candidates.each( function () { | ||
var $link = $( this ); | |||
var text = $link.text().trim(); | |||
if ( shouldHide( text ) ) { | |||
// Ẩn nguyên container cha (li, div, etc.) | |||
var $container = $link.closest( | |||
'li, .CategoryTreeItem, .mw-categoryTree-item, div.CategoryTreeSection' | |||
); | |||
if ( $container.length ) { | |||
var | $container.hide(); | ||
} else { | |||
$link.hide(); | |||
if ( | |||
} | } | ||
$link.attr( 'data-sidebar-hidden', '1' ); | |||
} | } | ||
}); | }); | ||
} | } | ||
// | function startFilter() { | ||
filterSidebar(); | |||
// Re-run nhiều lần vì CategoryTree load async | |||
setTimeout( filterSidebar, 300 ); | |||
setTimeout( filterSidebar, 1000 ); | |||
setTimeout( filterSidebar, 2500 ); | |||
setTimeout( filterSidebar, 5000 ); | |||
// MutationObserver bắt mọi thay đổi DOM trong sidebar | |||
if ( window.MutationObserver ) { | |||
var sidebarEls = document.querySelectorAll( | |||
'#mw-panel, #vector-main-menu-panel, .vector-main-menu' | |||
); | |||
sidebarEls.forEach( function ( el ) { | |||
new MutationObserver( filterSidebar ).observe( el, { | |||
childList: true, | |||
subtree: true | |||
}); | |||
}); | |||
} | |||
} | } | ||
$( startFilter ); | |||
$( window ).on( 'load', startFilter ); | |||
})( jQuery ); | |||
})(); | |||
Revision as of 03:17, 15 May 2026
(function () {
function esc(text) {
return String(text || "").replace(/[&<>"]/g, function (m) {
return { "&": "&", "<": "<", ">": ">", '"': """ }[m];
});
}
function renderLink(item, extraClass) {
var cls = extraClass ? ' class="' + extraClass + '"' : "";
var href = item.href ? esc(item.href) : "#";
return '<a' + cls + ' href="' + href + '">' + esc(item.label) + "</a>";
}
function buildFooter(data) {
var columns = (data.columns || []).map(function (col) {
var items = (col.items || []).map(function (item) {
return "<li>" + renderLink(item, "soji-footer-link") + "</li>";
}).join("");
return [
'<section class="soji-footer-col">',
"<h2>" + esc(col.title) + "</h2>",
"<ul>" + items + "</ul>",
"</section>"
].join("");
}).join("");
var policies = (data.policies || []).map(function (item, i) {
return (i ? '<span class="soji-footer-sep">|</span>' : "") +
renderLink(item, "soji-footer-link");
}).join("");
var social = (data.social || []).map(function (item) {
var href = item.href ? esc(item.href) : "#";
return '<a class="soji-footer-social-item" href="' + href + '" aria-label="' + esc(item.label) + '">' +
esc(item.short || item.label.charAt(0)) + "</a>";
}).join("");
var subscribe = data.subscribe
? renderLink(data.subscribe, "soji-footer-subscribe")
: "";
return [
'<section id="soji-site-footer" class="soji-site-footer">',
'<div class="soji-footer-shell">',
'<div class="soji-footer-grid">' + columns + "</div>",
'<div class="soji-footer-bottom">',
'<div class="soji-footer-meta">',
'<div class="soji-footer-policy-row">' + policies + "</div>",
'<div class="soji-footer-copy">' + esc(data.copyright || "") + "</div>",
"</div>",
'<div class="soji-footer-actions">',
subscribe,
'<div class="soji-footer-social">',
'<span class="soji-footer-social-label">Connect</span>',
'<div class="soji-footer-social-list">' + social + "</div>",
"</div>",
"</div>",
"</div>",
"</div>",
"</section>"
].join("");
}
function injectFooter(data) {
var footer = document.getElementById("footer");
if (!footer || document.getElementById("soji-site-footer")) return;
footer.insertAdjacentHTML("beforebegin", buildFooter(data));
document.body.classList.add("has-soji-footer");
}
function init() {
fetch("/index.php?title=MediaWiki:FooterData.json&action=raw")
.then(function (r) { return r.text(); })
.then(function (text) { return JSON.parse(text); })
.then(injectFooter)
.catch(function (err) { console.error("Footer load failed:", err); });
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
})();
$(function () {
if (mw.config.get('wgNamespaceNumber') < 0) return;
var title = mw.config.get('wgTitle');
var pageName = mw.config.get('wgPageName').replace(/_/g, ' ');
// Page Main Page: không cần breadcrumb
if (pageName === 'Main Page') return;
// Lấy category trực tiếp của page hoặc của category page hiện tại
var initialCats = mw.config.get('wgCategories') || [];
if (!initialCats.length) return;
var api = new mw.Api();
var chain = [];
var seen = {};
// Đi ngược lên cây qua API
function walkUp(catName) {
if (!catName || seen[catName] || catName === 'Main Page') {
return $.Deferred().resolve();
}
seen[catName] = true;
chain.unshift(catName);
return api.get({
action: 'query',
titles: 'Category:' + catName,
prop: 'categories',
cllimit: 1,
format: 'json',
formatversion: 2
}).then(function (data) {
var pages = (data.query && data.query.pages) || [];
if (!pages.length || !pages[0].categories) return;
var parent = pages[0].categories[0].title.replace(/^Category:/, '');
return walkUp(parent);
});
}
walkUp(initialCats[0]).then(function () {
var parts = [
'<a href="' + mw.util.getUrl('Main Page') + '">Main Page</a>'
];
chain.forEach(function (c) {
parts.push('<a href="' + mw.util.getUrl(c) + '">' + c + '</a>');
});
parts.push('<strong>' + title + '</strong>');
var html = '<nav class="custom-breadcrumb">' + parts.join(' > ') + '</nav>';
$('#mw-content-text').before(html);
});
});
$(function () {
// Tự động thêm target="_blank" cho mọi link tới file .pdf
$('a[href$=".pdf"], a[href*=".pdf?"], a[href*="/file/"][href*=".pdf"]').each(function () {
$(this).attr('target', '_blank');
$(this).attr('rel', 'noopener noreferrer');
// Thêm icon PDF nhỏ
if (!$(this).find('.pdf-icon').length) {
$(this).append(' <span class="pdf-icon" title="Open PDF in new tab">📄</span>');
}
});
});
$(function () {
// Force "View" links to open in new tab
$('.doc-view a').attr({
target: '_blank',
rel: 'noopener noreferrer'
});
// Force "Download" links to trigger file download
$('.doc-download a').each(function () {
var $a = $(this);
var href = $a.attr('href');
// Extract filename from URL
var match = href.match(/\/([^\/]+\.(?:pdf|zip|doc|docx|xls|xlsx))$/i);
if (match) {
$a.attr('download', match[1]);
} else {
$a.attr('download', '');
}
});
});
/* ============================================
* Auto-collapse TOC sub-headings on page load
* Only show H2 (main Heading) by default
* ============================================ */
$(function () {
var attempts = 0;
var maxAttempts = 15;
function collapseAllTOC() {
// Find all expanded toggle buttons in TOC
var $toggles = $('.vector-toc-toggle[aria-expanded="true"]');
if ($toggles.length > 0) {
$toggles.each(function () {
// Click toggle to collapse (triggers Vue.js handler)
this.click();
});
} else if (attempts < maxAttempts) {
// TOC chưa render xong, retry
attempts++;
setTimeout(collapseAllTOC, 100);
}
}
collapseAllTOC();
});
/* ============================================
* Sync chevron arrow with collapsible state
* ============================================ */
$(function () {
$('.category-block-toggle span[class*="mw-customtoggle-cb-"]').each(function () {
var $toggle = $(this);
var match = $toggle.attr('class').match(/mw-customtoggle-(\S+)/);
if (!match) return;
var $target = $('#mw-customcollapsible-' + match[1]);
if (!$target.length) return;
function syncArrow() {
if ($target.hasClass('mw-collapsed')) {
$toggle.removeClass('is-expanded');
} else {
$toggle.addClass('is-expanded');
}
}
// Initial state
syncArrow();
// Update on click
$toggle.on('click', function () {
setTimeout(syncArrow, 50);
});
});
});
/* ============================================
* Pending Changes Badge for Reviewers
* Show notification of pending changes count
* ============================================ */
$(function () {
// Chỉ show cho user có quyền review (reviewer, sysop, admin)
var userGroups = mw.config.get('wgUserGroups') || [];
var hasReviewRights = userGroups.some(function (g) {
return ['reviewer', 'sysop', 'bureaucrat'].indexOf(g) !== -1;
});
if (!hasReviewRights) {
return;
}
// Fetch pending count from API
function fetchPendingCount() {
new mw.Api().get({
action: 'query',
list: 'oldreviewedpages',
ornamespace: '0|14',
orfilterredir: 'nonredirects',
orlimit: 500,
format: 'json'
}).done(function (data) {
var pages = (data.query && data.query.oldreviewedpages) || [];
updateBadge(pages.length);
}).fail(function (err) {
console.log('Failed to fetch pending changes:', err);
});
}
// Update or create badge in sidebar
function updateBadge(count) {
var $badge = $('#pending-reviews-badge');
if (!$badge.length) {
// Tạo badge mới — tìm sidebar và prepend
var badgeHtml =
'<div id="pending-reviews-badge" style="' +
'padding: 12px 16px;' +
'margin: 12px 8px;' +
'background: #fff3e0;' +
'border: 1px solid #f57c00;' +
'border-left: 4px solid #e65100;' +
'border-radius: 4px;' +
'cursor: pointer;' +
'transition: background 0.2s;' +
'">' +
'<a href="' + mw.util.getUrl('Special:PendingChanges') + '" style="' +
'color: #e65100;' +
'text-decoration: none;' +
'font-weight: 600;' +
'font-size: 0.95em;' +
'display: block;' +
'">' +
'⚠️ <span id="pending-count" style="' +
'display: inline-block;' +
'background: #e65100;' +
'color: white;' +
'padding: 2px 10px;' +
'border-radius: 12px;' +
'margin: 0 6px;' +
'font-weight: 700;' +
'">0</span>' +
'pending review' +
'</a>' +
'</div>';
// Try multiple sidebar selectors (Vector 2022 + legacy)
var $sidebar = $('#mw-panel .vector-main-menu-content')
.add('#mw-panel-toc-list')
.add('#mw-panel')
.add('.vector-main-menu')
.first();
if ($sidebar.length) {
$sidebar.prepend(badgeHtml);
$badge = $('#pending-reviews-badge');
} else {
// Fallback: insert at top of body
$('body').prepend(
'<div style="position:fixed;top:60px;right:20px;z-index:9999;">' +
badgeHtml + '</div>'
);
$badge = $('#pending-reviews-badge');
}
}
$('#pending-count').text(count);
if (count === 0) {
$badge.hide();
} else {
$badge.show();
}
}
// Initial fetch
fetchPendingCount();
// Refresh every 60 seconds
setInterval(fetchPendingCount, 60000);
// Also refresh on focus (when user comes back to tab)
$(window).on('focus', fetchPendingCount);
});
/* ============================================================
* Sidebar Category Filter — Aggressive Mode
* ============================================================ */
( function ( $ ) {
'use strict';
var ALLOWED_SUFFIXES = [ 'Datasheet', 'User Guide', 'Video' ];
var PRODUCT_PREFIXES = [ 'LIGO AIR', 'LIGO PRO BLE AF', 'LIGO PRO BLE', 'LIGO PRO' ];
function shouldHide( text ) {
text = text.trim();
for ( var i = 0; i < PRODUCT_PREFIXES.length; i++ ) {
var prefix = PRODUCT_PREFIXES[ i ];
if ( text.indexOf( prefix + ' ' ) === 0 ) {
var suffix = text.substring( prefix.length ).trim();
return ALLOWED_SUFFIXES.indexOf( suffix ) === -1;
}
}
return false;
}
function filterSidebar() {
// Target every link inside the left nav panel
var $candidates = $(
'#mw-panel a, ' +
'#vector-main-menu a, ' +
'#vector-main-menu-panel a, ' +
'.vector-main-menu a, ' +
'#p-categories a, ' +
'.mw-portlet-Categories a, ' +
'.sidebar-chunk a'
);
$candidates.each( function () {
var $link = $( this );
var text = $link.text().trim();
if ( shouldHide( text ) ) {
// Ẩn nguyên container cha (li, div, etc.)
var $container = $link.closest(
'li, .CategoryTreeItem, .mw-categoryTree-item, div.CategoryTreeSection'
);
if ( $container.length ) {
$container.hide();
} else {
$link.hide();
}
$link.attr( 'data-sidebar-hidden', '1' );
}
});
}
function startFilter() {
filterSidebar();
// Re-run nhiều lần vì CategoryTree load async
setTimeout( filterSidebar, 300 );
setTimeout( filterSidebar, 1000 );
setTimeout( filterSidebar, 2500 );
setTimeout( filterSidebar, 5000 );
// MutationObserver bắt mọi thay đổi DOM trong sidebar
if ( window.MutationObserver ) {
var sidebarEls = document.querySelectorAll(
'#mw-panel, #vector-main-menu-panel, .vector-main-menu'
);
sidebarEls.forEach( function ( el ) {
new MutationObserver( filterSidebar ).observe( el, {
childList: true,
subtree: true
});
});
}
}
$( startFilter );
$( window ).on( 'load', startFilter );
})( jQuery );