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