MediaWiki:Common.js: Difference between revisions
Appearance
Deploy strict JS-rendered article creator with case-insensitive validation and category pills |
Change editintro from Template:Create_Intro to ZelocoreCMS:Style_Guide  |
||
| (2 intermediate revisions by the same user not shown) | |||
| Line 218: | Line 218: | ||
// --- Strict Create Article App --- | // --- Strict Create Article App --- | ||
(function( | mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function() { | ||
   $(function() { |    $(function() { | ||
     var $app = $('#strict-create-app'); |      var $app = $('#strict-create-app'); | ||
| Line 234: | Line 234: | ||
         '<div id="sc-cats"></div>' + |          '<div id="sc-cats"></div>' + | ||
         '<button id="sc-btn" disabled>đ Continue to Editor</button>' + |          '<button id="sc-btn" disabled>đ Continue to Editor</button>' + | ||
         '<div style="text-align:center;margin-top:8px;font-size:0.85em;"><a href="' + mw.util.getUrl('ZelocoreCMS: |          '<div style="text-align:center;margin-top:8px;font-size:0.85em;"><a href="' + mw.util.getUrl('ZelocoreCMS:Style_Guide') + '">Read article style guide</a></div>' + | ||
         '</div>'; |          '</div>'; | ||
       $app.html(html); |        $app.html(html); | ||
| Line 302: | Line 302: | ||
             action: 'query', |              action: 'query', | ||
             list: 'search', |              list: 'search', | ||
             srsearch: |              srsearch: val, | ||
             format: 'json' |              format: 'json' | ||
           }).done(function(data) { |            }).done(function(data) { | ||
| Line 343: | Line 343: | ||
           action: 'edit', |            action: 'edit', | ||
           preload: 'Template:New_CMS_Draft', |            preload: 'Template:New_CMS_Draft', | ||
           editintro: ' |            editintro: 'ZelocoreCMS:Style_Guide', | ||
           prefill_cats: selectedCats.join(',') |            prefill_cats: selectedCats.join(',') | ||
         }); |          }); | ||
| Line 378: | Line 378: | ||
     } |      } | ||
   }); |    }); | ||
} | }); | ||
// --- End Strict Create App --- | // --- End Strict Create App --- | ||
Latest revision as of 22:55, 29 July 2026
/* ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ
ZelocoreCMS Wiki â Enhanced Common.js (v2.0 world-class upgrade)
Features: dark mode, animated counters, helpful widget,
keyboard shortcuts, reading progress, back-to-top
ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ */
( function () {
'use strict';
var qs = function ( s, ctx ) { return ( ctx || document ).querySelector( s ); };
var qsa = function ( s, ctx ) { return Array.from( ( ctx || document ).querySelectorAll( s ) ); };
function ready( fn ) {
if ( document.readyState !== 'loading' ) { fn(); }
else { document.addEventListener( 'DOMContentLoaded', fn ); }
}
/* ââ Reading progress bar âââââââââââââââââââââââââââ */
function initProgress() {
var bar = document.createElement( 'div' );
bar.id = 'cms-progress';
document.body.prepend( bar );
window.addEventListener( 'scroll', function () {
var h = document.documentElement;
var pct = ( h.scrollTop / ( h.scrollHeight - h.clientHeight ) ) * 100;
bar.style.width = Math.min( pct, 100 ) + '%';
}, { passive: true } );
}
/* ââ Back to top ââââââââââââââââââââââââââââââââââââ */
function initBackTop() {
var btn = document.createElement( 'button' );
btn.id = 'cms-back-top';
btn.title = 'Back to top';
btn.innerHTML = 'â';
document.body.appendChild( btn );
window.addEventListener( 'scroll', function () {
btn.classList.toggle( 'visible', window.scrollY > 400 );
}, { passive: true } );
btn.addEventListener( 'click', function () {
window.scrollTo( { top: 0, behavior: 'smooth' } );
} );
}
/* ââ Dark mode toggle âââââââââââââââââââââââââââââââ */
function initDarkMode() {
var btn = document.createElement( 'button' );
btn.id = 'cms-dark-toggle';
btn.title = 'Toggle dark mode';
btn.innerHTML = 'đ';
document.body.appendChild( btn );
var isDark = localStorage.getItem( 'cms-dark' ) === '1';
if ( isDark ) { document.body.classList.add( 'cms-dark-mode' ); btn.innerHTML = 'âī¸'; }
btn.addEventListener( 'click', function () {
isDark = !isDark;
document.body.classList.toggle( 'cms-dark-mode', isDark );
btn.innerHTML = isDark ? 'âī¸' : 'đ';
localStorage.setItem( 'cms-dark', isDark ? '1' : '0' );
} );
}
/* ââ Animated number counters (main page stats) âââââ */
function animateCounter( el, target ) {
var start = 0;
var duration = 1400;
var startTime = null;
function step( ts ) {
if ( !startTime ) { startTime = ts; }
var progress = Math.min( ( ts - startTime ) / duration, 1 );
var ease = 1 - Math.pow( 1 - progress, 3 );
el.textContent = Math.floor( ease * target ).toLocaleString();
if ( progress < 1 ) { requestAnimationFrame( step ); }
else { el.textContent = target.toLocaleString(); }
}
requestAnimationFrame( step );
}
function initCounters() {
qsa( '.mp-stat-num' ).forEach( function ( el ) {
var raw = el.textContent.replace( /,/g, '' );
var num = parseInt( raw, 10 );
if ( !isNaN( num ) && num > 0 ) {
var obs = new IntersectionObserver( function ( entries, o ) {
if ( entries[0].isIntersecting ) {
animateCounter( el, num );
o.disconnect();
}
}, { threshold: 0.3 } );
obs.observe( el );
}
} );
}
/* ââ "Was this helpful?" widget âââââââââââââââââââââ */
function initHelpfulWidget() {
var body = qs( '#mw-content-text .mw-parser-output' );
if ( !body ) { return; }
// Only on article pages, not main page
if ( document.body.classList.contains( 'page-Main_Page' ) ) { return; }
if ( !mw.config.get( 'wgIsArticle' ) ) { return; }
var widget = document.createElement( 'div' );
widget.id = 'cms-helpful';
widget.innerHTML = [
'<span id="cms-helpful-label">Was this article helpful?</span>',
'<button class="cms-helpful-btn yes" data-v="yes">đ Yes</button>',
'<button class="cms-helpful-btn no" data-v="no">đ No</button>',
'<span id="cms-helpful-thanks">Thanks for your feedback! đ</span>'
].join( '' );
body.appendChild( widget );
qsa( '.cms-helpful-btn', widget ).forEach( function ( btn ) {
btn.addEventListener( 'click', function () {
qsa( '.cms-helpful-btn', widget ).forEach( function ( b ) { b.classList.remove( 'active' ); } );
btn.classList.add( 'active' );
qs( '#cms-helpful-thanks' ).style.display = 'inline';
} );
} );
}
/* ââ Keyboard shortcut: Ctrl+K = focus search âââââââ */
function initSearchShortcut() {
document.addEventListener( 'keydown', function ( e ) {
if ( ( e.ctrlKey || e.metaKey ) && e.key === 'k' ) {
e.preventDefault();
var input = qs( '#searchInput, .cdx-text-input__input' );
if ( input ) { input.focus(); input.select(); }
}
} );
}
/* ââ Copy buttons on <pre> blocks âââââââââââââââââââ */
function addCopyButtons() {
qsa( 'pre' ).forEach( function ( pre ) {
if ( qs( '.cms-copy-btn', pre ) ) { return; }
var btn = document.createElement( 'button' );
btn.className = 'cms-copy-btn';
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:8px;right:10px;padding:3px 10px;' +
'background:rgba(255,255,255,.15);color:#e2e8f0;border:1px solid rgba(255,255,255,.25);' +
'border-radius:4px;font-size:.78em;cursor:pointer;transition:background .2s;';
btn.addEventListener( 'mouseenter', function () { btn.style.background = 'rgba(255,255,255,.28)'; } );
btn.addEventListener( 'mouseleave', function () { btn.style.background = 'rgba(255,255,255,.15)'; } );
btn.addEventListener( 'click', function () {
navigator.clipboard.writeText( pre.innerText ).then( function () {
btn.textContent = 'â
Copied!';
setTimeout( function () { btn.textContent = 'Copy'; }, 2000 );
} );
} );
pre.style.position = 'relative';
pre.appendChild( btn );
} );
}
/* ââ Scroll reveal for portal cells ââââââââââââââââ */
function initScrollReveal() {
if ( !window.IntersectionObserver ) { return; }
var obs = new IntersectionObserver( function ( entries ) {
entries.forEach( function ( entry ) {
if ( entry.isIntersecting ) {
entry.target.style.opacity = '1';
entry.target.style.transform = 'translateY(0)';
obs.unobserve( entry.target );
}
} );
}, { threshold: 0.06 } );
qsa( '.mp-portal-cell, .mp-cat-card, .mp-card, .mp-stat' ).forEach( function ( el ) {
el.style.opacity = '0';
el.style.transform = 'translateY(14px)';
el.style.transition = 'opacity 0.4s ease, transform 0.4s ease';
obs.observe( el );
} );
}
/* ââ Table row highlight ââââââââââââââââââââââââââââ */
function initTableHighlight() {
qsa( '.wikitable tbody tr' ).forEach( function ( row ) {
row.style.transition = 'background .15s';
} );
}
/* ââ External link icon âââââââââââââââââââââââââââââ */
function initExternalLinks() {
qsa( '.mw-body-content a.external' ).forEach( function ( a ) {
if ( !qs( '.cms-ext', a ) ) {
var s = document.createElement( 'span' );
s.className = 'cms-ext';
s.setAttribute( 'aria-hidden', 'true' );
s.textContent = ' â';
s.style.cssText = 'font-size:.72em;color:#7c3aed;margin-left:1px;';
a.appendChild( s );
}
} );
}
/* ââ Bootstrap ââââââââââââââââââââââââââââââââââââââ */
ready( function () {
initProgress();
initBackTop();
initDarkMode();
initCounters();
initSearchShortcut();
initScrollReveal();
initTableHighlight();
initExternalLinks();
setTimeout( addCopyButtons, 500 );
setTimeout( initHelpfulWidget, 300 );
if ( mw && mw.hook ) {
mw.hook( 've.deactivate' ).add( function () {
setTimeout( addCopyButtons, 600 );
} );
}
} );
}() );
// --- Strict Create Article App ---
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function() {
$(function() {
var $app = $('#strict-create-app');
if ($app.length) {
var categories = [
'Blogging_CMS', 'Headless_CMS', 'ECommerce_CMS',
'Enterprise_CMS', 'Open_Source_CMS', 'SaaS_CMS', 'Wiki_Platforms'
];
var html = '<div class="strict-creator">' +
'<label class="sc-main-label">1. Platform Name</label>' +
'<input type="text" id="sc-title" placeholder="e.g. Contentful" autocomplete="off">' +
'<div id="sc-status"></div>' +
'<label class="sc-main-label">2. Select Categories (1 to 5)</label>' +
'<div id="sc-cats"></div>' +
'<button id="sc-btn" disabled>đ Continue to Editor</button>' +
'<div style="text-align:center;margin-top:8px;font-size:0.85em;"><a href="' + mw.util.getUrl('ZelocoreCMS:Style_Guide') + '">Read article style guide</a></div>' +
'</div>';
$app.html(html);
var $cats = $('#sc-cats');
categories.forEach(function(cat) {
$cats.append('<label class="sc-cat-label"><input type="checkbox" value="'+cat+'"> '+cat.replace(/_/g, ' ')+'</label>');
});
var $title = $('#sc-title');
var $status = $('#sc-status');
var $btn = $('#sc-btn');
var $checkboxes = $cats.find('input[type="checkbox"]');
var isTitleValid = false;
var debounceTimer;
function updateBtn() {
var checkedCount = $checkboxes.filter(':checked').length;
// Handle styling and max limits
$checkboxes.each(function() {
var $lbl = $(this).parent();
if ($(this).is(':checked')) {
$lbl.addClass('is-checked').removeClass('is-disabled');
$(this).prop('disabled', false);
} else {
$lbl.removeClass('is-checked');
if (checkedCount >= 5) {
$lbl.addClass('is-disabled');
$(this).prop('disabled', true);
} else {
$lbl.removeClass('is-disabled');
$(this).prop('disabled', false);
}
}
});
if (isTitleValid && checkedCount > 0 && checkedCount <= 5) {
$btn.prop('disabled', false);
} else {
$btn.prop('disabled', true);
}
}
$checkboxes.on('change', updateBtn);
$title.on('input', function() {
var val = $(this).val().trim();
clearTimeout(debounceTimer);
isTitleValid = false;
updateBtn();
if (val.length === 0) {
$status.html('');
$title.css('border-color', '#cbd5e1');
return;
}
$status.html('âŗ Checking for exact matches...').css('color', '#72777d');
$title.css('border-color', '#cbd5e1');
debounceTimer = setTimeout(function() {
var api = new mw.Api();
// Use search API which is case-insensitive
api.get({
action: 'query',
list: 'search',
srsearch: val,
format: 'json'
}).done(function(data) {
var results = data.query.search;
var exactMatch = null;
// Strict case-insensitive check against all results
for (var i = 0; i < results.length; i++) {
if (results[i].title.toLowerCase() === val.toLowerCase()) {
exactMatch = results[i].title;
break;
}
}
if (exactMatch) {
$status.html('â ī¸ Already exists as "<b>' + exactMatch + '</b>". <a href="' + mw.util.getUrl(exactMatch) + '">Edit it</a>').css('color', '#d33');
$title.css('border-color', '#d33');
} else {
isTitleValid = true;
$status.html('â
Available!').css('color', '#00af89');
$title.css('border-color', '#00af89');
updateBtn();
}
}).fail(function() {
$status.html('â API error').css('color', '#d33');
});
}, 500);
});
$btn.on('click', function() {
var finalTitle = $title.val().trim();
finalTitle = finalTitle.charAt(0).toUpperCase() + finalTitle.slice(1);
var selectedCats = [];
$checkboxes.filter(':checked').each(function() {
selectedCats.push($(this).val());
});
var url = mw.util.getUrl(finalTitle, {
action: 'edit',
preload: 'Template:New_CMS_Draft',
editintro: 'ZelocoreCMS:Style_Guide',
prefill_cats: selectedCats.join(',')
});
window.location.href = url;
});
}
// --- Edit Page Category Injector ---
if (mw.config.get('wgAction') === 'edit') {
var urlParams = new URLSearchParams(window.location.search);
var cats = urlParams.get('prefill_cats');
if (cats) {
// Wait briefly for textarea to be ready
setTimeout(function() {
var $textbox = $('#wpTextbox1');
if ($textbox.length) {
var currentVal = $textbox.val();
// Prevent duplicate injections on refresh
if (currentVal.indexOf('[[Category:CMS_Platforms]]') === -1) {
var catString = '\n\n[[Category:CMS_Platforms]]\n';
var catArray = cats.split(',');
catArray.forEach(function(c) {
catString += '[[Category:' + c + ']]\n';
});
$textbox.val(currentVal.trim() + catString);
}
// Clean the URL so refresh doesn't trigger it again
var cleanUrl = window.location.protocol + "//" + window.location.host + window.location.pathname + '?title=' + encodeURIComponent(mw.config.get('wgPageName')) + '&action=edit';
window.history.replaceState({path:cleanUrl}, '', cleanUrl);
}
}, 500);
}
}
});
});
// --- End Strict Create App ---