MediaWiki:Gadget-markreview.js: Difference between revisions

From Taranaki Lab Manual
Jump to navigation Jump to search
No edit summary
Tag: Reverted
No edit summary
Tag: Reverted
Line 1: Line 1:
/* Gadget: MarkReview — One‑click "Mark as reviewed" for page owners */
/* Gadget: MarkReview — One‑click "Mark as reviewed" for page owners (updates DocMeta parameter) */
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'mediawiki.user']).then(function () {
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'mediawiki.user']).then(function () {


    function run($content) {
  function run($content) {
        var $docMeta = $content.find('.doc-meta').first();
    var $docMeta = $content.find('.doc-meta').first();
        if (!$docMeta.length) {
    if (!$docMeta.length) {
            $docMeta = $('.doc-meta').first();
      $docMeta = $('.doc-meta').first();
            if (!$docMeta.length) return;
      if (!$docMeta.length) return;
    }
 
    var owner = $docMeta.attr('data-owner');
    var user = mw.config.get('wgUserName');
    var action = mw.config.get('wgAction') || 'view';
    if (action !== 'view') return;
    if (!owner || !user || owner.toLowerCase() !== user.toLowerCase()) return;
 
    function localStamp() {
      var now = new Date();
      return now.getFullYear() + '-' +
        String(now.getMonth() + 1).padStart(2, '0') + '-' +
        String(now.getDate()).padStart(2, '0') + 'T' +
        String(now.getHours()).padStart(2, '0') + ':' +
        String(now.getMinutes()).padStart(2, '0') + ':' +
        String(now.getSeconds()).padStart(2, '0');
    }
 
    function doReview() {
      var page = mw.config.get('wgPageName');
      var stamp = localStamp();
      var api = new mw.Api();
 
      api.get({
        action: 'query',
        prop: 'revisions',
        titles: page,
        rvslots: 'main',
        rvprop: 'content|timestamp',
        formatversion: 2
      }).done(function (data) {
        var pg = data.query.pages[0];
        if (!pg || !pg.revisions || !pg.revisions.length) {
          mw.notify('Could not read page content', { type: 'error' });
          return;
        }
        var rev = pg.revisions[0];
        var content = rev.slots.main.content;
        var baseTs = rev.timestamp;
 
        // Find the first {{DocMeta ...}} block (non-greedy to the first }}
        var docmetaRe = /\{\{\s*DocMeta\b([\s\S]*?)\}\}/i;
        var m = content.match(docmetaRe);
        if (!m) {
          mw.notify('DocMeta block not found on this page', { type: 'error' });
          return;
         }
         }


         var owner = $docMeta.attr('data-owner');
         var block = m[0];
         var user = mw.config.get('wgUserName');
         var inner = m[1]; // content between 'DocMeta' and closing '}}'
        var action = mw.config.get('wgAction') || 'view';


         if (action !== 'view') return;
         // Normalise newlines
         if (!owner || !user || owner.toLowerCase() !== user.toLowerCase()) return;
         var nl = /\r\n/.test(block) ? '\r\n' : '\n';


         function doReview() {
         // Does review_ping already exist?
            var page = mw.config.get('wgPageName');
        var rpRe = /(\|\s*review_ping\s*=\s*)(.*)/i;
            var now = new Date();
        var newInner;
            var stamp = now.getFullYear() + '-' +
                String(now.getMonth() + 1).padStart(2, '0') + '-' +
                String(now.getDate()).padStart(2, '0') + 'T' +
                String(now.getHours()).padStart(2, '0') + ':' +
                String(now.getMinutes()).padStart(2, '0') + ':' +
                String(now.getSeconds()).padStart(2, '0');


             var api = new mw.Api();
        if (rpRe.test(inner)) {
          // Replace its value only on that line
          newInner = inner.replace(rpRe, function(_, prefix) {
             return prefix + stamp;
          });
        } else {
          // Insert a new line with review_ping before the closing }}
          // Preserve indentation if present
          var indentMatch = inner.match(/\n([ \t]*)\|/);
          var indent = indentMatch ? indentMatch[1] : ' ';
          newInner = inner.replace(/[\s\S]*$/, function (s) {
            // Ensure trailing newline inside the template block
            if (!/\n$/.test(s)) s += nl;
            return s + indent + '| review_ping = ' + stamp + nl;
          });
        }


            // Get current page content
        var newBlock = block.replace(inner, newInner);
            api.get({
        var newContent = content.replace(docmetaRe, newBlock);
                action: 'query',
                prop: 'revisions',
                titles: page,
                rvslots: 'main',
                rvprop: 'content',
                formatversion: 2
            }).done(function (data) {
                var content = data.query.pages[0].revisions[0].slots.main.content;
                var newContent;


                // Replace review_ping parameter if it exists, otherwise add it
        // Remove any old HTML comment pings (optional cleanup)
                if (content.match(/(\|\s*review_ping\s*=\s*).*$/m)) {
        newContent = newContent.replace(/<!--\s*review-ping:.*?-->\s*/gi, '');
                    newContent = content.replace(/(\|\s*review_ping\s*=\s*).*$/m, '$1' + stamp);
                } else {
                    newContent = content.replace(/(\{\{DocMeta[^\n]*)/, '$1\n | review_ping = ' + stamp);
                }


                api.postWithToken('csrf', {
        api.postWithToken('csrf', {
                    action: 'edit',
          action: 'edit',
                    title: page,
          title: page,
                    text: newContent,
          text: newContent,
                    summary: 'Mark as reviewed',
          summary: 'Mark as reviewed',
                    minor: true,
          minor: true,
                    nocreate: true
          nocreate: true,
                }).done(function () {
          basetimestamp: baseTs, // basic edit-conflict protection
                    location.reload();
          starttimestamp: data.curtimestamp
                }).fail(function (err) {
        }).done(function () {
                    mw.notify('Review failed: ' + (err && err.error && err.error.info ? err.error.info : err), { type: 'error' });
          location.reload();
                });
        }).fail(function (err) {
            });
          mw.notify('Review failed: ' + (err && err.error && err.error.info ? err.error.info : err), { type: 'error' });
        }
        });
      }).fail(function (err) {
        mw.notify('Failed to fetch page: ' + err, { type: 'error' });
      });
    }


        // Create the button
    // Create the button
        var $btn = $('<a>')
    var $btn = $('<a>')
            .attr('href', '#')
      .attr('href', '#')
            .text('Mark as reviewed')
      .text('Mark as reviewed')
            .css({
      .css({
                display: 'inline-block',
        display: 'inline-block',
                marginTop: '8px',
        marginTop: '8px',
                padding: '4px 8px',
        padding: '4px 8px',
                background: '#f0f0f0',
        background: '#f0f0f0',
                border: '1px solid #ccc',
        border: '1px solid #ccc',
                borderRadius: '2px',
        borderRadius: '2px',
                textDecoration: 'none'
        textDecoration: 'none'
            })
      })
            .on('click', function (e) {
      .on('click', function (e) {
                e.preventDefault();
        e.preventDefault();
                doReview();
        doReview();
            });
      });


        // Place it under the DocMeta table
    // Place it under the DocMeta table
        var $actions = $docMeta.closest('table').nextAll('.doc-meta-actions').first();
    var $actions = $docMeta.closest('table').nextAll('.doc-meta-actions').first();
        if (!$actions.length) {
    if (!$actions.length) {
            $actions = $('<div class="doc-meta-actions"></div>').insertAfter($docMeta.closest('table'));
      $actions = $('<div class="doc-meta-actions"></div>').insertAfter($docMeta.closest('table'));
        }
        $actions.empty().append($btn);
     }
     }
    $actions.empty().append($btn);
  }


    mw.hook('wikipage.content').add(run);
  mw.hook('wikipage.content').add(run);
});
});

