Show Last Modified Timestamp for Non-HTML Documents

Display the Last-Modified timestamp for non-HTML documents

2025-05-03 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        Show Last Modified Timestamp for Non-HTML Documents
// @namespace   https://greasyfork.org/users/1310758
// @description Display the Last-Modified timestamp for non-HTML documents
// @match       *://*/*
// @license     MIT License
// @author      pachimonta
// @grant       GM.setValue
// @grant       GM_getValue
// @grant       GM_setClipboard
// @noframes
// @version     2025.05.03.001
// ==/UserScript==
(function () {
  // Only proceed for non-HTML documents with required properties
  if (
    !document ||
    !document.lastModified ||
    !document.contentType ||
    /^text\/html(?:$|;)/.test(document.contentType) ||
    !document.head ||
    !document.body
  ) return;

  // Information note shown on overlay focus
  const NOTE = String.raw`Content last modified time.

Note: The 'lastModified' value may differ from the
server's 'Last-Modified' header. If not available,
the 'Date' header is used, showing when the server
sent the response. Timestamp formats may also vary
by environment, which can cause display errors.

📋: Copy, 📌: Drag overlay, ✅: Toggle display.
Press CTRL+C to copy the last modified time.
Press CTRL+V to toggle display.
When unchecked, it hides on blur.`;

  const DEFAULT_OVERLAY_VISIBLE = true;

  // Parse lastModified string into Date
  const lastModifiedDate = new Date(document.lastModified);

  // Format date (YYYY-MM-DD)
  const dateFormatOptions = {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  };
  const formattedDate = new Intl.DateTimeFormat(
    'sv-SE',
    dateFormatOptions
  ).format(lastModifiedDate);

  // Format time with timezone
  const timeFormatOptions = {
    hour12: false,
    hour: '2-digit',
    minute: '2-digit',
    second: '2-digit',
    timeZoneName: 'short',
  };
  const formattedTime = new Intl.DateTimeFormat(
    'sv-SE',
    timeFormatOptions
  ).format(lastModifiedDate);

  // Get short weekday name (Mon, Tue, ...)
  const weekdayOptions = {
    weekday: 'short',
  };
  const dayOfWeek = new Intl.DateTimeFormat('en-US', weekdayOptions).format(
    lastModifiedDate
  );

  // Final display string
  const lastModifiedText = `${formattedDate} (${dayOfWeek}) ${formattedTime}`;

  // Get overlay visibility state from user settings
  const isOverlayVisible = GM_getValue(
    'lastModifiedOverlayVisible',
    DEFAULT_OVERLAY_VISIBLE
  );

  // Create overlay root element
  const overlay = document.createElement('span');
  overlay.classList.add('last-modified-overlay');
  if (!isOverlayVisible) overlay.classList.add('opacity-zero');
  overlay.setAttribute('tabindex', '0');
  overlay.dataset.content = NOTE;

  // Last-modified info button
  const infoButton = document.createElement('button');
  infoButton.textContent = lastModifiedText;
  infoButton.classList.add('overlay-info-button');
  infoButton.setAttribute('tabindex', '-1');
  overlay.append(infoButton);

  // When info button receives focus, move focus to overlay for accessibility/tooltips
  infoButton.addEventListener(
    'focus',
    function () {
      overlay.focus();
    },
    true
  );

  // Copy button
  const copyButton = document.createElement('button');
  copyButton.classList.add('copy-button');
  copyButton.textContent = '📋';
  copyButton.dataset.tooltip = 'Copy';
  overlay.append(copyButton);

  // Copy to clipboard with double confirmation
  let copyTooltipTimeout;
  copyButton.addEventListener('click', () => {
    if (copyButton.dataset.tooltip === 'Copied!') return;
    function resetTooltip() {
      copyButton.dataset.tooltip = 'Copy';
      copyButton.blur();
    }
    if (copyTooltipTimeout) clearTimeout(copyTooltipTimeout);

    if (copyButton.dataset.tooltip === 'Copy') {
      copyButton.dataset.tooltip = 'Copy?';
      copyTooltipTimeout = setTimeout(resetTooltip, 3000);
      return;
    }
    GM_setClipboard(lastModifiedText, 'text');
    copyButton.dataset.tooltip = 'Copied!';
    copyTooltipTimeout = setTimeout(resetTooltip, 1000);
  });

  // Toggle label & checkbox for overlay visibility
  const toggleLabel = document.createElement('label');
  toggleLabel.setAttribute('tabindex', '0');
  toggleLabel.classList.add('toggle-label');
  toggleLabel.dataset.tooltip = 'Toggle display';

  const toggleCheckbox = document.createElement('input');
  toggleCheckbox.type = 'checkbox';
  toggleCheckbox.classList.add('toggle-checkbox');
  toggleCheckbox.checked = isOverlayVisible;
  toggleCheckbox.addEventListener(
    'change',
    function () {
      const checked = toggleCheckbox.checked;
      const prevValue = GM_getValue(
        'lastModifiedOverlayVisible',
        DEFAULT_OVERLAY_VISIBLE
      );
      if (prevValue !== checked) {
        GM.setValue('lastModifiedOverlayVisible', checked);
      }
      overlay.classList.toggle('opacity-zero', !checked);
    },
    true
  );
  toggleLabel.append(toggleCheckbox);
  overlay.prepend(toggleLabel);

  // Drag button
  const dragButton = document.createElement('button');
  dragButton.classList.add('drag-button');
  dragButton.textContent = '📌';
  dragButton.dataset.tooltip = 'Drag overlay';
  overlay.prepend(dragButton);

  // Reset overlay position on double click of drag button
  dragButton.addEventListener('dblclick', function() {
    overlay.style.top = defaultTop;
    overlay.style.right = defaultRight;
    overlay.style.bottom = 'auto';
    overlay.style.left = 'auto';
  });

  // Keyboard shortcuts: Ctrl+C to copy, Ctrl+V to toggle
  overlay.addEventListener('keydown', function(event) {
    if (event.ctrlKey && event.key === 'c') {
      GM_setClipboard(lastModifiedText, 'text');
    }
    if (event.ctrlKey && event.key === 'v') {
      toggleCheckbox.click();
    }
  });

  // Sync overlay state when window/tab gets focus (cross-tab)
  window.addEventListener(
    'focus',
    function () {
      const currentVisible = GM_getValue(
        'lastModifiedOverlayVisible',
        DEFAULT_OVERLAY_VISIBLE
      );
      toggleCheckbox.checked = currentVisible;
      overlay.classList.toggle('opacity-zero', !currentVisible);
    },
    true
  );

  // Overlay styles
  const style = document.createElement('style');
  style.textContent = String.raw`
  .last-modified-overlay {
    display: inline-block;
    position: fixed;
    top: 8px;
    right: 32px;
    background-color: rgba(0, 0, 0, 0.6);
    color: white;
    z-index: 10;
    font-size: 13px;
    padding: 8px;
    font-family: sans-serif;
    cursor: pointer;
    border: 1px solid transparent;
    border-radius: 6px;
  }
  .last-modified-overlay:focus::after {
    content: attr(data-content);
    position: absolute;
    top: 100%;
    left: 0;
    background-color: #ffe;
    color: #000;
    padding: 4px 8px;
    margin-top: 4px;
    border: 1px solid #000;
    border-radius: 6px;
    z-index: 10;
    white-space: break-spaces;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.19);
    opacity: 0.9;
  }
  .last-modified-overlay:focus,
  .last-modified-overlay:hover {
    opacity: 1;
  }
  .overlay-info-button {
    appearance: none;
    color: white;
    margin: 0 6px;
    background-color: transparent;
    border: 2px solid transparent;
    cursor: pointer;
  }
  .drag-button {
    cursor: move;
  }
  .copy-button {
    cursor: copy;
  }
  [data-tooltip] {
    font-family: inherit;
    position: relative;
    opacity: 0.6;
    z-index: 100;
  }
  [data-tooltip]:hover::after,
  [data-tooltip]:focus::after {
    content: attr(data-tooltip);
    display: inline-block;
    position: absolute;
    top: 100%;
    left: -26px;
    background-color: #ffe;
    color: #000;
    padding: 4px 8px;
    margin-top: 4px;
    border: 1px solid #000;
    border-radius: 6px;
    white-space: break-spaces;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.19);
  }
  [data-tooltip]:hover,
  [data-tooltip]:focus {
    opacity: 0.9;
  }
  .opacity-zero {
    opacity: 0;
    transition: opacity 0.25s;
  }
  `;
  document.head.append(style);

  // Insert overlay after <body> for layout consistency
  document.body.after(overlay);

  // ------- Drag and Drop Functionality -------
  // Utility: Get clientX and clientY from mouse or touch event
  function getEventClientXY(e) {
    if (e.touches && e.touches.length > 0) {
      return { x: e.touches[0].clientX, y: e.touches[0].clientY };
    } else if (typeof e.clientX === 'number' && typeof e.clientY === 'number') {
      return { x: e.clientX, y: e.clientY };
    }
    return null;
  }

  let isDragging = false;
  let dragOffsetX = 0;
  let dragOffsetY = 0;
  // Store default position for reset
  const defaultTop = overlay.style.top;
  const defaultRight = overlay.style.right;

  function dragStart(e) {
    // Only allow dragging from the drag button
    if (e.target !== dragButton) return;
    isDragging = true;
    const pos = getEventClientXY(e);
    if (!pos) return;
    const rect = overlay.getBoundingClientRect();
    dragOffsetX = pos.x - rect.left;
    dragOffsetY = pos.y - rect.top;
    e.preventDefault();
  }

  function dragMove(e) {
    if (!isDragging) return;
    const pos = getEventClientXY(e);
    if (!pos) return;
    let newLeft = pos.x - dragOffsetX;
    let newTop = pos.y - dragOffsetY;
    // Keep overlay within viewport
    newLeft = Math.max(0, Math.min(window.innerWidth - overlay.offsetWidth, newLeft));
    newTop = Math.max(0, Math.min(window.innerHeight - overlay.offsetHeight, newTop));
    overlay.style.left = newLeft + 'px';
    overlay.style.top = newTop + 'px';
    overlay.style.right = 'auto';
    overlay.style.bottom = 'auto';
    e.preventDefault();
  }

  function dragEnd() {
    isDragging = false;
  }

  // Register drag event listeners for mouse and touch
  overlay.addEventListener('mousedown', dragStart);
  overlay.addEventListener('touchstart', dragStart, { passive: false });

  document.addEventListener('mousemove', dragMove);
  document.addEventListener('touchmove', dragMove, { passive: false });

  document.addEventListener('mouseup', dragEnd);
  document.addEventListener('touchend', dragEnd);

})();