GitHub HTML Preview Shortcuts

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

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey, Greasemonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да инсталирате разширение, като например Tampermonkey .

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Violentmonkey.

За да инсталирате този скрипт, трябва да имате инсталирано разширение като Tampermonkey или Userscripts.

За да инсталирате скрипта, трябва да инсталирате разширение като Tampermonkey.

За да инсталирате този скрипт, трябва да имате инсталиран скриптов мениджър.

(Вече имам скриптов мениджър, искам да го инсталирам!)

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да инсталирате разширение като Stylus.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

За да инсталирате този стил, трябва да имате инсталиран мениджър на потребителски стилове.

(Вече имам инсталиран мениджър на стиловете, искам да го инсталирам!)

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