GitHub HTML Preview Shortcuts

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

Bu betiği kurabilmeniz için Tampermonkey, Greasemonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği yüklemek için Tampermonkey gibi bir uzantı yüklemeniz gerekir.

Bu betiği kurabilmeniz için Tampermonkey ya da Violentmonkey gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği kurabilmeniz için Tampermonkey ya da Userscripts gibi bir kullanıcı betiği eklentisini kurmanız gerekmektedir.

Bu betiği indirebilmeniz için ayrıca Tampermonkey gibi bir eklenti kurmanız gerekmektedir.

Bu betiği yüklemek için bir betik yöneticisi eklentisi yüklemeniz gerekecektir.

(Zaten bir betik yöneticim var, hadi yükleyelim!)

Bu stili yüklemek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için Stylus gibi bir uzantı kurmanız gerekir.

Bu stili yükleyebilmek için Stylus gibi bir uzantı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

Bu stili yüklemek için bir kullanıcı stili yöneticisi uzantısı kurmanız gerekir.

Bu stili yükleyebilmek için bir kullanıcı stili yöneticisi uzantısı yüklemeniz gerekir.

(Zateb bir user-style yöneticim var, yükleyeyim!)

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