Xeverything11 (talk | contribs)
JS for RC
 
Xeverything11 (talk | contribs)
clarification, noted JS code is AI-generated
Line 1: Line 1:
// MediaWiki:Common.js
// 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(() => {
mw.loader.using('mediawiki.api').then(() => {
Line 5: Line 45:
   const RC_CONTAINER = document.getElementById('RCMain');
   const RC_CONTAINER = document.getElementById('RCMain');


   // Simple "time ago" formatter
   // Convert timestamp into "X minutes ago"
   const timeAgo = timestamp => {
   const timeAgo = timestamp => {
     const seconds = Math.floor((Date.now() - new Date(timestamp)) / 1000);
     const seconds = Math.floor((Date.now() - new Date(timestamp)) / 1000);
Line 23: Line 63:
   };
   };


   // Build one entry, prepend it, then activate
   // Map MediaWiki RC types and logtypes to readable verbs
   const prependChange = c => {
  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');
     const entry = document.createElement('div');
     entry.className = 'rc-entry';
     entry.className = 'rc-entry';
     entry.innerHTML = `
     entry.innerHTML = `
       <div class="rc-line1">
       <div class="rc-line1">
         <strong>${mw.html.escape(c.user)}</strong>
         <strong>${mw.html.escape(change.user)}</strong>
         ${c.type}
         ${verb}
         <a href="${mw.config.get('wgServer') + mw.config.get('wgScriptPath')}/index.php?title=${encodeURIComponent(c.title)}">
         <a href="${mw.config.get('wgServer') + mw.config.get('wgScriptPath')}/index.php?title=${encodeURIComponent(change.title)}">
           ${mw.html.escape(c.title)}
           ${mw.html.escape(change.title)}
         </a>
         </a>
       </div>
       </div>
       <div class="rc-line2">
       <div class="rc-line2">
         ${timeAgo(c.timestamp)} ago
         ${timeAgo(change.timestamp)} ago
       </div>
       </div>
     `;
     `;
     RC_CONTAINER.insertBefore(entry, RC_CONTAINER.firstChild);
     RC_CONTAINER.insertBefore(entry, RC_CONTAINER.firstChild);
    // give the browser a tick before adding the active class
     setTimeout(() => {
     setTimeout(() => {
       entry.classList.add('rc-active');
       entry.classList.add('rc-active');
Line 46: Line 108:
   };
   };


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


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


     // 3–5. every 3s prepend the next one with activation
     // Add rest with delay
     let idx = 3;
     let idx = 3;
     const timer = setInterval(() => {
     const timer = setInterval(() => {

Revision as of 17:32, 23 June 2025

// 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.';
  });
});