Revision as of 08:58, 9 September 2025

/* Gadget: MarkReview — One‑click "Mark as reviewed" for page owners (updates DocMeta parameter) */
mw.loader.using(['mediawiki.api', 'mediawiki.util', 'mediawiki.user']).then(function () {

  function run($content) {
    var $docMeta = $content.find('.doc-meta').first();
    if (!$docMeta.length) {
      $docMeta = $('.doc-meta').first();
      if (!$docMeta.length) return;
    }

    var owner = $docMeta.attr('data-owner');
    var user = mw.config.get('wgUserName');
    var action = mw.config.get('wgAction') || 'view';
    if (action !== 'view') return;
    if (!owner || !user || owner.toLowerCase() !== user.toLowerCase()) return;

    function localStamp() {
      var now = new Date();
      return now.getFullYear() + '-' +
        String(now.getMonth() + 1).padStart(2, '0') + '-' +
        String(now.getDate()).padStart(2, '0') + 'T' +
        String(now.getHours()).padStart(2, '0') + ':' +
        String(now.getMinutes()).padStart(2, '0') + ':' +
        String(now.getSeconds()).padStart(2, '0');
    }

    function doReview() {
      var page = mw.config.get('wgPageName');
      var stamp = localStamp();
      var api = new mw.Api();

      api.get({
        action: 'query',
        prop: 'revisions',
        titles: page,
        rvslots: 'main',
        rvprop: 'content|timestamp',
        formatversion: 2
      }).done(function (data) {
        var pg = data.query.pages[0];
        if (!pg || !pg.revisions || !pg.revisions.length) {
          mw.notify('Could not read page content', { type: 'error' });
          return;
        }
        var rev = pg.revisions[0];
        var content = rev.slots.main.content;
        var baseTs = rev.timestamp;

        // Find the first {{DocMeta ...}} block (non-greedy to the first }}
        var docmetaRe = /\{\{\s*DocMeta\b([\s\S]*?)\}\}/i;
        var m = content.match(docmetaRe);
        if (!m) {
          mw.notify('DocMeta block not found on this page', { type: 'error' });
          return;
        }

        var block = m[0];
        var inner = m[1]; // content between 'DocMeta' and closing '}}'

        // Normalise newlines
        var nl = /\r\n/.test(block) ? '\r\n' : '\n';

        // Does review_ping already exist?
        var rpRe = /(\|\s*review_ping\s*=\s*)(.*)/i;
        var newInner;

        if (rpRe.test(inner)) {
          // Replace its value only on that line
          newInner = inner.replace(rpRe, function(_, prefix) {
            return prefix + stamp;
          });
        } else {
          // Insert a new line with review_ping before the closing }}
          // Preserve indentation if present
          var indentMatch = inner.match(/\n([ \t]*)\|/);
          var indent = indentMatch ? indentMatch[1] : ' ';
          newInner = inner.replace(/[\s\S]*$/, function (s) {
            // Ensure trailing newline inside the template block
            if (!/\n$/.test(s)) s += nl;
            return s + indent + '| review_ping = ' + stamp + nl;
          });
        }

        var newBlock = block.replace(inner, newInner);
        var newContent = content.replace(docmetaRe, newBlock);

        // Remove any old HTML comment pings (optional cleanup)
        newContent = newContent.replace(/<!--\s*review-ping:.*?-->\s*/gi, '');

        api.postWithToken('csrf', {
          action: 'edit',
          title: page,
          text: newContent,
          summary: 'Mark as reviewed',
          minor: true,
          nocreate: true,
          basetimestamp: baseTs, // basic edit-conflict protection
          starttimestamp: data.curtimestamp
        }).done(function () {
          location.reload();
        }).fail(function (err) {
          mw.notify('Review failed: ' + (err && err.error && err.error.info ? err.error.info : err), { type: 'error' });
        });
      }).fail(function (err) {
        mw.notify('Failed to fetch page: ' + err, { type: 'error' });
      });
    }

    // Create the button
    var $btn = $('<a>')
      .attr('href', '#')
      .text('Mark as reviewed')
      .css({
        display: 'inline-block',
        marginTop: '8px',
        padding: '4px 8px',
        background: '#f0f0f0',
        border: '1px solid #ccc',
        borderRadius: '2px',
        textDecoration: 'none'
      })
      .on('click', function (e) {
        e.preventDefault();
        doReview();
      });

    // Place it under the DocMeta table
    var $actions = $docMeta.closest('table').nextAll('.doc-meta-actions').first();
    if (!$actions.length) {
      $actions = $('<div class="doc-meta-actions"></div>').insertAfter($docMeta.closest('table'));
    }
    $actions.empty().append($btn);
  }

  mw.hook('wikipage.content').add(run);
});