Show Last Modified Timestamp for Non-HTML Documents

Displays the Last-Modified timestamp for non-HTML documents

As of 2025-04-26. See the latest version.

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 Displays the Last-Modified timestamp for non-HTML documents
// @match       *://*/*
// @license     MIT License
// @author      pachimonta
// @grant       GM.setValue
// @grant       GM_getValue
// @version     2025.04.26.002
// ==/UserScript==
(function () {
  /*
   * Note: The 'lastModified' property of this script may not necessarily match the
   * 'Last-Modified' header returned by the server. If the server does not provide
   * a 'Last-Modified' header, the 'Date' header will be used instead, which reflects
   * the time when the server sent the response. Additionally, the format of the
   * timestamps may vary depending on the environment, which could lead to
   * issues with formatting or errors in the code execution.
   */

  // Default overlay display state
  const defaultOverlayVisible = true;

  // Exit the process if the document is not valid
  if (
    !document ||
    !document.lastModified ||
    !document.contentType ||
    /^text\/html(?:$|;)/.test(document.contentType) ||
    !document.head ||
    !document.body
  ) {
    return;
  }

  // Get the last modified date
  const lastModifiedDate = new Date(document.lastModified);

  // Set up date formatting options
  const dateFormatOptions = {
    year: 'numeric',
    month: '2-digit',
    day: '2-digit',
  };
  const formattedDate = new Intl.DateTimeFormat('sv-SE', dateFormatOptions).format(lastModifiedDate);

  // Set up time formatting options
  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 the day of the week
  const weekdayOptions = {
    weekday: 'short',
  };
  const dayOfWeek = new Intl.DateTimeFormat('en-US', weekdayOptions).format(lastModifiedDate);

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

  // Save and retrieve the visibility state of the overlay
  const isOverlayVisible = GM_getValue('lastModifiedOverlayVisible', defaultOverlayVisible);

  // Create a div for the overlay
  const overlayDiv = document.createElement('div');
  overlayDiv.classList.add('last-modified-overlay');
  if (!isOverlayVisible) {
    overlayDiv.classList.add('hidden'); // Hide the overlay if not visible
  }
  overlayDiv.setAttribute('tabindex', '0'); // Make the overlay focusable

  const overlayText = document.createElement('span');
  overlayText.textContent = lastModifiedText; // Set the text content to the last modified date
  overlayText.setAttribute('tabindex', '0'); // Make the text focusable
  overlayText.addEventListener(
    'focus',
    function () {
      const range = document.createRange();
      range.selectNodeContents(overlayText); // Select the text when focused
      const selection = window.getSelection();
      selection.removeAllRanges();
      selection.addRange(range);
    },
    true
  );
  overlayDiv.append(overlayText);

  // Create a checkbox to toggle the display
  const toggleCheckbox = document.createElement('input');
  toggleCheckbox.type = 'checkbox';
  toggleCheckbox.checked = isOverlayVisible; // Set the checkbox state based on visibility
  toggleCheckbox.title = 'Toggle the display of the Last-Modified timestamp'; // Tooltip for the checkbox
  toggleCheckbox.addEventListener(
    'change',
    function () {
      const currentVisible = GM_getValue('lastModifiedOverlayVisible', defaultOverlayVisible);
      const checked = toggleCheckbox.checked;
      if (currentVisible !== checked) {
        GM.setValue('lastModifiedOverlayVisible', checked); // Save the new visibility state
      }
      if (checked) {
        overlayDiv.classList.remove('hidden'); // Show the overlay if checked
      } else {
        overlayDiv.classList.add('hidden'); // Hide the overlay if unchecked
      }
    },
    true
  );
  overlayDiv.prepend(toggleCheckbox);

  const dragButton = document.createElement('button');
  dragButton.classList.add('drag-button');
  dragButton.setAttribute('tabindex', '0'); // Make the button focusable
  dragButton.textContent = '📌'; // Set the button text to a pin emoji
  dragButton.title = 'Drag to move the overlay'; // Tooltip for the drag button
  overlayDiv.append(dragButton);

  // Synchronize the state when the window regains focus
  window.addEventListener(
    'focus',
    function () {
      const currentVisible = GM_getValue('lastModifiedOverlayVisible', defaultOverlayVisible);
      toggleCheckbox.checked = currentVisible; // Update checkbox state
      if (currentVisible) {
        overlayDiv.classList.remove('hidden'); // Show the overlay if visible
      } else {
        overlayDiv.classList.add('hidden'); // Hide the overlay if not visible
      }
    },
    true
  );

  // Add styles for the overlay
  const style = document.createElement('style');
  style.textContent = String.raw`
    .last-modified-overlay {
      position: fixed;
      top: 8px;
      right: 32px;
      background: rgba(0, 0, 0, 0.6);
      color: white;
      z-index: 100;
      font-size: 14px;
      padding: 8px;
      font-family: sans-serif;
      cursor: pointer;
    }
    .last-modified-overlay [type="checkbox"] {
      opacity: 0.6;
      margin-right: 16px;
    }
    .last-modified-overlay [type="checkbox"]:hover,
    .last-modified-overlay [type="checkbox"]:focus {
      opacity: 1;
    }
    .last-modified-overlay .drag-button {
      margin-left: 16px;
      appearance: none;
      background: none;
      border: none;
      font: inherit;
      color: inherit;
      cursor: move;
    }
    .last-modified-overlay.hidden {
      opacity: 0;
    }
    .last-modified-overlay:focus,
    .last-modified-overlay:hover {
      opacity: 1;
    }
  `;
  document.head.append(style); // Append the styles to the document head

  document.body.append(overlayDiv); // Append the overlay to the body

  function getEventClientXY(e) {
    // If it is a touch event, get from touches[0], otherwise mouse event
    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 };
    }
    // Otherwise null
    return null;
  }
  // Variables for dragging functionality
  let isDragging = false; // Flag to indicate if dragging is in progress
  let dragOffsetX = 0; // X offset for dragging
  let dragOffsetY = 0; // Y offset for dragging

  function dragStart(e) {
    // Ignored if e.target is not a dragButton
    if (e.target !== dragButton) return;
    isDragging = true;
    const pos = getEventClientXY(e);
    if (!pos) return;
    const rect = overlayDiv.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;
    newLeft = Math.max(0, Math.min(window.innerWidth - overlayDiv.offsetWidth, newLeft));
    newTop = Math.max(0, Math.min(window.innerHeight - overlayDiv.offsetHeight, newTop));
    overlayDiv.style.left = newLeft + 'px';
    overlayDiv.style.top = newTop + 'px';
    overlayDiv.style.right = 'auto';
    overlayDiv.style.bottom = 'auto';
    e.preventDefault();
  }

  function dragEnd(e) {
    isDragging = false;
  }

  // Event registration (both mouse and touch)
  overlayDiv.addEventListener('mousedown', dragStart);
  overlayDiv.addEventListener('touchstart', dragStart, {passive: false});

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

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

  // Uncomment the line below to set the document title to the last modified date
  // document.body.title = lastModifiedText; // Set the title to the last modified date
})();