MediaWiki:Common.js: Difference between revisions

From Hidden Mickey Wiki

No edit summary
Tag: Manual revert
No edit summary
Tag: Reverted
Line 92: Line 92:


/* Test code to make checkboxes and timestamps work */
/* Test code to make checkboxes and timestamps work */
/* This code works for PC Windows */
(function () {
(function () {
   'use strict';
   'use strict';


   // Toggle this in the console with `window.mwTimestamp.DEBUG = false` to silence logs
   // Debug flag (set false to silence logs)
   window.mwTimestamp = window.mwTimestamp || {};
   window.mwTimestamp = window.mwTimestamp || {};
   window.mwTimestamp.DEBUG = window.mwTimestamp.DEBUG !== undefined ? window.mwTimestamp.DEBUG : true;
   window.mwTimestamp.DEBUG = window.mwTimestamp.DEBUG !== undefined ? window.mwTimestamp.DEBUG : true;
  function dbg() { if (!window.mwTimestamp.DEBUG) return; var args = Array.prototype.slice.call(arguments); console.log.apply(console, args); }


   var PREFIX = 'mw-checkbox-ts:';
   var PREFIX = 'mw-checkbox-ts:';
  function formatTime(d) { return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', second: '2-digit' }); }


  function formatTime(d) {
    try {
      return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', second: '2-digit' });
    } catch (e) {
      dbg('formatTime fallback to ISO', e);
      return d.toISOString();
    }
  }
  // Robust ensureTsBox: many fallbacks to work across different DOM shapes
   function ensureTsBox(cb) {
   function ensureTsBox(cb) {
     if (!cb) { return null; }
     if (!cb) return null;
     // prefer row-based lookup
 
     var tr = cb.closest && cb.closest('tr');
     // 1) If there's already one in the row / cell, use it
    if (tr) {
     try {
      var existing = tr.querySelector && tr.querySelector('.mw-ts-box');
      var tr = cb.closest && cb.closest('tr');
      if (existing) { return existing; }
      if (tr) {
      // create in last cell if not present
        var existing = tr.querySelector && tr.querySelector('.mw-ts-box');
      var lastCell = tr.querySelector('td:last-child, th:last-child') || tr.lastElementChild;
        if (existing) return existing;
      if (lastCell) {
 
        var ts = lastCell.querySelector('.mw-ts-box');
        var lastCell = tr.querySelector('td:last-child, th:last-child') || tr.lastElementChild;
        if (!ts) { ts = document.createElement('span'); ts.className = 'mw-ts-box'; lastCell.appendChild(ts); }
        if (lastCell) {
         return ts;
          var ts = lastCell.querySelector && lastCell.querySelector('.mw-ts-box');
          if (ts) return ts;
          ts = document.createElement('span');
          ts.className = 'mw-ts-box';
          lastCell.appendChild(ts);
          return ts;
        }
      }
    } catch (e) {
      dbg('ensureTsBox: row/cell method failed', e);
    }
 
    // 2) Try next sibling cell (common fallback)
    try {
      var td = (cb.closest && cb.closest('td')) || cb.parentElement;
      if (td && td.nextElementSibling) {
        var next = td.nextElementSibling;
         var ts2 = next.querySelector && next.querySelector('.mw-ts-box');
        if (ts2) return ts2;
        ts2 = document.createElement('span');
        ts2.className = 'mw-ts-box';
        next.appendChild(ts2);
        return ts2;
       }
       }
    } catch (e) {
      dbg('ensureTsBox: sibling-cell method failed', e);
     }
     }
     // fallback: next sibling cell
 
    var td = cb.closest && cb.closest('td') || cb.parentElement;
     // 3) Fallback: insert immediately after checkbox (most robust)
     if (td && td.nextElementSibling) {
     try {
       var next = td.nextElementSibling;
       // Avoid duplicate creation if parent already has one
       var ts2 = next.querySelector && next.querySelector('.mw-ts-box');
       var parentExisting = cb.parentNode && cb.parentNode.querySelector && cb.parentNode.querySelector('.mw-ts-box');
       if (!ts2) { ts2 = document.createElement('span'); ts2.className = 'mw-ts-box'; next.appendChild(ts2); }
       if (parentExisting) return parentExisting;
       return ts2;
 
      var span = document.createElement('span');
      span.className = 'mw-ts-box';
      span.style.marginLeft = '0.4em';
      if (cb.insertAdjacentElement) {
        cb.insertAdjacentElement('afterend', span);
      } else if (cb.parentNode) {
        cb.parentNode.insertBefore(span, cb.nextSibling);
      } else {
        // give up gracefully
        dbg('ensureTsBox: could not insert after checkbox (no parent)');
        return null;
      }
      return span;
    } catch (e) {
      dbg('ensureTsBox: final insert failed', e);
       return null;
     }
     }
    // last resort: insert immediately after checkbox
    var span = document.createElement('span');
    span.className = 'mw-ts-box';
    if (cb.parentNode) cb.parentNode.insertBefore(span, cb.nextSibling);
    return span;
   }
   }


   function saveState(cb, tsBox) {
   function saveState(cb, tsBox) {
     if (!cb || !cb.id) {
     if (!cb || !cb.id) {
      dbg('saveState: missing cb or id', cb && cb.id);
       return;
       return;
     }
     }
Line 143: Line 188:
     try {
     try {
       localStorage.setItem(PREFIX + cb.id, JSON.stringify(data));
       localStorage.setItem(PREFIX + cb.id, JSON.stringify(data));
      dbg('saveState: saved', PREFIX + cb.id, data);
     } catch (e) {
     } catch (e) {
       // Nothing
       dbg('saveState localStorage error', e);
     }
     }
   }
   }
Line 150: Line 196:
   function restoreOne(id, data) {
   function restoreOne(id, data) {
     var cb = document.getElementById(id);
     var cb = document.getElementById(id);
     if (!cb) {
     if (!cb) return false;
    try {
      cb.checked = !!data.checked;
      var tsBox = ensureTsBox(cb);
      if (tsBox) {
        tsBox.textContent = data.timestamp || '';
      } else {
        dbg('restoreOne: could not create tsBox for', id);
      }
      return true;
    } catch (e) {
      dbg('restoreOne error', e);
       return false;
       return false;
     }
     }
    cb.checked = !!data.checked;
    var tsBox = ensureTsBox(cb);
    if (tsBox) {
      tsBox.textContent = data.timestamp || '';
    } else {
      // Nothing
    }
    return true;
   }
   }


Line 170: Line 219:
         var id = k.slice(PREFIX.length);
         var id = k.slice(PREFIX.length);
         var json = localStorage.getItem(k);
         var json = localStorage.getItem(k);
         if (!json) {
         if (!json) return;
          return;
        }
         try {
         try {
           var data = JSON.parse(json);
           var data = JSON.parse(json);
           restoreOne(id, data);
           restoreOne(id, data);
         } catch (e) {
         } catch (e) {
           // Nothing
           dbg('restoreAllOnce parse error for', k, e);
         }
         }
       });
       });
     } catch (e) {
     } catch (e) {
       // Nothing
       dbg('restoreAllOnce outer error', e);
     }
     }
    // Nothing
   }
   }


   function initRestore() {
   function initRestore() {
    dbg('initRestore start (userAgent:', navigator.userAgent, ')');
     restoreAllOnce();
     restoreAllOnce();


    // MediaWiki hook (if available)
     if (window.mw && mw.hook) {
     if (window.mw && mw.hook) {
       try {
       try {
Line 196: Line 242:
         });
         });
       } catch (e) {
       } catch (e) {
         // Nothing
         dbg('mw.hook attach failed', e);
       }
       }
     }
     }


    // short retries (handles async injection)
     setTimeout(restoreAllOnce, 200);
     setTimeout(function () { restoreAllOnce(); }, 200);
     setTimeout(restoreAllOnce, 1200);
     setTimeout(function () { restoreAllOnce(); }, 1200);


    // MutationObserver fallback
     if (window.MutationObserver) {
     if (window.MutationObserver) {
       try {
       try {
Line 224: Line 268:
         observer.observe(document.body, { childList: true, subtree: true });
         observer.observe(document.body, { childList: true, subtree: true });
       } catch (e) {
       } catch (e) {
         // Nothing
         dbg('MutationObserver failed', e);
       }
       }
     }
     }
    dbg('initRestore done');
   }
   }


   // change handler — writes timestamp and saves state
  // Helper: robustly find checkbox from click/change target
  function getCheckboxFromEventTarget(target) {
    if (!target) return null;
    try {
      if (target.matches && target.matches('input[type="checkbox"].mw-checkbox-ts')) return target;
 
      // if label clicked, look up associated input
      if (target.tagName === 'LABEL') {
        var forId = target.getAttribute('for');
        if (forId) {
          var el = document.getElementById(forId);
          if (el && el.matches && el.matches('input[type="checkbox"].mw-checkbox-ts')) return el;
        }
        var inner = target.querySelector && target.querySelector('input[type="checkbox"].mw-checkbox-ts');
        if (inner) return inner;
      }
 
      // fallback: closest checkbox
      var cb = target.closest && target.closest('input[type="checkbox"].mw-checkbox-ts');
      if (cb) return cb;
    } catch (e) {
      dbg('getCheckboxFromEventTarget error', e);
    }
    return null;
  }
 
   // change handler — writes timestamp and saves state (defensive)
   document.addEventListener('change', function (ev) {
   document.addEventListener('change', function (ev) {
     var cb = ev.target;
     try {
    if (!cb || !(cb.matches && cb.matches('.mw-checkbox-ts'))) return;
      var cb = getCheckboxFromEventTarget(ev.target);
    var tsBox = ensureTsBox(cb);
      if (!cb) return;
    tsBox.textContent = cb.checked ? formatTime(new Date()) : '';
      dbg('change event on', cb.id, 'checked=', cb.checked);
     saveState(cb, tsBox);
      var tsBox = ensureTsBox(cb);
      var ts = cb.checked ? formatTime(new Date()) : '';
      if (tsBox) {
        tsBox.textContent = ts;
      } else {
        dbg('change: tsBox not available, trying to create fallback for', cb.id);
        tsBox = ensureTsBox(cb);
        if (tsBox) tsBox.textContent = ts;
      }
      saveState(cb, tsBox);
    } catch (e) {
      dbg('change handler error', e);
    }
  }, false);
 
  // click handler: quick immediate UI update if 'change' is delayed/not firing in some environments
  document.addEventListener('click', function (ev) {
    try {
      var cb = getCheckboxFromEventTarget(ev.target);
      if (!cb) return;
      // update the UI immediately for better feedback; save happens on 'change'
      var tsBox = ensureTsBox(cb);
      if (tsBox) tsBox.textContent = cb.checked ? formatTime(new Date()) : '';
     } catch (e) {
      dbg('click handler error', e);
    }
   }, false);
   }, false);


