MediaWiki:Gadget-markreview.js
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)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* review_ping in the template call.
```javascript
/* Gadget: MarkReview — One‑click "Mark Gadget: MarkReview — One‑click "Mark as reviewed" (updates review_ping) with robust injection as reviewed" (updates review_ping) with robust injection + diagnostics */
mw.loader.using( + diagnostics */
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'mediawiki.user', 'mediawiki.notify']).['mediawiki.api', 'mediawiki.util', 'mediawiki.user', 'mediawiki.notify']).then(function () {
function localStamp() {
varthen(function () {
function localStamp() {
var now = new Date();
return now.get now = new Date();
return now.getFullYear() + '-' +
String(nowFullYear() + '-' +
String(now.getMonth() + 1)..getMonth() + 1).padStart(2, '0')padStart(2, '0') + '-' +
String + '-' +
String(now.getDate()).padStart(2, '0')(now.getDate()).padStart(2, '0') + 'T' +
String + 'T' +
String(now.getHours()).padStart(2, '0')(now.getHours()). + ':' +
String(now.getMinutes()).padStart(2, '0') + ':' +
String(now.getMinutes()).padStart(2, '0')padStart(2, '0') + ':' +
String(now.getSeconds()).padStart(2, '0');
}
function findDocMeta($scope) {
+ ':' +
String(now.getSeconds()).padStart(2, '0');
}
function findDocMeta($scope) {
// Find either the table.doc-meta or any element with // Find either the table.doc-meta .doc-meta
var $el = $scope.find or any element with .doc-meta
var $el = $scope.find('table.doc-meta').first();
if('table.doc-meta').first();
if (!$el.length) $el = $scope.find (!$el.length) $el = $scope.find('.doc-meta').first();
if (!$el('.doc-meta').first();
if (!$el.length) $el = $('table.doc-meta, ..length) $el = $('table.doc-meta, .doc-meta').first();
return $el;
}
functiondoc-meta').first();
return $ injectButton($docel;
}
function injectButton($docMeta, onClick) {
var $table =Meta, onClick) {
var $table = $docMeta.is('table') ? $docMeta : $ $docMeta.is('table') ? $docMeta : $docMeta.closest('table');
if (!$docMeta.closest('table');
if (!$table.length) $tabletable.length) $table = $docMeta; // fallback = $docMeta; // fallback
// Use existing .doc-meta-actions
// Use existing .doc-meta-actions if present, else if present, else create
var $actions = $table create
var $actions = $table.nextAll('.doc-meta-actions').first();
if (!$actions.nextAll('.doc-meta-actions').first();
if (!$actions.length) {
.length) {
$actions = $('<div class="doc-meta-actions"></div>').insertAfter($table);
}
$actions = $('<div class="doc-meta-actions"></div>').insertAfter($table);
}
var $btn = $(' var $btn = $('<a>')
.attr('href', '#')
.text('Mark as reviewed<a>')
.attr('href', '#')
.text('Mark as reviewed')
.css({
display: 'inline-block',
')
.css({
display: 'inline-block',
marginTop: '8px',
marginTop: '8px',
padding: '4px 8px',
background padding: '4px 8px',
background: '#f0f0f0',
: '#f0f0f0',
border: '1px solid #ccc',
border border: '1px solid #ccc',
borderRadius: '2px',
Radius: '2px',
textDecoration: textDecoration: 'none'
})
'none'
})
.on('click', function (e) {
e.prevent .on('click', function (e) {
Default();
onClick();
e.preventDefault();
onClick();
});
$actions.empty().append($btn);
}
function });
$actions.empty().append($btn);
}
function updateReviewParam updateReviewParam(page, stamp) {
(page, stamp) {
var api = new mw.Api();
return api.get({
var api = new mw.Api();
return api.get({
action: 'query',
prop: 'revisions action: 'query',
prop: 'revisions',
titles: page,
rvslots',
titles: page,
rvslots: 'main',
rvprop: 'content|timestamp',
formatversion: 'main',
rvprop: 'content|timestamp',
formatversion: 2
}).then(function: 2
}).then(function (data) {
var (data) {
var pg = data.query.pages[0];
if (!pg || !pg.revisions || !pg.re pg = data.query.pages[0];
if (!pg || !pg.revisions || !pg.revisions.length) {
return $.Deferred().reject('No revisions found').promise();
visions.length) {
return $.Deferred().reject('No revisions found').promise();
}
var rev = pg.revisions[0];
var content }
var rev = pg.revisions[0];
var content = rev.slots.main = rev.slots.main.content;
var baseTs = rev.timestamp.content;
var baseTs = rev.timestamp;
// Find the first {{Doc;
// Find the first {{DocMeta ...}} blockMeta ...}} block
var docmeta
var docmetaRe = /\{\{\s*DocRe = /\{\{\s*DocMeta\b([\s\S]*?)Meta\b([\s\S]*?)\}\}/i;
var m = content.match\}\}/i;
var m = content.match(docmetaRe);
(docmetaRe);
if (!m) {
return $.Deferred if (!m) {
return $.Deferred().reject('DocMeta block not found').promise();
().reject('DocMeta block not found').promise();
}
var block = m[0];
var }
var block = m[0];
var inner = m[1];
// Replace or insert review_ping
var rp inner = m[1];
// Replace or insert review_ping
var rpRe = /(\|\s*reviewRe = /(\|\s*review_ping\s*=\s*)(.*)/i;
var newInner;
if _ping\s*=\s*)(.*)/i;
var newInner;
if (rpRe.test(inner)) {
newInner = inner.replace(rpRe.test(inner)) {
newInner = inner.replace(rpRe, '$1' + stamp(rpRe, '$1' + stamp);
} else {
// Ensure a newline before inserting
);
} else {
// Ensure a newline before inserting
if (!/\n$/.test(inner)) inner += if (!/\n$/.test(inner)) inner += '\n';
newInner = inner + ' | review_ping = '\n';
newInner = inner + ' | review_ping = ' + stamp;
}
var new ' + stamp;
}
var newBlock = block.replace(inner, newInner);
var newBlock = block.replace(inner, newInner);
var newContent = contentContent = content.replace(docmetaRe, newBlock);
return api.replace(docmetaRe, newBlock);
return api.postWithToken('csrf', {
action: 'edit',
.postWithToken('csrf', {
action: 'edit',
title: page,
text: new title: page,
text: newContent,
summary: 'Mark as reviewed',
Content,
summary: 'Mark as reviewed',
minor: true,
nocreate: true,
basetimestamp: baseTs
});
});
}
function init($content) {
var action = mw.config.get('wgAction') || 'view';
if (action !== 'view') return;
var $docMeta = findDocMeta($content);
if (!$docMeta.length) {
// Loud hint once per page view
mw.notify('MarkReview: DocMeta panel not found on this page.', { type: 'warn' });
return;
}
var owner = ($docMeta.attr('data-owner') || '').trim();
var user = (mw.config.get('wgUserName') || '').trim();
if (!owner) {
mw.notify('MarkReview: data-owner is missing on DocMeta.', { type: 'warn' });
return;
}
if (!user) {
// Not logged in — nothing to show
return;
}
// Normalize (case-insensitive); also accept underscores vs spaces
var norm = function (s) { return s.replace(/_/g, ' ').toLowerCase(); };
if (norm(owner) !== norm(user)) {
// Add a debug hint if URL contains ?reviewdebug=1
if (location.search.indexOf('reviewdebug=1') !== -1) {
mw.notify('MarkReview: owner mismatch. owner=' + owner + ' user=' + user, { type: 'warn' });
}
return;
}
injectButton($docMeta, function () {
var page = mw.config.get('wgPageName');
var stamp = localStamp();
updateReviewParam(page, stamp).done(function () {
// Optional success cue before reload
location.reload();
}).fail(function (err) {
mw.notify('MarkReview failed: ' + (err && err.error && err.error.info ? err.error.info : err), { type: 'error' });
});
});
}
// Run on initial load and after AJAX loads
mw.hook('wikipage.content').add(init);
// Also run once on DOM ready to catch any missed hook edge cases
$(init.bind(null, $(document)));
});