您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
A userscript that makes some lists & markdown tables sortable
当前为
- // ==UserScript==
- // @name GitHub Sort Content
- // @version 2.0.3
- // @description A userscript that makes some lists & markdown tables sortable
- // @license MIT
- // @author Rob Garrison
- // @namespace https://github.com/Mottie
- // @include https://github.com/*
- // @include https://gist.github.com/*
- // @run-at document-idle
- // @grant GM.addStyle
- // @grant GM_addStyle
- // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?updated=20180103
- // @require https://cdnjs.cloudflare.com/ajax/libs/tinysort/2.3.6/tinysort.min.js
- // @require https://greasyfork.org/scripts/28721-mutations/code/mutations.js?version=666427
- // @icon https://assets-cdn.github.com/pinned-octocat.svg
- // ==/UserScript==
- (() => {
- "use strict";
- /** Example pages:
- * Tables (Readme & wikis) - https://github.com/Mottie/GitHub-userscripts
- * Repo files - https://github.com/Mottie/GitHub-userscripts (sort content, message or age)
- * Your Repos & Your Teams - https://github.com/
- * Pinned repos (org & user)- https://github.com/:org
- * Organization repos - https://github.com/:org
- * Organization people - https://github.com/orgs/:org/people
- * Organization outside collaborators (own orgs) - https://github.com/orgs/:org/outside-collaborators
- * Organization teams - https://github.com/orgs/:org/teams
- * Repo stargazers - https://github.com/:user/:repo/stargazers
- * Repo watchers - https://github.com/:user/:repo/watchers
- * User repos - https://github.com/:user?tab=repositories
- * User stars - https://github.com/:user?tab=stars
- * User Followers - https://github.com/:user?tab=followers & https://github.com/:user/followers(/you_know)
- * User Following - https://github.com/:user?tab=following & https://github.com/:user/following(/you_know)
- * watching - https://github.com/watching
- */
- /**
- * sortables[entry].setup - exec on userscript init (optional)
- * sortables[entry].check - exec on doc.body click; return truthy/falsy or
- * header element (passed to the sort)
- * sortables[entry].sort - exec if check returns true or a header element;
- * el param is the element returned by check or original click target
- */
- const sortables = {
- // markdown tables
- "tables": {
- // init after a short delay to allow rendering of file list
- setup: () => setTimeout(() => addRepoFileThead(), 200),
- check: el => el.nodeName === "TH" &&
- el.matches(".markdown-body table thead th, table.files thead th"),
- sort: el => initSortTable(el)
- },
- // https://github.com (repo list & teams list)
- "feed": {
- check: el => el.classList.contains("Box-title") &&
- el.closest(".Box.js-repos-container"),
- sort: el => initSortUl(el, $$(".Box-body li", el.closest(".Box")))
- },
- // https://github.com/orgs/:org/dashboard (repo list)
- "org-feed": {
- check: el => el.classList.contains("Box-title") &&
- el.closest("#org_your_repos.js-repos-container"),
- sort: el => initSortUl(el, $$(".boxed-group-inner li", el))
- },
- // https://github.com/(:user|:org) (pinned repos)
- "pinned": {
- check: el => $(".js-pinned-repos-reorder-container") &&
- el.matches(".org-profile.js-pinned-repos-reorder-container h2, .user-profile-nav"),
- sort: el => initSortUl(el, $(".pinned-repos-list").children)
- },
- // https://github.com/:org
- "org-repos": {
- check: el => {
- // Org repos have weirdly nested forms if there are pinned repos
- let wrap = false;
- if ($(".org-repos.repo-list") && el.matches(".TableObject, .TableObject-item")) {
- wrap = el.closest("form[data-pjax='#org-repositories']");
- if (wrap) {
- wrap = wrap.parentNode;
- } else {
- wrap = el;
- }
- return wrap && wrap.classList.contains("TableObject") ? wrap : false;
- }
- return wrap;
- },
- sort: el => {
- const list = $(".org-repos.repo-list");
- initSortUl(el, list.children);
- movePaginate(list);
- }
- },
- // https://github.com/orgs/:org/people
- "org-people": {
- setup: () => checkOwnOrg(),
- check: (el, loc) => loc.href.indexOf("/people") > -1 &&
- $("#org-members-table") && el.matches(".org-toolbar.ghsc-org-people"),
- sort: el => initSortUl(el, $$("#org-members-table li"), ".member-info a")
- },
- // https://github.com/orgs/:org/outside-collaborators (own org)
- "org-collab-own": {
- check: (el, loc) => loc.href.indexOf("/outside-collaborators") > -1 &&
- $("#org-outside-collaborators") && el.matches(".org-toolbar.ghsc-org-outside_collaborators"),
- sort: el => initSortUl(el, $$("#org-outside-collaborators li"), ".member-info a")
- },
- // https://github.com/orgs/:org/teams
- "org-teams": {
- check: el => $("#org-teams") && el.matches(".ghsc-org-teams.subnav.org-toolbar"),
- sort: el => initSortUl(el, $$("#org-teams li"), ".team-name")
- },
- // https://github.com/:user?tab=repositories
- "user-repos": {
- check: (el, loc) => loc.search.indexOf("tab=repositories") > -1 &&
- el.classList.contains("user-profile-nav"),
- sort: el => initSortUl(el, $$("#user-repositories-list li"))
- },
- // https://github.com/:user?tab=stars
- "user-stars": {
- check: (el, loc) => loc.search.indexOf("tab=stars") > -1 &&
- el.classList.contains("user-profile-nav"),
- sort: el => {
- const list = $(".TableObject").parentNode;
- initSortUl(el, $$(".col-12", list), "h3 a");
- movePaginate(list);
- }
- },
- // https://github.com/:user?tab=follow(ers|ing)
- "user-tab-follow": {
- check: (el, loc) => loc.search.indexOf("tab=follow") > -1 &&
- el.classList.contains("user-profile-nav"),
- sort: el => {
- const list = $(".table-fixed").parentNode;
- initSortUl(el, $$(".col-12", list), ".col-9 a.no-underline");
- movePaginate(list);
- }
- },
- // https://github.com/:user/follow(ers|ing)
- // https://github.com/:user/follow(ers|ing)/you_know
- "user-follow": {
- setup: () => {
- if (window.location.href.indexOf("/follow") > -1) {
- const repo = $(".userrepos, .follow-list");
- const wrap = repo && repo.closest(".container");
- if (wrap) {
- $("h2", wrap).classList.add("ghsc-header");
- repo.classList.add("ghsc-active");
- }
- }
- },
- check: el => $(".userrepos.ghsc-active, .follow-list.ghsc-active") && el.matches("h2.ghsc-header"),
- sort: el => initSortUl(el, $$(".userrepos li, .follow-list li"), ".follow-list-name")
- },
- // https://github.com/watching
- "user-watch": {
- check: (el, loc) => loc.href.indexOf("/watching") > -1 &&
- el.matches(".subscriptions-content .Box-header h3, .subscriptions-content .Box-header .text-right"),
- sort: el => initSortUl(el.closest(".Box-header"), $$(".standalone.repo-list li"))
- },
- // https://github.com/:user/repo/(stargazers|watchers)
- "repo-stars-or-watchers": {
- check: (el, loc) => (loc.href.indexOf("/stargazers") > -1 ||
- loc.href.indexOf("/watchers") > -1) &&
- $(".follow-list") && el.matches("#repos > h2"),
- sort: el => initSortUl(el, $$(".follow-list-item"), ".follow-list-name")
- }
- };
- const sorts = ["asc", "desc"];
- const icons = {
- unsorted: color => `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${color}">
- <path d="M15 8H1l7-8zm0 1H1l7 7z" opacity=".2"/>
- </svg>`,
- asc: color => `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${color}">
- <path d="M15 8H1l7-8z"/>
- <path d="M15 9H1l7 7z" opacity=".2"/>
- </svg>`,
- desc: color => `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="${color}">
- <path d="M15 8H1l7-8z" opacity=".2"/>
- <path d="M15 9H1l7 7z"/>
- </svg>`
- };
- function getIcon(type, color) {
- return "data:image/svg+xml;charset=UTF-8," + encodeURIComponent(icons[type](color));
- }
- function needDarkTheme() {
- // color will be "rgb(#, #, #)" or "rgba(#, #, #, #)"
- let color = window.getComputedStyle(document.body).backgroundColor;
- const rgb = (color || "")
- .replace(/\s/g, "")
- .match(/^rgba?\((\d+),(\d+),(\d+)/i);
- if (rgb) {
- // remove "rgb.." part from match & parse
- const colors = rgb.slice(1).map(Number);
- // http://stackoverflow.com/a/15794784/145346
- const brightest = Math.max.apply(null, colors);
- // return true if we have a dark background
- return brightest < 128;
- }
- // fallback to bright background
- return false;
- }
- function addRepoFileThead() {
- const $table = $("table.files");
- if ($table && !$(".ghsc-header", $table)) {
- const thead = document.createElement("thead");
- thead.innerHTML = `<tr class="ghsc-header">
- <td></td>
- <th>Content</th>
- <th>Message</th>
- <th class="ghsc-age">Age</th>
- </tr>`;
- $table.insertBefore(thead, $table.childNodes[0]);
- }
- }
- function initSortTable(el) {
- removeSelection();
- const dir = el.classList.contains(sorts[0]) ? sorts[1] : sorts[0],
- table = el.closest("table"),
- options = {
- order: dir,
- natural: true,
- selector: `td:nth-child(${el.cellIndex + 1})`
- };
- if (el.classList.contains("ghsc-age")) {
- // sort repo age column using ISO 8601 datetime format
- options.selector += " [datetime]";
- options.attr = "datetime";
- }
- tinysort($$("tbody tr:not(.up-tree)", table), options);
- $$("th", table).forEach(elm => {
- elm.classList.remove(...sorts);
- });
- el.classList.add(dir);
- }
- function initSortUl(arrows, list, selector) {
- if (list) {
- removeSelection();
- const dir = arrows.classList.contains(sorts[0]) ? sorts[1] : sorts[0],
- options = {
- order: dir,
- natural: true
- };
- if (selector) {
- options.selector = selector;
- }
- tinysort(list, options);
- arrows.classList.remove(...sorts);
- arrows.classList.add(dir);
- }
- }
- function getFixedHeader() {
- // Is https://github.com/StylishThemes/GitHub-FixedHeader active?
- const header = window.getComputedStyle($(".Header"));
- const height = header.position === "fixed" && parseInt(header.height, 10);
- // Adjust sort arrow position
- return height ?
- `.user-profile-nav.js-sticky.is-stuck {
- background-position:calc(100% - 5px) ${height + 20}px !important;
- }` : "";
- }
- // The paginate block is a sibling along with the items in the list...
- // it needs to be moved to the end
- function movePaginate(list) {
- list.appendChild($(".paginate-container", list));
- }
- // Own organization repo has admin stuff, so the layout needs to be
- // adjusted slightly
- function checkOwnOrg() {
- // div[data-bulk-actions-url$="people/toolbar_actions"] .subnav.org-toolbar
- const el = $(".subnav.org-toolbar");
- const wrapper = el && el.closest("div[data-bulk-actions-url]");
- if (wrapper) {
- // "/orgs/:org/people/toolbar_actions"
- const type = wrapper.getAttribute("data-bulk-actions-url").split("/")[3]
- el.classList.add("ghsc-org", `ghsc-org-${type}`);
- }
- // Own org people
- if (
- sortables["org-people"].check(el, window.location) &&
- $(".member-list-item.adminable")
- ) {
- // Own org shows an admin table
- el.classList.add("ghsc-own-org");
- }
- }
- function $(str, el) {
- return (el || document).querySelector(str);
- }
- function $$(str, el) {
- return [...(el || document).querySelectorAll(str)];
- }
- function removeSelection() {
- // remove text selection - http://stackoverflow.com/a/3171348/145346
- const sel = window.getSelection ?
- window.getSelection() :
- document.selection;
- if (sel) {
- if (sel.removeAllRanges) {
- sel.removeAllRanges();
- } else if (sel.empty) {
- sel.empty();
- }
- }
- }
- function update() {
- Object.keys(sortables).forEach(item => {
- if (sortables[item].setup) {
- sortables[item].setup();
- }
- });
- }
- function init() {
- const color = needDarkTheme() ? "#ddd" : "#222";
- const userSortPosition = getFixedHeader();
- GM.addStyle(`
- tr.ghsc-header th, tr.ghsc-header td {
- border-bottom: #eee 1px solid;
- padding: 2px 2px 2px 10px;
- }
- /* unsorted icon */
- .markdown-body table thead th, table.files thead th,
- .markdown-body table.csv-data thead th {
- cursor: pointer;
- padding-right: 22px !important;
- background-image: url(${getIcon("unsorted", color)}) !important;
- background-repeat: no-repeat !important;
- background-position: calc(100% - 5px) center !important;
- text-align: left;
- }
- .js-repos-container h3.Box-title,
- #org_your_repos h3.Box-title,
- .org-profile .TableObject:first-child,
- .ghsc-org.subnav.org-toolbar,
- .user-profile-nav.js-sticky,
- .user-profile-nav.js-sticky.is-stuck,
- .org-profile.js-pinned-repos-reorder-container h2,
- .subscriptions-content .Box-header .text-right,
- #repos > h2,
- h2.ghsc-header {
- cursor:pointer;
- background-image: url(${getIcon("unsorted", color)}) !important;
- background-repeat: no-repeat !important;
- background-position: calc(100% - 5px) center !important;
- }
- /* https://github.com/ -> your repositories */
- .dashboard-sidebar .js-repos-container h3 {
- background-position: 115px 5px !important;
- }
- /* https://github.com/ -> your teams */
- .dashboard-sidebar #your_teams h3 {
- background-position: 240px 10px !important;
- }
- /* pinned repos */
- .org-profile.js-pinned-repos-reorder-container h2 {
- background-position: 150px 5px !important;
- }
- /* https://github.com/:user?tab=repositories */
- .user-profile-nav.js-sticky {
- background-position: calc(100% - 5px) 22px !important;
- }
- ${userSortPosition}
- /* https://github.com/:org repos */
- .org-profile > div > .TableObject {
- width: 100%; /* Fix width of org with no pinned repos */
- padding-right: 30px;
- background-position: right 10px !important;
- }
- .org-profile form + .TableObject-item .ml-6,
- .org-profile .TableObject-item .mr-6 {
- margin-left: 2px !important;
- margin-right: 2px !important;
- }
- .org-profile .TableObject {
- background-position: calc(100% - 12px) 10px !important;
- }
- /* Own org people; collaborators page doesn't need adjusting */
- .ghsc-org-people.ghsc-own-org.subnav.org-toolbar,
- .ghsc-org-teams.subnav.org-toolbar,
- #org_your_repos h3.Box-title {
- background-position: calc(100% - 135px) center !important;
- }
- /* https://github.com/watching */
- .subscriptions-content .Box-header .text-right {
- background-position: 5px 7px !important;
- }
- /* Hide "Sorted by most recently watched" text when sorted */
- .subscriptions-content .Box-header.asc .text-right > .text-small,
- .subscriptions-content .Box-header.desc .text-right > .text-small {
- display: none;
- }
- /* https://github.com/watching */
- .subscriptions-content .Box-header {
- background-position: 160px 15px !important;
- }
- /* asc/dec icons */
- table thead th.asc,
- .markdown-body table.csv-data thead th.asc,
- .js-repos-container.asc .Box-title,
- #org_your_repos.asc .Box-title,
- .org-profile .TableObject.asc,
- .js-bulk-actions-container .subnav.org-toolbar.asc,
- .user-profile-nav.asc,
- .user-profile-nav.is-stuck.asc,
- .org-profile.js-pinned-repos-reorder-container h2.asc,
- .subscriptions-content .Box-header.asc .text-right,
- #repos > h2.asc,
- h2.ghsc-header.asc {
- background-image: url(${getIcon("asc", color)}) !important;
- background-repeat: no-repeat !important;
- }
- table thead th.desc,
- .markdown-body table.csv-data thead th.desc,
- .js-repos-container.desc .Box-title,
- #org_your_repos.desc .Box-title,
- .org-profile .TableObject.desc,
- .js-bulk-actions-container .subnav.org-toolbar.desc,
- .user-profile-nav.desc,
- .user-profile-nav.is-stuck.desc,
- .org-profile.js-pinned-repos-reorder-container h2.desc,
- .subscriptions-content .Box-header.desc .text-right,
- #repos > h2.desc,
- h2.ghsc-header.desc {
- background-image: url(${getIcon("desc", color)}) !important;
- background-repeat: no-repeat !important;
- }
- `);
- document.body.addEventListener("click", event => {
- const target = event.target;
- const loc = window.location;
- if (target && target.nodeType === 1) {
- Object.keys(sortables).some(item => {
- const el = sortables[item].check(target, loc);
- if (el) {
- sortables[item].sort(el instanceof HTMLElement ? el : target);
- event.preventDefault();
- return true;
- }
- return false;
- });
- }
- });
- update();
- }
- document.addEventListener("ghmo:container", () => update());
- init();
- })();