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.
// Generated by ChatGPT o4-mini

// Prompts below:

// Write me JS code to display recent changes for a wiki using MediaWiki Action API
// Make sure it is ES6-compatible so it can work on MediaWiki:Common.js on a MediaWiki wiki.
/*
[[User:Xeverything11|Xeverything11]] ([[User talk:Xeverything11|talk]])
<templatestyles src="RecentChanges/styles.css" />
<div class="mw-parser-output">
<div class="rc-container">
<div class="rc-header">
<div class="rc-unpatrolled">[[Special:RecentChanges|<span id="RCUnpatrolled">16</span> {{material icon|1=<span id="RCUnpatrolledIcon">sunny</span>|size=inherit}}]]</div>
Changes to patrol</div>
<div class="rc-main" id="RCMain">
JavaScript is required to view the recent changes.
</div>
</div>
</div>
[[User:Xeverything11|Xeverything11]] ([[User talk:Xeverything11|talk]])

1. Remove all children from #RCMain
2. Prepend three most recent changes into #RCMain.
3. Wait 3 seconds.
4. Prepend the next recent change (4th).
5. Repeat steps 3-4. (5th, 6th, 7th change etc.)

Format:
<username> <action> <page>
<time ago> ago

Example
Xeverything11 edited Make Vegan Gingerbread
13 hours ago
*/
// 1ms after each RC prepends, add .rc-active CSS class to that (to allow transitions).
/*
edit -> edited
new -> created
log seems vague, it can be "uploaded", "moved", "blocked", "protected" etc.
*/

mw.loader.using('mediawiki.api').then(() => {
  const api = new mw.Api();
  const RC_CONTAINER = document.getElementById('RCMain');

  // Convert timestamp into "X minutes ago"
  const timeAgo = timestamp => {
    const seconds = Math.floor((Date.now() - new Date(timestamp)) / 1000);
    const intervals = [
      { label: 'day',    secs: 86400 },
      { label: 'hour',   secs: 3600  },
      { label: 'minute', secs: 60    },
      { label: 'second', secs: 1     }
    ];
    for (const { label, secs } of intervals) {
      const count = Math.floor(seconds / secs);
      if (count > 0) {
        return `${count} ${label}${count > 1 ? 's' : ''}`;
      }
    }
    return 'just now';
  };

  // Map MediaWiki RC types and logtypes to readable verbs
  const getActionVerb = change => {
    const type = change.type;
    if (type === 'edit') return 'edited';
    if (type === 'new') return 'created';
    if (type === 'log') {
      const map = {
        move: 'moved',
        upload: 'uploaded',
        block: 'blocked',
        protect: 'protected',
        delete: 'deleted',
        rights: 'changed rights',
        patrol: 'patrolled',
        import: 'imported',
        tag: 'tagged',
      };
      return map[change.logtype] || 'performed log action';
    }
    return type; // fallback (e.g. external, unknown future types)
  };

  // Prepend entry with .rc-active after 1ms
  const prependChange = change => {
    const verb = getActionVerb(change);
    const entry = document.createElement('div');
    entry.className = 'rc-entry';
    entry.innerHTML = `
      <div class="rc-line1">
        <strong>${mw.html.escape(change.user)}</strong>
        ${verb}
        <a href="${mw.config.get('wgServer') + mw.config.get('wgScriptPath')}/index.php?title=${encodeURIComponent(change.title)}">
          ${mw.html.escape(change.title)}
        </a>
      </div>
      <div class="rc-line2">
        ${timeAgo(change.timestamp)} ago
      </div>
    `;
    RC_CONTAINER.insertBefore(entry, RC_CONTAINER.firstChild);
    setTimeout(() => {
      entry.classList.add('rc-active');
    }, 1);
  };

  // Load recent changes
  api.get({
    action: 'query',
    list: 'recentchanges',
    rcprop: 'title|ids|sizes|comment|user|timestamp|flags|loginfo',
    rclimit: 50,
    rcshow: '!bot',
    format: 'json'
  }).then(data => {
    const changes = data.query.recentchanges;

    // Clear placeholder
    RC_CONTAINER.replaceChildren();

    // Add first 3 immediately
    changes.slice(0, 3).forEach(prependChange);

    // Add rest with delay
    let idx = 3;
    const timer = setInterval(() => {
      if (idx >= changes.length) {
        clearInterval(timer);
      } else {
        prependChange(changes[idx++]);
      }
    }, 3000);
  }).catch(err => {
    console.error('Failed to load recent changes:', err);
    RC_CONTAINER.textContent = 'Error loading recent changes.';
  });
});