GitHub HTML Preview Shortcuts

Adds a professional button to view GitHub HTML files directly via htmlpreview.github.io

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 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         GitHub HTML Preview Shortcuts
// @namespace    html_preview.user.js
// @run-at       document-start
// @match        *://github.com/*
// @match        *://*.github.com/*
// @grant        none
// @version      1.0.1
// @author       Prashant Adesara
// @description  Adds a professional button to view GitHub HTML files directly via htmlpreview.github.io
// @license MIT
// ==/UserScript==

(function () {
  'use strict';

  const PREVIEW_BASE = 'https://htmlpreview.github.io/?';

  function isGitHubHtmlFile() {
      // Inspects location structure accurately during soft or hard navigation
      return location.hostname === 'github.com' &&
             location.pathname.includes('/blob/') &&
             location.pathname.endsWith('.html');
  }

  function ensureButton() {
      if (document.getElementById('__html_preview_btn_wrap__')) return;

      const wrap = document.createElement('div');
      wrap.id = '__html_preview_btn_wrap__';

      // Position it at the bottom right corner
      Object.assign(wrap.style, {
          position: 'fixed',
          right: '16px',
          bottom: '16px',
          zIndex: 2147483647,
      });
      // Position it at the top right corner
      /*
      Object.assign(wrap.style, {
          position: 'fixed',
          right: '20px',
          top: '20px',
          zIndex: 2147483647,
      });*/

      // Position it at the right center
      /*
      Object.assign(wrap.style, {
          position: 'fixed',
          right: '20px',
          top: '50%',
          transform: 'translateY(-50%)',
          zIndex: 2147483647,
      });*/

      const btn = document.createElement('button');
      btn.id = '__html_preview_btn__';
      btn.type = 'button';
      btn.innerHTML = 'Click to Preview';

      // Professional dark slate styling
      Object.assign(btn.style, {
          position: 'relative',
          padding: '8px 16px',
          borderRadius: '6px',
          border: '1px solid #334155',
          background: '#1e293b',
          color: '#f8fafc',
          fontSize: '14px',
          fontWeight: '600',
          fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
          cursor: 'pointer',
          boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.15), 0 2px 4px -1px rgba(0, 0, 0, 0.1)',
          userSelect: 'none',
          transition: 'all 0.2s ease',
      });

      // Hover animations
      btn.addEventListener('mouseenter', () => {
          btn.style.background = '#4f46e5';
          btn.style.borderColor = '#6366f1';
      });
      btn.addEventListener('mouseleave', () => {
          btn.style.background = '#1e293b';
          btn.style.borderColor = '#334155';
      });

      // Click action to open preview URL in current tab
      btn.addEventListener('click', (e) => {
          e.preventDefault();
          location.href = PREVIEW_BASE + location.href;
      });

      wrap.appendChild(btn);

      // Append to documentElement safely at document-start execution
      (document.body || document.documentElement).appendChild(wrap);
  }

  function removeButton() {
      document.getElementById('__html_preview_btn_wrap__')?.remove();
  }

  function updateUI() {
      if (isGitHubHtmlFile()) {
          ensureButton();
      } else {
          removeButton();
      }
  }

  // Handles dynamic elements lifecycle changes on SPA page transitions
  function observe() {
      const init = () => {
          updateUI();

          // Observe the document body for mutations to catch SPA view re-renders instantly
          /*if (document.body) {
              new MutationObserver(() => {
                  updateUI();
              }).observe(document.body, { childList: true, subtree: true });
          }*/
          const title = document.querySelector('title');
          if (title) {
            new MutationObserver(updateUI).observe(title, { childList: true, subtree: true });
          }

          if (document.head) {
            new MutationObserver(updateUI).observe(document.head, { childList: true, subtree: true });
          }
      };

      // Fallback checks for safe execution environment attachment points
      if (document.readyState === 'loading') {
          document.addEventListener('DOMContentLoaded', init, { once: true });
      } else {
          init();
      }
  }

  observe();
})();