Xeverything11 (talk | contribs)
clarification, noted JS code is AI-generated
Xeverything11 (talk | contribs)
format move
Line 1: Line 1:
// Generated by ChatGPT o4-mini
// Generated by ChatGPT o4-mini and GPT-4o


// Prompts below:
// Prompts below:
Line 39: Line 39:
new -> created
new -> created
log seems vague, it can be "uploaded", "moved", "blocked", "protected" etc.
log seems vague, it can be "uploaded", "moved", "blocked", "protected" etc.
*/
/*
For move:
Xeverything11 moved User:Xeverything11/Use Google Analytics -> Xeverything11 moved User:Xeverything11/Use Google Analytics to Use Google Analytics
<username> moved <source page> -> <username> moved <source page> to <username> moved <source page> to <target page>
*/
*/


Line 45: Line 51:
   const RC_CONTAINER = document.getElementById('RCMain');
   const RC_CONTAINER = document.getElementById('RCMain');


  // 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);
     const intervals = [
     const intervals = [
       { label: 'day',   secs: 86400 },
       { label: 'day', secs: 86400 },
       { label: 'hour',   secs: 3600 },
       { label: 'hour', secs: 3600 },
       { label: 'minute', secs: 60   },
       { label: 'minute', secs: 60 },
       { label: 'second', secs: 1     }
       { label: 'second', secs: 1 }
     ];
     ];
     for (const { label, secs } of intervals) {
     for (const { label, secs } of intervals) {
Line 63: Line 68:
   };
   };


   // Map MediaWiki RC types and logtypes to readable verbs
   // ✅ Format each action line based on type/logtype
   const getActionVerb = change => {
   const formatActionLine = change => {
     const type = change.type;
     const user = mw.html.escape(change.user);
     if (type === 'edit') return 'edited';
    const source = mw.html.escape(change.title);
     if (type === 'new') return 'created';
    const link = `${mw.config.get('wgServer') + mw.config.get('wgScriptPath')}/index.php?title=${encodeURIComponent(change.title)}`;
     if (type === 'log') {
 
       const map = {
     if (change.type === 'edit') {
         move: 'moved',
      return `${user} edited <a href="${link}">${source}</a>`;
    }
 
     if (change.type === 'new') {
      return `${user} created <a href="${link}">${source}</a>`;
    }
 
     if (change.type === 'log') {
       if (change.logtype === 'move' && change.loginfo && change.loginfo.target_title) {
        const target = mw.html.escape(change.loginfo.target_title);
        const targetLink = `${mw.config.get('wgServer') + mw.config.get('wgScriptPath')}/index.php?title=${encodeURIComponent(change.loginfo.target_title)}`;
         return `${user} moved <a href="${link}">${source}</a> to <a href="${targetLink}">${target}</a>`;
      }
 
      const logVerbMap = {
         upload: 'uploaded',
         upload: 'uploaded',
         block: 'blocked',
         block: 'blocked',
Line 78: Line 97:
         patrol: 'patrolled',
         patrol: 'patrolled',
         import: 'imported',
         import: 'imported',
         tag: 'tagged',
         tag: 'tagged'
       };
       };
       return map[change.logtype] || 'performed log action';
 
       const verb = logVerbMap[change.logtype] || 'performed log action';
      return `${user} ${verb} <a href="${link}">${source}</a>`;
     }
     }
     return type; // fallback (e.g. external, unknown future types)
 
     return `${user} ${change.type} <a href="${link}">${source}</a>`;
   };
   };


   // Prepend entry with .rc-active after 1ms
   // Prepend an entry with a 1ms delayed activation
   const prependChange = change => {
   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(change.user)}</strong>
         ${formatActionLine(change)}
        ${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>
       <div class="rc-line2">
       <div class="rc-line2">
Line 108: Line 125:
   };
   };


   // Load recent changes
   // Load changes with loginfo (needed for move targets)
   api.get({
   api.get({
     action: 'query',
     action: 'query',
Line 118: Line 135:
   }).then(data => {
   }).then(data => {
     const changes = data.query.recentchanges;
     const changes = data.query.recentchanges;
    // Clear placeholder
     RC_CONTAINER.replaceChildren();
     RC_CONTAINER.replaceChildren();
    // Add first 3 immediately
     changes.slice(0, 3).forEach(prependChange);
     changes.slice(0, 3).forEach(prependChange);


    // Add rest with delay
     let idx = 3;
     let idx = 3;
     const timer = setInterval(() => {
     const timer = setInterval(() => {

Revision as of 17:37, 23 June 2025

// Generated by ChatGPT o4-mini and GPT-4o

// 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.
*/
/*
For move:
Xeverything11 moved User:Xeverything11/Use Google Analytics -> Xeverything11 moved User:Xeverything11/Use Google Analytics to Use Google Analytics
 
<username> moved <source page> -> <username> moved <source page> to <username> moved <source page> to <target page>
*/

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

  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';
  };

  // ✅ Format each action line based on type/logtype
  const formatActionLine = change => {
    const user = mw.html.escape(change.user);
    const source = mw.html.escape(change.title);
    const link = `${mw.config.get('wgServer') + mw.config.get('wgScriptPath')}/index.php?title=${encodeURIComponent(change.title)}`;

    if (change.type === 'edit') {
      return `${user} edited <a href="${link}">${source}</a>`;
    }

    if (change.type === 'new') {
      return `${user} created <a href="${link}">${source}</a>`;
    }

    if (change.type === 'log') {
      if (change.logtype === 'move' && change.loginfo && change.loginfo.target_title) {
        const target = mw.html.escape(change.loginfo.target_title);
        const targetLink = `${mw.config.get('wgServer') + mw.config.get('wgScriptPath')}/index.php?title=${encodeURIComponent(change.loginfo.target_title)}`;
        return `${user} moved <a href="${link}">${source}</a> to <a href="${targetLink}">${target}</a>`;
      }

      const logVerbMap = {
        upload: 'uploaded',
        block: 'blocked',
        protect: 'protected',
        delete: 'deleted',
        rights: 'changed rights',
        patrol: 'patrolled',
        import: 'imported',
        tag: 'tagged'
      };

      const verb = logVerbMap[change.logtype] || 'performed log action';
      return `${user} ${verb} <a href="${link}">${source}</a>`;
    }

    return `${user} ${change.type} <a href="${link}">${source}</a>`;
  };

  // ✅ Prepend an entry with a 1ms delayed activation
  const prependChange = change => {
    const entry = document.createElement('div');
    entry.className = 'rc-entry';
    entry.innerHTML = `
      <div class="rc-line1">
        ${formatActionLine(change)}
      </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 changes with loginfo (needed for move targets)
  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;
    RC_CONTAINER.replaceChildren();
    changes.slice(0, 3).forEach(prependChange);

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