MediaWiki:Gadget-markreview.js: Difference between revisions
Jump to navigation
Jump to search
No edit summary Tag: Reverted |
No edit summary Tag: Reverted |
||
| Line 1: | Line 1: | ||
/* | /* 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', '#') | .attr('href', '#') | ||
.text('Mark as reviewed') | .text('Mark as reviewed') | ||
.css({ | |||
display: 'inline-block', | |||
') | |||
.css({ | .css({ | ||
display: 'inline-block', | display: 'inline-block', | ||
marginTop: '8px', | marginTop: '8px', | ||
marginTop: '8px', | |||
padding: '4px 8px', | padding: '4px 8px', | ||
background padding: '4px 8px', | |||
background: '#f0f0f0', | background: '#f0f0f0', | ||
: '#f0f0f0', | |||
border: '1px solid #ccc', | border: '1px solid #ccc', | ||
border border: '1px solid #ccc', | |||
borderRadius: '2px', | borderRadius: '2px', | ||
textDecoration: 'none' | Radius: '2px', | ||
textDecoration: textDecoration: 'none' | |||
}) | |||
'none' | |||
}) | }) | ||
.on('click', function (e) { | .on('click', function (e) { | ||
e.preventDefault(); | e.prevent .on('click', function (e) { | ||
Default(); | |||
onClick(); | |||
e.preventDefault(); | |||
onClick(); | |||
}); | }); | ||
// | $actions.empty().append($btn); | ||
} | |||
if (!$ | |||
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 | // Run on initial load and after AJAX loads | ||
mw.hook('wikipage.content').add(run); | mw.hook('wikipage.content').add(init); | ||
// Also run once on DOM ready to catch any missed hook edge cases | |||
$(init.bind(null, $(document))); | |||
}); | }); | ||
Revision as of 09:08, 9 September 2025
/* 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)));
});