GitHub README before files

Moves the repository README above the file list on GitHub repository home pages

2026-08-03 기준 버전입니다. 최신 버전을 확인하세요.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         GitHub README before files
// @namespace    https://github.com/
// @version      3.0
// @description  Moves the repository README above the file list on GitHub repository home pages
// @match        https://github.com/*/*
// @match        https://github.com/*/*/
// @exclude      https://github.com/*/*/blob/*
// @exclude      https://github.com/*/*/tree/*
// @exclude      https://github.com/*/*/issues*
// @exclude      https://github.com/*/*/pull*
// @exclude      https://github.com/*/*/actions*
// @exclude      https://github.com/*/*/projects*
// @exclude      https://github.com/*/*/wiki*
// @exclude      https://github.com/*/*/settings*
// @exclude      https://github.com/*/*/security*
// @exclude      https://github.com/*/*/network*
// @exclude      https://github.com/*/*/stargazers*
// @exclude      https://github.com/*/*/watchers*
// @exclude      https://github.com/*/*/forks*
// @exclude      https://github.com/*/*/releases*
// @exclude      https://github.com/*/*/tags*
// @exclude      https://github.com/*/*/commits*
// @exclude      https://github.com/*/*/branches*
// @exclude      https://github.com/*/*/compare/*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  let scheduled = false;
  let lastUrl = location.href;

  function isRepoRootPath(pathname) {
    const clean = pathname.replace(/^\/+|\/+$/g, "");
    const parts = clean.split("/").filter(Boolean);
    return parts.length === 2;
  }

  function getMain() {
    return document.querySelector("main");
  }

  function getReadme(main) {
    return main?.querySelector("#readme") || null;
  }

  function getFiles(main) {
    return (
      main?.querySelector('[aria-labelledby="folders-and-files"]') ||
      main?.querySelector('table[aria-labelledby="folders-and-files"]') ||
      main?.querySelector('[aria-labelledby="files"]') ||
      main?.querySelector(".js-navigation-container") ||
      null
    );
  }

  function getMovableContainer(node) {
    return node?.closest("section, div[data-testid], div.Box, div") || null;
  }

  function isEligiblePage() {
    if (location.hostname !== "github.com") return false;
    if (!isRepoRootPath(location.pathname)) return false;

    const main = getMain();
    if (!main) return false;

    const readme = getReadme(main);
    const files = getFiles(main);

    return Boolean(readme && files);
  }

  function moveReadme() {
    if (!isEligiblePage()) return;

    const main = getMain();
    const readme = getReadme(main);
    const files = getFiles(main);

    if (!readme || !files) return;

    const readmeBox = getMovableContainer(readme);
    const filesBox = getMovableContainer(files);

    if (!readmeBox || !filesBox) return;
    if (!filesBox.parentElement) return;
    if (readmeBox === filesBox) return;
    if (readmeBox.parentElement !== filesBox.parentElement) return;

    const relation = filesBox.compareDocumentPosition(readmeBox);
    const alreadyBefore = Boolean(relation & Node.DOCUMENT_POSITION_PRECEDING);

    if (alreadyBefore) return;

    filesBox.parentElement.insertBefore(readmeBox, filesBox);
  }

  function scheduleMove() {
    if (scheduled) return;
    scheduled = true;
    requestAnimationFrame(() => {
      scheduled = false;
      moveReadme();
    });
  }

  const observer = new MutationObserver(() => {
    if (location.href !== lastUrl) {
      lastUrl = location.href;
    }
    scheduleMove();
  });

  observer.observe(document.documentElement, {
    childList: true,
    subtree: true,
  });

  window.addEventListener("load", scheduleMove, { once: true });
  document.addEventListener("DOMContentLoaded", scheduleMove, { once: true });
  scheduleMove();
})();