Line 245: Line 341:
   }
   }


   // Expose debug helpers
   // Helpers for debugging in console
   window.mwTimestamp.restoreAll = restoreAllOnce;
   window.mwTimestamp.restoreAll = restoreAllOnce;
   window.mwTimestamp.listSaved = function () {
   window.mwTimestamp.listSaved = function () {
Line 255: Line 351:
   };
   };


  dbg('mwTimestamp loaded; debug ON');
})();
})();

Revision as of 12:46, 6 October 2025

/* Any JavaScript here will be loaded for all users on every page load. */
// JavaScript code to save checkbox state and restore it when the page loads
$(document).ready(function() {
    // Function to save the state of checkboxes to localStorage
    function saveCheckboxState() {
        $('input[type="checkbox"]').each(function() {
            localStorage.setItem($(this).attr('id'), $(this).prop('checked'));
        });
    }

    // Function to load the state of checkboxes from localStorage
    function loadCheckboxState() {
        $('input[type="checkbox"]').each(function() {
            const savedState = localStorage.getItem($(this).attr('id'));
            if (savedState !== null) {
                $(this).prop('checked', savedState === 'true');
            }
        });
    }

    // Load the saved checkbox state when the page is loaded
    loadCheckboxState();

    // Save the checkbox state whenever a checkbox is changed
    $('input[type="checkbox"]').change(function() {
        saveCheckboxState();
    });
});

