NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @name GitHub Search Autocomplete // @version 1.0.2 // @description A userscript that adds autocomplete search filters to GitHub // @license MIT // @author Rob Garrison // @namespace https://github.com/Mottie // @match https://github.com/* // @match https://gist.github.com/* // @run-at document-idle // @grant GM_addStyle // @require https://code.jquery.com/jquery-3.2.1.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/Caret.js/0.3.1/jquery.caret.min.js // @require https://cdnjs.cloudflare.com/ajax/libs/at.js/1.5.4/js/jquery.atwho.min.js // @icon https://github.githubassets.com/pinned-octocat.svg // @updateURL https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-search-autocomplete.user.js // @downloadURL https://raw.githubusercontent.com/Mottie/Github-userscripts/master/github-search-autocomplete.user.js // @supportURL https://github.com/Mottie/GitHub-userscripts/issues // ==/UserScript== (($) => { "use strict"; const data = {}, // search input classes used by GitHub selectors = [ ".header-search-input", // main header search "[aria-label*='Search']", // https://github.com/search ".search-page-input", // https://github.com/search/advanced "#js-issues-search", // https://github.com/:user/:repo/issues & pulls ".js-search-query" // https://gist.github.com/search?q=css ].join(","), // update examples using current & previous year year = new Date().getFullYear(), /** * This data was manually extracted from the pages listed on * https://help.github.com/categories/search/ */ filters = { "AND": { "": "search operator (max 5 of any operator)" }, // Issue "assignee": { "": "search for issues assigned to the named user", "fred": 'search for issues assigned to "@fred"' }, // Commits & Issues "author": { "": "search for issues or commits authored by the username", "fred": 'search for issues or commits authored by "@fred"' }, "author-date": { "": "search commits authored before or after a date", ">${year-1}-12-31": "search commits authored since ${year}", ">=${year}-01-01": "search commits authored since ${year}", "<${year}-01-01": "search commits authored before ${year}", "<=${year-1}-12-31": "search commits authored before ${year}" }, "author-email": { "": "search for a commit authored by the specified user email", "fred@gmail.com": "search for commits authored by gmail fred" }, "author-name": { "": "search for a commit created using the author's actual name", "smith": 'search for commits authored by someone named "smith"' }, // Issues "base": { "": "search pull requests to be merged into the specified branch name", "gh-pages": 'search pull requests being merged into the "gh-pages" branch' }, // Issues "closed": { "": "search issues & pull requests closed before or after a date", ">${year-1}-12-31": "search issues & pull requests closed since ${year}", ">=${year}-01-01": "search issues & pull requests closed since ${year}", "<${year}-01-01": "search issues & pull requests closed before ${year}", "<=${year-1}-12-31": "search issues & pull requests closed before ${year}" }, "commenter": { "": "search for comments authored by the named user", "fred": 'search for comments authored by "@fred"' }, "comments": { "": "search issues with specified number of comments", "100": "search issues with exactly 100 comments", ">100": "search issues with >100 comments", ">=100": "search issues with >=100 comments", "10..20": "search issues with 10-20 comments", "<100": "search issues with <100 comments", "<=100": "search issues with <=100 comments" }, "committer": { "": "search for commits authored by the named user", "fred": 'search for commits authored by "@fred"' }, "committer-date": { "": "search commits authored before or after a date", ">${year-1}-12-31": "search commits authored since ${year}", ">=${year}-01-01": "search commits authored since ${year}", "<${year}-01-01": "search commits authored before ${year}", "<=${year-1}-12-31": "search commits authored before ${year}" }, "committer-email": { "": "search for a commit authored by the specified user email", "fred@gmail.com": "search for commits authored by gmail fred" }, "committer-name": { "": "search for a commit created using the author's actual name", "smith": 'search for commits authored by someone named "smith"' }, // Issues & user "created": { "": "search issues & user accounts created at the specified date", ">${year-1}-12-31": "search issues & user accounts created since ${year}", ">=${year}-01-01": "search issues & user accounts created since ${year}", "${year}-01-01..*": "search issues & user accounts created since ${year}", "${year}-01-01..${year}-01-31": "search commits authored in Jan ${year}", "<${year}-01-01": "search issues & user accounts created before ${year}", "<=${year-1}-12-31": "search issues & user accounts created before ${year}", "*..${year-1}-12-31": "search issues & user accounts created before ${year}" }, // Code "extension": { "": "search within file extensions", "js": "search within JavaScript files", "rb": "search within Ruby files", "css": "search within CSS files", "coffee": "search within CoffeeScript files", "md": "search within markdown files" }, "-extension": { "": "search excludes this file extension", "js": "search excludes JavaScript files", "rb": "search excludes Ruby files", "css": "search excludes CSS files", "coffee": "search excludes CoffeeScript files", "md": "search excludes markdown files" }, // Code "filename": { "": "search within code files named as specified", "test_helper": 'search within the "test_helper" code file(s)', ".vimrc": 'search "*.vimfc*" code files named as specified' }, // User "followers": { "": "search users & organizations with the specified number of followers", "100": "search users with exactly 100 followers", ">100": "search users with >100 followers", ">=100": "search users with >=100 followers", "10..20": "search users with 10-20 followers", "<100": "search users with <100 followers", "<=100": "search users with <=100 followers" }, // Code... includes "-fork"? "fork": { "": "searches exclude forks when this filter is undefined", "only": "search only within forked repos", "true": "search includes forked repos" }, // Repos, Code "forks": { "": "search repos with greater, less, or a range of forks", "100": "search repos with exactly 100 forks", ">100": "search repos with >100 forks", ">=100": "search repos with >=100 forks", "10..20": "search repos with 10-20 forks", "<100": "search repos with <100 forks", "<=100": "search repos with <=100 forks" }, "-forks": { "": "search repos outside of the selected number of forks", "100": "search repos with that do not have exactly 100 forks", ">100": "search repos with <=100 forks", ">=100": "search repos with <100 forks", "10..20": "search repos with <10 or >20 forks", "<100": "search repos with >=100 forks", "<=100": "search repos with >100 forks" }, "fullname": { "": "search within a user's full name", "linus": 'search for users with "linus" in their full name', '"Linus Torvalds"': "search for a full name wrapped in quotes" }, // Commits "hash": { "": "search commits with the specified SHA-1 hash", "124a9a0ee1d8f1e15e833aff432fbb3b02632105": "search matching hash" }, // Issues "head": { "": "search pull requests opened from the specified branch name", "fix": 'search pull requests opened from branch names containing the word "fix"' }, // Repos, Issue, User "in": { "": "search within repo, issue or user meta data", // Repo "body": "search within an issue or wiki body", "description": "search within the repo description", "file": "search within the repo file contents", "file,path": "search within the repo file contents & path name", "name": "searh within a user, organization or repo name", "name,description" : "search within the repo name or description", "path": "search within the repo path name", "readme": "search within the repo readme", // Issue & Wiki "comments": "search within an issue comments", "title": "search within an issue or wiki title", "title,body": "search within an issue or wiki title & body", // User "email": "search within a user or organization email (not the domain name)", "login": "search within a user or organization username", "fullname": "search within a user's full name" }, "involves": { "": "search for issues or commits that involves the named user", "fred": 'search for issues or commits that involve "@fred"' }, // Commits "is": { "": "search commits and issues of the specified state (multiple allowed)", "public": "search public commits & issues", "private": "search private commits & issues", "open": "search open issues & pull requests", "closed": "search closed issues & pull requests", "issue": "search within issues", "pr": "search within pull requests", "merged": "search merged pull requests", "unmerged": "search unmerged pull requests" }, // Issue "label": { "": "search issues & pull requests with the specified label (multiple allowed)", "bug": 'search for issues & pull requests labeled "bug"', '"in progress"': "search for multiword labels inside quotes" }, "-label": { "": "search issues & pull requests without the specified label", "bug": 'search for issues & pull requests not labeled "bug"' }, // Code, Issue, Repos, User "language": { "": "search within this language", "javascript": "search within repos containing JavaScript", "php": "search within repos containing PHP", "scss": "search within repos containing SCSS", "c#": "search within repos containing C#", "markdown": "search within repos containing markdown (.md, .markdown, etc)", }, "-language": { "": "search excludes this language", "javascript": "search excludes repos with JavaScript", "php": "search excludes repos with PHP", "scss": "search excludes repos with SCSS", "xml": "search excludes repos with XML", "markdown": "search excludes repos with markdown (.md, .markdown, etc)", }, "location": { "": "search users within a location", '"San Francisco, CA"': "search users in San Francisco", "london": "search users in london", "iceland": "search users in Iceland" }, "-location": { "": "search users not in a specific location", '"San Francisco, CA"': "search users not in San Francisco", "london": "search users not in london", "iceland": "search users not in Iceland" }, // Issues "mentions": { "": "search for issues mentioning the named user", "fred": 'search for issues that mention "@fred"' }, // Commits "merge": { "true": "searches that match merge commits", "false": "searches that match non-merge commits" }, "merged": { "": "search pull requests merged at the specified date", ">${year-1}-12-31": "search pull requests merged since ${year}", ">=${year}-01-01": "search pull requests merged since ${year}", "<${year}-01-01": "search pull requests merged before ${year}", "<=${year-1}-12-31": "search pull requests merged before ${year}" }, "milestone": { "": "search for issues & pull requests within the specified milestone", "sprint-42": 'search for issues & pull requests in the "sprint-42" milestone' }, "no": { "": "search issues & pull requests missing the specified association", "label": "search issues & pull requests that don't have a label", "assignee": "search issues & pull requests that don't have an assignee", "milestone": "search issues & pull requests that don't have a milestone", "project": "search issues & pull requests that don't have a project" }, "NOT": { "": "search operator (max 5 of any operator)" }, "OR": { "": "search operator (max 5 of any operator)" }, "org": { "": "search within the named organization", "github": "search GitHub's repositories" }, // Commits "parent": { "": "search children of specified SHA-1 hash", "124a9a0ee1d8f1e15e833aff432fbb3b02632105": "search children of hash" }, "path": { "": "search code within the specific file path (directory)", "cgi-bin": 'search code within the "cgi-bin" folder', "test/spec": "search code within sub-folders" }, "project": { "": "search issues within a specified repository project board", "github/linguist/1": "search issues in GitHub's linguist repo project board 1" }, // Repos "pushed": { "": "search repo pushes before or after a date (no range)", ">${year}-01-15": "search repos pushed to after Jan 15, ${year}", ">=${year}-01-15": "search repos pushed to since Jan 15, ${year}", "<${year}-02-01": "pushed to before Feb ${year}", "<=${year}-02-01": "pushed to before and including Feb 1, ${year}" }, "-pushed": { "": "search pushes opposite of selected dates (no range)", ">${year}-01-15": "search repos pushed to after Jan 15, ${year}", ">=${year}-01-15": "search repos pushed to since Jan 15, ${year}", "<${year}-02-01": "pushed to before Feb ${year}", "<=${year}-02-01": "pushed to before and including Feb 1, ${year}" }, // Commits "repo": { "": "search within the specified user's repository", "torvalds/linux": "search commits within Linus' Linux repository" }, "repos": { "": "search user or organization with specified number of repos", "100": "search user or org with exactly 100 repos", ">100": "search user or org with >100 repos", ">=100": "search user or org with >=100 repos", "10..20": "search user or org with 10-20 repos", "<100": "search user or org with <100 repos", "<=100": "search user or org with <=100 repos" }, "review": { "": "search pull requests with the specified review state", "none": "search pull requests that have not been reviewed", "required": "search pull requests that require a review before merge", "approved": "search pull requests that have been approved", "changes_requested": "search pull requests where a reviewer requested changes" }, "review-requested": { "": "search pull requests with a requested reviewer, but only before they review the PR", "fred": 'search pull requests where "@fred" was asked to review' }, "reviewed-by": { "": "search pull requests that have been reviewed by the specified user", "fred": 'search pull requests reviewed by "@fred"' }, // Repos, Code - include "-size"? "size": { "": "search repos or files with a specific size (in KB)", "1000": "search repos or files that are exactly 1 MB in size", ">10000": "search repos or files that are more than 10 MB in size", ">=10000": "search repos or files that are at least 10 MB in size", "1024..4096": "search repos or files between 1024 and 4096 KB in size", "<1000": "search repos or files less than 1 MB in size", "<=100": "search repos or files that are less than or equal to 100 KB in size" }, "sort": { "": 'apply an ascending or descending sort to the specified filter; add "-asc" or "-desc"', "author-date-asc": "sort oldest authored date first", "author-date-desc": "sort newest authored date first", "comments-asc": "sort least comments fist", "comments-desc": "sort most comments first", "committer-date-asc": "sort oldest committer date first", "committer-date-desc": "sort newest committer date first", "created-asc": "sort oldest item first", "created-desc": "sort newest item first", "forks-asc": "sort least forks first", "forks-desc": "sort most forks first", "reactions-+1-asc": "sort least +1 reactions first", "reactions-+1-desc": "sort most +1 reactions first", "reactions--1-asc": "sort least -1 reactions first", "reactions--1-desc": "sort most -1 reactions first", "reactions-heart-asc": "sort least heart reactions first", "reactions-heart-desc": "sort most heart reactions first", "reactions-smile-asc": "sort least smile reactions first", "reactions-smile-desc": "sort most smile reactions first", "reactions-tada-asc": "sort least tada reactions first", "reactions-tada-desc": "sort most tada reactions first", "reactions-thinking_face-asc": "sort least thinking face reactions first", "reactions-thinking_face-desc": "sort most thinking face reactions first", "stars-asc": "sort least stars first", "stars-desc": "sort most stars first", "updated-asc": "sort least recently updated first", "updated-desc": "sort recently updated first" }, "stars": { "": "search repos with greater, less, or a range of stars", "100": "search repos with exactly 100 stars", ">100": "search repos with >100 stars", ">=100": "search repos with >=100 stars", "10..20": "search repos with 10-20 stars", "<100": "search repos with <100 stars", "<=100": "search repos with <=100 stars" }, "-stars": { "": "search repos outside of the selected number of stars", "100": "search repos with that do not have exactly 100 stars", ">100": "search repos with <=100 stars", ">=100": "search repos with <100 stars", "10..20": "search repos with <10 or >20 stars", "<100": "search repos with >=100 stars", "<=100": "search repos with >100 stars" }, "state": { "closed": "search closed issues", "open": "search open issues" }, "status": { "": "search pull requests with the specified status", "success": "search pull requests with a successful status", "pending": "search pull requests with a pending status", "failed": "search pull requests with a failed status" }, "team": { "": "search issues or pull requests mentioning an organization team", "jekyll/owners": 'search issues or pull requests for team "@jekyll/owners"', "myorg/ops": 'search issues or pull requests for team "@myorg/ops"' }, "topic": { "": "search repos with the specified topic", "jekyll": 'search for repos classified with a "jekyll" topic' }, "topics": { "": "search repos with greater, less, or a range of topics", "3": "search repos with exactly 3 topics", ">3": "search repos with more than three topics", ">=3": "search repos with three or more topics", "<3": "search repos with less than 3 topics", "<=2": "search repos with zero to two topics" }, // Commits "tree": { "": "searches commits referring to the specified tree hash", "99ca967": "search commits of tree hash" }, "type": { "": "search for a user, organization, issue or pull request", "issue": "only search issues", "pr": "only search pull requests", "org": "only search organizations", "user": "only search personal accounts" }, // Wiki "updated": { "": "search issue & wiki updates before or after a date (no range)", ">${year}-01-31": "search repos pushed to after Jan 31, ${year}", ">=${year}-01-31": "search repos pushed to since Jan 31, ${year}", "<${year}-02-01": "pushed to before Feb ${year}", "<=${year}-02-01": "pushed to before and including Feb 1, ${year}" }, // User "user": { "": "search for a specific user or organization", "github": "search in github organization repos" }, "-user": { "": "search excludes a specific user or organization", "github": "search does not include github organization repos" } }, // array containing items that should not include a trailing colon noTrailingColon = [ "AND", "OR", "NOT" ], list = Object.keys(filters); function updateYear(string) { return string.replace(/(\$\{year(-1)?\})/g, function(str) { return { "${year}": year, "${year-1}": year - 1 }[str]; }); } // copied from atwho.js DEFAULT_CALLBACKS.highlighter // https://github.com/ichord/At.js/blob/master/dist/js/jquery.atwho.js#L105 // to add a class to the <strong> html function highlighter(li, query) { if (!query) { return li; } const regexp = new RegExp( ">\\s*([^\<]*?)(" + query.replace("+", "\\+") + ")([^\<]*)\\s*<", "ig" ); return li.replace(regexp, function(str, $1, $2, $3) { return `>${$1}<strong class="text-emphasized">${$2}</strong>${$3} <`; }); } function escapeHTML(string) { return updateYear(string).replace(/[<>"&]/g, function(str) { return { "<" : "<", ">" : ">", '"' : """, "&" : "&" }[str]; }); } function addAtJs() { const $selectors = $(selectors); // add "?" to open list of filters $selectors.atwho({ at: "?", data: list, insertTpl: "${name}", // show everything in dropdown limit: list.length, suffix: "", callbacks: { highlighter: highlighter, beforeInsert: function(value) { // add colon suffix, as needed return value + (noTrailingColon.includes(value) ? " " : ":"); } }, }); // Add specific filter examples list.forEach(label => { $selectors.atwho({ at: label + (noTrailingColon.includes(label) ? " " : ":"), data: data[label], limit: 20, startWithSpace: false, callbacks: { highlighter: highlighter, tplEval: function(tpl, map) { // look for default displayTpl = "<li>${name}</li>" if (tpl.indexOf("<li>") > -1) { // menu template; text-emphasized needed for GitHub-Dark userstyle return ` <li class="navigation-item"> <strong class="ghsa-key text-emphasized"> ${escapeHTML(map.name)} </strong> <small>${escapeHTML(map.description)}</small> </li>`; } // insert text template return `${map["atwho-at"]}${updateYear(map.name)}`; }, sorter: function(query, items) { // reset suffix setting this.setting.suffix = " "; return items; }, beforeInsert: function(value) { // don't add a space if the user chooses an empty string value // meaning the filter ends with a colon, e.g. "in:" this.setting.suffix = value.slice(-1) === ":" ? "" : " "; return value; } } }); }); // use classes from GitHub-Dark to make theme match GitHub-Dark document.querySelectorAll(".atwho-view").forEach(el => { el.classList.add(...["jump-to-suggestions-results-container", "Box"]); }); } // prevent reinitializing if user clicks in the input multiple times function init() { // build data for At.js let array; list.forEach(label => { array = []; Object.keys(filters[label]).forEach(key => { array.push({ "name": key, "description": filters[label][key] }); }); // sort empty string to top data[label] = array.sort((a, b) => { if (a.name === "") { return -1; } return a.name > b.name ? 1 : -1; }); }); document.querySelector("body").addEventListener("click", event => { const target = event.target; if ( target.nodeName === "INPUT" && target.matches(selectors) ) { $(selectors).atwho("destroy"); addAtJs(); } }); // remove At.js before the page refreshes document.querySelector("body").addEventListener("pjax:start", event => { const target = event.target; if (target.nodeName === "INPUT" && target.matches(selectors)) { $(selectors).atwho("destroy"); } }); } GM_addStyle(` .atwho-view { position:absolute; top:0; left:0; display:none; margin-top:18px; border:1px solid #ddd; border-radius:3px; box-shadow:0 0 5px rgba(0,0,0,0.1); max-height:225px; min-width:300px; max-width:none !important; overflow: auto; z-index: 11110 !important; } .atwho-view .cur { background:#36F; color:#fff; } .atwho-view .cur small { color:#fff; } .atwho-view strong { color:#36F; } .atwho-view .cur strong { color:#fff; font:bold; } .atwho-view ul { list-style:none; padding:0; margin:auto; max-height: 200px; overflow-y:auto; } .atwho-view ul li { display:block; padding:5px 10px; border-bottom: 1px solid #ddd; cursor:pointer; text-align:right; } .atwho-view small { font-size:smaller; color:#777; font-weight:normal; } .atwho-view .ghsa-key { font-style:normal; float:left; margin-right:10px; }` ); init(); })(jQuery.noConflict(true));