GitHub Preview (UserScript)

Adds a button on GitHub to preview HTML files directly.

2026/05/12のページです。最新版はこちら

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==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 = `<svg ${OCTICON_ATTRS}><path d="M8 2c1.981 0 3.671.992 4.933 2.078 1.27 1.091 2.187 2.345 2.637 3.023a1.62 1.62 0 0 1 0 1.798c-.45.678-1.367 1.932-2.637 3.023C11.67 13.008 9.981 14 8 14c-1.981 0-3.671-.992-4.933-2.078C1.797 10.83.88 9.576.43 8.898a1.62 1.62 0 0 1 0-1.798c.45-.677 1.367-1.931 2.637-3.022C4.33 2.992 6.019 2 8 2ZM1.679 7.932a.12.12 0 0 0 0 .136c.411.622 1.241 1.75 2.366 2.717C5.176 11.758 6.527 12.5 8 12.5c1.473 0 2.825-.742 3.955-1.715 1.124-.967 1.954-2.096 2.366-2.717a.12.12 0 0 0 0-.136c-.412-.621-1.242-1.75-2.366-2.717C10.824 4.242 9.473 3.5 8 3.5c-1.473 0-2.825.742-3.955 1.715-1.124.967-1.954 2.096-2.366 2.717ZM8 10a2 2 0 1 1-.001-3.999A2 2 0 0 1 8 10Z"></path></svg>`;
    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);
})();