// Adjust the search box width
$(document).ready(function () {
    $('#searchInput').css('width', '600px'); // Adjust width as needed
});

// Add Edit Source to user dropdown
mw.loader.using('mediawiki.util', function () {
    mw.util.addPortletLink( 'p-personal', mw.util.getUrl( mw.config.get('wgPageName'), { action: 'edit' } ), 'Edit Source', 'pt-editsource' );
    mw.util.addPortletLink( 'p-personal', mw.util.getUrl( mw.config.get('wgPageName'), { action: 'history' } ), 'View History', 'pt-history' );
    mw.util.addPortletLink( 'p-personal', mw.util.getUrl( mw.config.get('wgPageName'), { action: 'delete' } ), 'Delete', 'pt-delete' );
    var moveLink = document.getElementById('ca-move');
    if (moveLink) {
        var a = moveLink.querySelector('a');
        mw.util.addPortletLink( 'p-personal', a.href, a.textContent.trim(), 'pt-move', a.title || 'Move this page' );
        moveLink.remove();
    }
    mw.util.addPortletLink( 'p-personal', mw.util.getUrl( mw.config.get('wgPageName'), { action: 'protect' } ), 'Protect', 'pt-protect' );
    mw.util.addPortletLink( 'p-personal', mw.util.getUrl( mw.config.get('wgPageName'), { action: 'unwatch' } ), 'Unwatch', 'pt-unwatch' );
    var talkLink = document.getElementById('pt-mytalk');
    talkLink.remove();
    var whatLinksHereLink = document.getElementById('t-whatlinkshere');
    if (whatLinksHereLink) {
        var a = whatLinksHereLink.querySelector('a');
        mw.util.addPortletLink( 'p-personal', a.href, a.textContent.trim(), 'pt-whatlinkshere', a.title || 'What Links Here' );
        whatLinksHereLink.remove();
    }
    var relatedChangesLink = document.getElementById('t-recentchangeslinked');
    if (relatedChangesLink) {
        var a = relatedChangesLink.querySelector('a');
        mw.util.addPortletLink( 'p-personal', a.href, a.textContent.trim(), 'pt-recentchanges', a.title || 'Recent Changes' );
        relatedChangesLink.remove();
    }
    var uploadLink = document.getElementById('t-upload');
    if (uploadLink) {
        var a = uploadLink.querySelector('a');
        mw.util.addPortletLink( 'p-personal', a.href, a.textContent.trim(), 'pt-upload', a.title || 'Upload File' );
        uploadLink.remove();
    }
    var specialPagesLink = document.getElementById('t-specialpages');
    if (specialPagesLink) {
        var a = specialPagesLink.querySelector('a');
        mw.util.addPortletLink( 'p-personal', a.href, a.textContent.trim(), 'pt-specialpages', a.title || 'Special Pages' );
        specialPagesLink.remove();
    }
    var permanentLink = document.getElementById('t-permalink');
    if (permanentLink) {
        var a = permanentLink.querySelector('a');
        mw.util.addPortletLink( 'p-personal', a.href, a.textContent.trim(), 'pt-permalink', a.title || 'Permanent Link' );
        permanentLink.remove();
    }
    var pageInfoLink = document.getElementById('t-info');
    if (pageInfoLink) {
        var a = pageInfoLink.querySelector('a');
        mw.util.addPortletLink( 'p-personal', a.href, a.textContent.trim(), 'pt-info', a.title || 'Page Info' );
        pageInfoLink.remove();
    }
    var printLink = document.getElementById('t-print');
    if (printLink) {
        var a = printLink.querySelector('a');
        printLink.remove();
    }
});

