GitHub HTML Preview Shortcuts

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

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==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();
})();