Adds a professional button to view GitHub HTML files directly via htmlpreview.github.io
// ==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();
})();