/* Test code to make checkboxes and timestamps work */
(function () {
  'use strict';

  // Debug flag (set false to silence logs)
  window.mwTimestamp = window.mwTimestamp || {};
  window.mwTimestamp.DEBUG = window.mwTimestamp.DEBUG !== undefined ? window.mwTimestamp.DEBUG : true;
  function dbg() { if (!window.mwTimestamp.DEBUG) return; var args = Array.prototype.slice.call(arguments); console.log.apply(console, args); }

  var PREFIX = 'mw-checkbox-ts:';

  function formatTime(d) {
    try {
      return d.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit', second: '2-digit' });
    } catch (e) {
      dbg('formatTime fallback to ISO', e);
      return d.toISOString();
    }
  }

  // Robust ensureTsBox: many fallbacks to work across different DOM shapes
  function ensureTsBox(cb) {
    if (!cb) return null;

    // 1) If there's already one in the row / cell, use it
    try {
      var tr = cb.closest && cb.closest('tr');
      if (tr) {
        var existing = tr.querySelector && tr.querySelector('.mw-ts-box');
        if (existing) return existing;

        var lastCell = tr.querySelector('td:last-child, th:last-child') || tr.lastElementChild;
        if (lastCell) {
          var ts = lastCell.querySelector && lastCell.querySelector('.mw-ts-box');
          if (ts) return ts;
          ts = document.createElement('span');
          ts.className = 'mw-ts-box';
          lastCell.appendChild(ts);
          return ts;
        }
      }
    } catch (e) {
      dbg('ensureTsBox: row/cell method failed', e);
    }

    // 2) Try next sibling cell (common fallback)
    try {
      var td = (cb.closest && cb.closest('td')) || cb.parentElement;
      if (td && td.nextElementSibling) {
        var next = td.nextElementSibling;
        var ts2 = next.querySelector && next.querySelector('.mw-ts-box');
        if (ts2) return ts2;
        ts2 = document.createElement('span');
        ts2.className = 'mw-ts-box';
        next.appendChild(ts2);
        return ts2;
      }
    } catch (e) {
      dbg('ensureTsBox: sibling-cell method failed', e);
    }

    // 3) Fallback: insert immediately after checkbox (most robust)
    try {
      // Avoid duplicate creation if parent already has one
      var parentExisting = cb.parentNode && cb.parentNode.querySelector && cb.parentNode.querySelector('.mw-ts-box');
      if (parentExisting) return parentExisting;

      var span = document.createElement('span');
      span.className = 'mw-ts-box';
      span.style.marginLeft = '0.4em';
      if (cb.insertAdjacentElement) {
        cb.insertAdjacentElement('afterend', span);
      } else if (cb.parentNode) {
        cb.parentNode.insertBefore(span, cb.nextSibling);
      } else {
        // give up gracefully
        dbg('ensureTsBox: could not insert after checkbox (no parent)');
        return null;
      }
      return span;
    } catch (e) {
      dbg('ensureTsBox: final insert failed', e);
      return null;
    }
  }

  function saveState(cb, tsBox) {
    if (!cb || !cb.id) {
      dbg('saveState: missing cb or id', cb && cb.id);
      return;
    }
    var data = {
      checked: !!cb.checked,
      timestamp: tsBox ? tsBox.textContent : ''
    };
    try {
      localStorage.setItem(PREFIX + cb.id, JSON.stringify(data));
      dbg('saveState: saved', PREFIX + cb.id, data);
    } catch (e) {
      dbg('saveState localStorage error', e);
    }
  }

  function restoreOne(id, data) {
    var cb = document.getElementById(id);
    if (!cb) return false;
    try {
      cb.checked = !!data.checked;
      var tsBox = ensureTsBox(cb);
      if (tsBox) {
        tsBox.textContent = data.timestamp || '';
      } else {
        dbg('restoreOne: could not create tsBox for', id);
      }
      return true;
    } catch (e) {
      dbg('restoreOne error', e);
      return false;
    }
  }

  function restoreAllOnce() {
    try {
      var keys = Object.keys(localStorage);
      keys.forEach(function (k) {
        if (k.indexOf(PREFIX) !== 0) return;
        var id = k.slice(PREFIX.length);
        var json = localStorage.getItem(k);
        if (!json) return;
        try {
          var data = JSON.parse(json);
          restoreOne(id, data);
        } catch (e) {
          dbg('restoreAllOnce parse error for', k, e);
        }
      });
    } catch (e) {
      dbg('restoreAllOnce outer error', e);
    }
  }

  function initRestore() {
    dbg('initRestore start (userAgent:', navigator.userAgent, ')');
    restoreAllOnce();

    if (window.mw && mw.hook) {
      try {
        mw.hook('wikipage.content').add(function () {
          restoreAllOnce();
        });
      } catch (e) {
        dbg('mw.hook attach failed', e);
      }
    }

    setTimeout(restoreAllOnce, 200);
    setTimeout(restoreAllOnce, 1200);

    if (window.MutationObserver) {
      try {
        var observer = new MutationObserver(function (mutations) {
          var want = false;
          for (var i = 0; i < mutations.length && !want; i++) {
            var added = mutations[i].addedNodes;
            for (var j = 0; j < added.length && !want; j++) {
              var node = added[j];
              if (node.nodeType !== 1) continue;
              if (node.matches && node.matches('.mw-checkbox-ts')) want = true;
              if (node.querySelector && (node.querySelector('.mw-checkbox-ts') || node.querySelector('.mw-ts-box'))) want = true;
            }
          }
          if (want) {
            restoreAllOnce();
          }
        });
        observer.observe(document.body, { childList: true, subtree: true });
      } catch (e) {
        dbg('MutationObserver failed', e);
      }
    }
    dbg('initRestore done');
  }

  // Helper: robustly find checkbox from click/change target
  function getCheckboxFromEventTarget(target) {
    if (!target) return null;
    try {
      if (target.matches && target.matches('input[type="checkbox"].mw-checkbox-ts')) return target;

      // if label clicked, look up associated input
      if (target.tagName === 'LABEL') {
        var forId = target.getAttribute('for');
        if (forId) {
          var el = document.getElementById(forId);
          if (el && el.matches && el.matches('input[type="checkbox"].mw-checkbox-ts')) return el;
        }
        var inner = target.querySelector && target.querySelector('input[type="checkbox"].mw-checkbox-ts');
        if (inner) return inner;
      }

      // fallback: closest checkbox
      var cb = target.closest && target.closest('input[type="checkbox"].mw-checkbox-ts');
      if (cb) return cb;
    } catch (e) {
      dbg('getCheckboxFromEventTarget error', e);
    }
    return null;
  }

  // change handler — writes timestamp and saves state (defensive)
  document.addEventListener('change', function (ev) {
    try {
      var cb = getCheckboxFromEventTarget(ev.target);
      if (!cb) return;
      dbg('change event on', cb.id, 'checked=', cb.checked);
      var tsBox = ensureTsBox(cb);
      var ts = cb.checked ? formatTime(new Date()) : '';
      if (tsBox) {
        tsBox.textContent = ts;
      } else {
        dbg('change: tsBox not available, trying to create fallback for', cb.id);
        tsBox = ensureTsBox(cb);
        if (tsBox) tsBox.textContent = ts;
      }
      saveState(cb, tsBox);
    } catch (e) {
      dbg('change handler error', e);
    }
  }, false);

  // click handler: quick immediate UI update if 'change' is delayed/not firing in some environments
  document.addEventListener('click', function (ev) {
    try {
      var cb = getCheckboxFromEventTarget(ev.target);
      if (!cb) return;
      // update the UI immediately for better feedback; save happens on 'change'
      var tsBox = ensureTsBox(cb);
      if (tsBox) tsBox.textContent = cb.checked ? formatTime(new Date()) : '';
    } catch (e) {
      dbg('click handler error', e);
    }
  }, false);

  // Start
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initRestore);
  } else {
    initRestore();
  }

  // Helpers for debugging in console
  window.mwTimestamp.restoreAll = restoreAllOnce;
  window.mwTimestamp.listSaved = function () {
    return Object.keys(localStorage).filter(function (k) { return k.indexOf(PREFIX) === 0; });
  };
  window.mwTimestamp.getSaved = function (id) {
    var j = localStorage.getItem(PREFIX + id);
    try { return j ? JSON.parse(j) : null; } catch (e) { dbg('getSaved parse error', e); return null; }
  };

  dbg('mwTimestamp loaded; debug ON');
})();