// ==UserScript== // @name GitHub Preview (UserScript) // @name:zh-CN GitHub 预览 (UserScript) // @namespace http://tampermonkey.net/ // @version 0.1.1 // @description Adds a button on GitHub to preview HTML files directly. // @description:zh-CN 在 GitHub 上添加一个按钮,用于直接预览 HTML 文件。 // @author PRO-2684 // @match https://github.com/* // @run-at document-start // @icon http://github.com/favicon.ico // @license gpl-3.0 // @grant none // ==/UserScript== (function () { "use strict"; const PREVIEW_URL = new URL( "https://pro-2684.github.io/GitHub-Preview/?preview=1", ); const OCTICON_ATTRS = 'data-component="Octicon" aria-hidden="true" focusable="false" class="octicon octicon-eye" viewBox="0 0 16 16" width="16" height="16" fill="currentColor" display="inline-block" overflow="visible" style="vertical-align: text-bottom;"'; const EYE_OCTICON = ``; const ANCHOR_BUTTON = "a[data-testid='raw-button']"; const REFERENCE_BUTTON = "button[data-testid='copy-raw-button']"; /** * Create a "Preview" button. * @param {HTMLLinkElement | null} refButton - The reference button. * @param {URL} rawUrl - The GitHub raw URL of the file. * @returns {HTMLDivElement | null} The container element with the preview button and tooltip, or null if creation failed. */ function createPreviewButton(refButton, rawUrl) { const refTooltip = refButton.nextElementSibling; if (!refTooltip) { console.warn( "Reference tooltip not found. Skipping preview button insertion.", ); return; } const previewButton = document.createElement("a"); for (const attr of refButton.attributes) { // Copy attributes from the reference button previewButton.setAttribute(attr.name, attr.value); } // Override specific attributes for the preview button const previewUrl = new URL(PREVIEW_URL); previewUrl.searchParams.set("url", rawUrl.href); previewButton.setAttribute("href", previewUrl.href); previewButton.setAttribute("data-testid", "preview-button"); previewButton.setAttribute("aria-labelledby", "preview-tooltip"); previewButton.setAttribute("interestfor", "preview-tooltip"); // https://developer.mozilla.org/en-US/docs/Web/API/Popover_API previewButton.innerHTML = EYE_OCTICON; // Create the tooltip element const tooltip = document.createElement("div"); for (const attr of refTooltip.attributes) { // Copy attributes from the reference tooltip tooltip.setAttribute(attr.name, attr.value); } // Override specific attributes for the preview tooltip tooltip.setAttribute("id", "preview-tooltip"); tooltip.textContent = "Preview this file"; // Insert the preview button and tooltip const container = document.createElement("div"); container.append(previewButton, tooltip); return container; } /** * Fixes the tooltip position. * @param {HTMLDivElement} container - The container element that holds the preview button and tooltip. */ function fixTooltipPosition(container) { const button = container.querySelector( "a[data-testid='preview-button']", ); const tooltip = container.querySelector("div[id='preview-tooltip']"); const { x, y } = button.getBoundingClientRect(); tooltip.style.left = `${x - 41.7}px`; tooltip.style.top = `${y - 31.5}px`; } /** * Adds preview button if applicable. */ function githubPreview() { console.log("Checking for preview button insertion..."); const buttons = document.querySelector( ".react-blob-header-edit-and-raw-actions", ); const anchorButton = buttons?.querySelector(ANCHOR_BUTTON); const refButton = buttons?.querySelector(REFERENCE_BUTTON); if (!anchorButton || !refButton) { console.warn( "Required buttons not found. Skipping preview button insertion.", ); return; } // Check if the file is an HTML file or if we've already added a preview button const rawUrl = new URL(anchorButton?.href); const extension = rawUrl.pathname.split(".").pop().toLowerCase(); if ( !["html", "htm"].includes(extension) || buttons.querySelector("a[data-testid='preview-button']") ) return; const previewButton = createPreviewButton(refButton, rawUrl); if (!previewButton) return; anchorButton.parentElement.after(previewButton); fixTooltipPosition(previewButton); console.log("Preview button inserted successfully."); } document.addEventListener("soft-nav:react-done", githubPreview); document.addEventListener("turbo:load", githubPreview); })();