├── .jshintignore ├── .gitignore ├── .npmignore ├── .jshint ├── userscript.header.ejs ├── lib ├── core │ ├── plugins.js │ ├── storage.js │ └── utils.js ├── plugins │ ├── repo-counts.js │ ├── twitter-link.js │ ├── pull-request-links.js │ ├── gh-pages-link.js │ ├── fork-count.js │ ├── settings.js │ └── repo-filter-info.js ├── index.js └── extras │ └── ay-pie-chart.js ├── package.json ├── .gitmodules ├── README.md ├── gulpfile.js └── plugins-invalid ├── editor-theme.js └── code-search.js /.jshintignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .project -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | _ignore/ 3 | build/ 4 | test/ 5 | .DS_Store 6 | .npm-debug.log 7 | .project 8 | .travis.yml 9 | TODO.md 10 | -------------------------------------------------------------------------------- /.jshint: -------------------------------------------------------------------------------- 1 | { 2 | "curly": false, 3 | "noempty": true, 4 | "newcap": false, 5 | "eqeqeq": true, 6 | "eqnull": true, 7 | "undef": true, 8 | "devel": true, 9 | "node": true, 10 | "browser": true, 11 | "evil": false, 12 | "latedef": true, 13 | "nonew": true, 14 | "trailing": true, 15 | "immed": true, 16 | "smarttabs": true, 17 | "strict": true, 18 | "globals": { 19 | "define": true 20 | } 21 | } -------------------------------------------------------------------------------- /userscript.header.ejs: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name GitHub Enhancement Suite 3 | // @namespace https://github.com/skratchdot/github-enhancement-suite 4 | // @description <%= description %> 5 | // @include https://github.com/* 6 | // @match https://github.com/* 7 | // @run-at document-end 8 | // @icon http://skratchdot.com/favicon.ico 9 | // @downloadURL https://github.com/skratchdot/github-enhancement-suite/raw/master/enhancement-suite.user.js 10 | // @updateURL https://github.com/skratchdot/github-enhancement-suite/raw/master/enhancement-suite.user.js 11 | // @version <%= version %> 12 | // ==/UserScript== 13 | -------------------------------------------------------------------------------- /lib/core/plugins.js: -------------------------------------------------------------------------------- 1 | /* autogenerated by running `gulp plugins` (see gulpfile.js) */ 2 | "use strict"; 3 | exports["fork-count"] = require("../plugins/fork-count"); 4 | exports["gh-pages-link"] = require("../plugins/gh-pages-link"); 5 | exports["pull-request-links"] = require("../plugins/pull-request-links"); 6 | exports["repo-counts"] = require("../plugins/repo-counts"); 7 | exports["repo-filter-info"] = require("../plugins/repo-filter-info"); 8 | exports.settings = require("../plugins/settings"); 9 | exports["twitter-link"] = require("../plugins/twitter-link"); 10 | exports.pluginNames = ["fork-count","gh-pages-link","pull-request-links","repo-counts","repo-filter-info","settings","twitter-link"]; 11 | -------------------------------------------------------------------------------- /lib/plugins/repo-counts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var jQuery = require('jquery'); 3 | 4 | exports.name = 'Repo Counts'; 5 | exports.description = 'A user script to display repo counts when browsing Github repository pages.'; 6 | exports.enabledSelector = 'body.page-profile .tabnav-tab.selected:contains("Repositories")'; 7 | 8 | exports.onPage = function () { 9 | // Make input filter smaller when the "new repo" button exists 10 | if (jQuery('body.page-profile .filter-bar a.new-repo').length > 0) { 11 | jQuery('#your-repos-filter').css('width', '180px'); 12 | } 13 | jQuery('.page-profile ul.repo_filterer li a').each(function () { 14 | try { 15 | var elem = jQuery(this), 16 | selector = elem.data('filter'), 17 | elements = jQuery('ul.js-repo-list').find('li' + selector); 18 | elem.append(' (' + elements.size() + ')'); 19 | elem.css('font-size', '11px'); 20 | } catch (e) {} 21 | }); 22 | }; 23 | -------------------------------------------------------------------------------- /lib/core/storage.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var util = require('util'); 3 | var getPath = require('object-path-get'); 4 | var setPath = require('object-path-set'); 5 | var rootKey = 'GES.by.skratchdot'; 6 | 7 | var getRoot = function () { 8 | try { 9 | var root = JSON.parse(window.localStorage.getItem(rootKey)); 10 | if (!util.isObject(root)) { 11 | root = {}; 12 | } 13 | return root; 14 | } catch (e) { 15 | return {}; 16 | } 17 | }; 18 | 19 | exports.get = function (key, defaultValue) { 20 | var root = getRoot(); 21 | return getPath(root, key, defaultValue); 22 | }; 23 | 24 | exports.set = function (key, value) { 25 | var root = getRoot(); 26 | root = setPath(root, key, value); 27 | window.localStorage.setItem(rootKey, JSON.stringify(root)); 28 | }; 29 | 30 | exports.test = function () { 31 | var key = 'localStorageTest'; 32 | var value = Date.now(); 33 | exports.set(key, value); 34 | return value === exports.get(key, null); 35 | }; 36 | -------------------------------------------------------------------------------- /lib/plugins/twitter-link.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var jQuery = require('jquery'); 3 | var utils = require('../core/utils'); 4 | 5 | exports.name = 'Twitter Link'; 6 | exports.description = 'Adds a link to Twitter on a user\'s profile page.'; 7 | exports.enabledSelector = 'body.page-profile'; 8 | 9 | exports.onPage = function () { 10 | var username, $link, twitterSection; 11 | if (jQuery('#skratchdot-twitter-section').length === 0) { 12 | username = utils.getCurrentAuthor(); 13 | $link = jQuery('') 14 | .attr('href', '//twitter.com/' + encodeURIComponent(username)) 15 | .text('@' + username); 16 | twitterSection = '
  • ' + 17 | '' + 18 | '' + 19 | '
  • '; 20 | jQuery('.column.vcard:first ul.vcard-details:first').append(twitterSection); 21 | jQuery('#skratchdot-twitter-link').append($link); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /lib/plugins/pull-request-links.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var jQuery = require('jquery'); 3 | var utils = require('../core/utils'); 4 | 5 | exports.name = 'Pull Request Links'; 6 | exports.description = 'A user script to "linkify" the to/from branches on Pull Request pages.'; 7 | exports.enabledSelector = '.commit-ref:not(.editor-expander)'; 8 | 9 | exports.onPage = function () { 10 | jQuery(exports.enabledSelector).css('cursor', 'pointer').click(function () { 11 | var repo = utils.getCurrentRepo(), 12 | commitInfo = jQuery(this).text().trim().split(':'); 13 | console.log(repo, commitInfo); 14 | // When pull requests are coming from the same account, we need to make sure 15 | // commitInfo[0] is the account, and commitInfo[1] is the branch name. 16 | if (commitInfo.length === 1) { 17 | commitInfo = [utils.getCurrentAuthor(), commitInfo[0]]; 18 | } 19 | if (repo.length > 0 && commitInfo.length === 2) { 20 | document.location = '/' + commitInfo[0] + '/' + repo + '/tree/' + commitInfo[1]; 21 | } 22 | }); 23 | }; 24 | -------------------------------------------------------------------------------- /lib/core/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var jQuery = require('jquery'); 3 | var url = require('url'); 4 | var storage = require('./storage'); 5 | 6 | exports.getCurrentAuthorAndRepo = function () { 7 | var parts = (url.parse(document.location.toString()).pathname || '').split('/'); 8 | return { 9 | author: parts[1] || '', 10 | repo: parts[2] || '' 11 | }; 12 | }; 13 | 14 | exports.getCurrentRepo = function () { 15 | return exports.getCurrentAuthorAndRepo().repo; 16 | }; 17 | 18 | exports.getCurrentAuthor = function () { 19 | return exports.getCurrentAuthorAndRepo().author; 20 | }; 21 | 22 | exports.isPluginEnabled = function (pluginName) { 23 | // plugins are enabled by default 24 | if (typeof storage.get('enabled.' + pluginName) !== 'boolean') { 25 | storage.set('enabled.' + pluginName, true); 26 | } 27 | return storage.get('enabled.' + pluginName, true); 28 | }; 29 | 30 | exports.enablePlugin = function (pluginName, enabled) { 31 | storage.set('enabled.' + pluginName, enabled); 32 | }; 33 | 34 | exports.togglePluginEnabled = function (pluginName) { 35 | exports.enablePlugin(pluginName, !exports.isPluginEnabled(pluginName)); 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "github-enhancement-suite", 3 | "version": "2.0.2", 4 | "description": "A collection of userscripts to add functionality when browsing github.com", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "gulp test" 8 | }, 9 | "author": "skratchdot", 10 | "license": "MIT", 11 | "bugs": { 12 | "url": "https://github.com/skratchdot/github-enhancement-suite/issues" 13 | }, 14 | "homepage": "https://github.com/skratchdot/github-enhancement-suite", 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/skratchdot/github-enhancement-suite" 18 | }, 19 | "devDependencies": { 20 | "browserify": "^11.0.1", 21 | "d3": "^3.5.6", 22 | "ejs": "^2.3.3", 23 | "gulp": "^3.9.0", 24 | "gulp-insert": "^0.5.0", 25 | "gulp-nodeunit": "0.0.5", 26 | "gulp-rename": "^1.2.2", 27 | "gulp-size": "^2.0.0", 28 | "gulp-uglify": "^1.2.0", 29 | "jquery": "^2.1.4", 30 | "jsxhint": "^0.15.1", 31 | "lodash.debounce": "^3.1.1", 32 | "lodash.throttle": "^3.0.4", 33 | "mutation-summary": "0.0.0", 34 | "object-path-get": "0.0.2", 35 | "object-path-set": "0.0.1", 36 | "react": "^0.13.3", 37 | "reactify": "^1.1.1", 38 | "through2": "^2.0.0", 39 | "vinyl-transform": "^1.0.0" 40 | }, 41 | "keywords": [ 42 | "github", 43 | "extension", 44 | "browser", 45 | "enhancement" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var storage = require('./core/storage'); 3 | var $ = require('jquery'); 4 | var plugins = require('./core/plugins'); 5 | var utils = require('./core/utils'); 6 | var handleOnPage; 7 | 8 | // make sure storage works 9 | if (!storage.test()) { 10 | console.warn('Github Enhancement Suite cannot use localStorage' + 11 | 'and may not work properly.'); 12 | } 13 | 14 | // loop through each plugin calling onPage() if it's enabled and the enabledSelector matches 15 | handleOnPage = function () { 16 | plugins.pluginNames.forEach(function (plugin) { 17 | if (utils.isPluginEnabled(plugin) && 18 | $(plugins[plugin].enabledSelector).length && 19 | typeof plugins[plugin].onPage === 'function') { 20 | console.log('Firing onPage() for plugin: ' + plugin + ' at ' + Date.now()); 21 | plugins[plugin].onPage(); 22 | } 23 | }); 24 | }; 25 | 26 | // handle regular page loads 27 | handleOnPage(); 28 | 29 | // handle pjax pages 30 | (function () { 31 | var pjaxActive = false; 32 | var observer = new MutationObserver(function (mutations) { 33 | if ($('.pjax-active').length) { 34 | pjaxActive = true; 35 | } else if (pjaxActive) { 36 | pjaxActive = false; 37 | // do something 38 | setImmediate(function () { 39 | handleOnPage(); 40 | }); 41 | } 42 | }); 43 | observer.observe(document, { 44 | attributes: true, 45 | childList: true, 46 | characterData: true, 47 | characterDataOldValue: true, 48 | subtree: true 49 | }); 50 | }()); 51 | -------------------------------------------------------------------------------- /lib/plugins/gh-pages-link.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var jQuery = require('jquery'); 3 | var utils = require('../core/utils'); 4 | 5 | exports.name = 'Github Pages Link'; 6 | exports.description = 'If a repository has a gh-pages branch, then this will add links to the Github Page, as well as the gh-page source code.'; 7 | exports.enabledSelector = '.repo-container .repository-meta.js-details-container'; 8 | 9 | exports.onPage = function () { 10 | var data = utils.getCurrentAuthorAndRepo(), 11 | ghPageLink, ghPageSourceLink; 12 | if (data.author !== '' && data.repo !== '') { 13 | if (jQuery('[data-tab-filter="branches"] [data-name="gh-pages"]').length > 0) { 14 | ghPageLink = 'http://' + data.author + '.github.io/' + data.repo; 15 | ghPageSourceLink = 'https://github.com/' + data.author + '/' + 16 | data.repo + '/tree/gh-pages'; 17 | if (jQuery('#skratchdot-gh-pages-container').length === 0) { 18 | jQuery(exports.enabledSelector).append('
    ' + 19 | 'gh-pages:' + 20 | '' + 21 | ' • ' + 22 | '[gh-pages source]' + 23 | '
    '); 24 | // Fix html 25 | jQuery('#skratchdot-gh-pages-link').attr('href', ghPageLink).text(ghPageLink); 26 | jQuery('#skratchdot-gh-pages-link-source').attr('href', ghPageSourceLink); 27 | } 28 | } else { 29 | 30 | } 31 | 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "repos/github-code-search.user.js"] 2 | path = repos/github-code-search.user.js 3 | url = git@github.com:skratchdot/github-code-search.user.js.git 4 | [submodule "repos/github-editor-theme.user.js"] 5 | path = repos/github-editor-theme.user.js 6 | url = git@github.com:skratchdot/github-editor-theme.user.js.git 7 | [submodule "repos/github-fork-count.user.js"] 8 | path = repos/github-fork-count.user.js 9 | url = git@github.com:skratchdot/github-fork-count.user.js.git 10 | [submodule "repos/github-get-missing-descriptions.user.js"] 11 | path = repos/github-get-missing-descriptions.user.js 12 | url = git@github.com:skratchdot/github-get-missing-descriptions.user.js.git 13 | [submodule "repos/github-gh-pages-link.user.js"] 14 | path = repos/github-gh-pages-link.user.js 15 | url = git@github.com:skratchdot/github-gh-pages-link.user.js.git 16 | [submodule "repos/github-pull-request-links.user.js"] 17 | path = repos/github-pull-request-links.user.js 18 | url = git@github.com:skratchdot/github-pull-request-links.user.js.git 19 | [submodule "repos/github-repo-counts.user.js"] 20 | path = repos/github-repo-counts.user.js 21 | url = git@github.com:skratchdot/github-repo-counts.user.js.git 22 | [submodule "repos/github-repo-filter-info.user.js"] 23 | path = repos/github-repo-filter-info.user.js 24 | url = git@github.com:skratchdot/github-repo-filter-info.user.js.git 25 | [submodule "repos/github-twitter-link.user.js"] 26 | path = repos/github-twitter-link.user.js 27 | url = git@github.com:skratchdot/github-twitter-link.user.js.git 28 | [submodule "scripts"] 29 | path = scripts 30 | url = git@gist.github.com:5604120.git 31 | -------------------------------------------------------------------------------- /lib/plugins/fork-count.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var jQuery = require('jquery'); 3 | 4 | exports.name = 'Fork Count'; 5 | exports.description = 'Display repo counts (public, private, sources, forks, mirrors) on the profile page underneath a users followers/starred/following count'; 6 | exports.enabledSelector = 'body.page-profile .tabnav-tab.selected'; 7 | 8 | exports.onPage = function () { 9 | // Initial our variables (and jQuery selectors) 10 | var countRepos = 0, 11 | countPublic = 0, 12 | countPrivate = 0, 13 | countSources = 0, 14 | countForks = 0, 15 | countMirrors = 0, 16 | repoList = jQuery('ul.js-repo-list > li'), 17 | statsContainer = jQuery('.column.vcard:first .vcard-stats'), 18 | stats; 19 | 20 | // insert our container 21 | if (jQuery('#skratchdot-fork-count').length === 0) { 22 | statsContainer.append('
    '); 23 | statsContainer.append('
    repo counts visible on tab repositories
    '); 24 | } 25 | stats = jQuery('#skratchdot-fork-count'); 26 | if (!stats.hasClass('stats-populated') && repoList.length > 0) { 27 | // Loop through all repos, looking for public forks 28 | repoList.each(function () { 29 | try { 30 | var elem = jQuery(this); 31 | countRepos = countRepos + 1; 32 | if (elem.hasClass('public')) { 33 | countPublic = countPublic + 1; 34 | } 35 | if (elem.hasClass('private')) { 36 | countPrivate = countPrivate + 1; 37 | } 38 | if (elem.hasClass('source')) { 39 | countSources = countSources + 1; 40 | } 41 | if (elem.hasClass('fork')) { 42 | countForks = countForks + 1; 43 | } 44 | if (elem.hasClass('mirror')) { 45 | countMirrors = countMirrors + 1; 46 | } 47 | } catch (e) {} 48 | }); 49 | stats.html('' + countPublic + ' public, ' + 50 | countPrivate + ' private, ' + 51 | countSources + ' sources, ' + 52 | countForks + ' forks' + 53 | (countMirrors > 0 ? '' + countMirrors + ' mirrors' : '') 54 | ); 55 | stats.addClass('stats-populated'); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Github Enhancement Suite 2 | ======================== 3 | 4 | ### Description ### 5 | 6 | The [Github Enhancement Suite](https://github.com/skratchdot/github-enhancement-suite) 7 | is a collection of userscripts to add functionality 8 | when browsing [github.com](https://github.com/). 9 | 10 | 11 | ### Installation ### 12 | 13 | 1. Make sure you have user scripts enabled in your browser (these instructions refer to the latest versions of the browsers): 14 | * ***CHROME***: Install [TamperMonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo). Continue to STEP 2. 15 | * ***FIREFOX***: Install [GreaseMonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/). Continue to STEP 2. 16 | * ***IE***: Install [Trixie](http://www.bhelpuri.net/Trixie/). Continue to STEP 2. 17 | * ***OPERA***: Follow instructions located on Opera's site: [User JS](http://www.opera.com/docs/userjs/). Continue to STEP 2. 18 | * ***SAFARI***: Install [NinjaKit](http://d.hatena.ne.jp/os0x/20100612/1276330696). Continue to STEP 2. 19 | 2. Install the "GitHub Enhancement Suite" user script by clicking here: [github-enhancement-suite](https://github.com/skratchdot/github-enhancement-suite/raw/master/build/github-enhancement-suite.user.js). 20 | 21 | 22 | ### Included Userscripts ### 23 | 24 | - [Github: Code Search](https://github.com/skratchdot/github-code-search.user.js) 25 | - [Github: Editor Theme](https://github.com/skratchdot/github-editor-theme.user.js) 26 | - [Github: Fork Count](https://github.com/skratchdot/github-fork-count.user.js) 27 | - [Github: Get Missing Descriptions](https://github.com/skratchdot/github-get-missing-descriptions.user.js) 28 | - [Github: gh-pages Link](https://github.com/skratchdot/github-gh-pages-link.user.js) 29 | - [Github: Pull Request Links](https://github.com/skratchdot/github-pull-request-links.user.js) 30 | - [Github: Repo Counts](https://github.com/skratchdot/github-repo-counts.user.js) 31 | - [Github: Repo Filter Info](https://github.com/skratchdot/github-repo-filter-info.user.js) 32 | - [Github: Twitter Link](https://github.com/skratchdot/github-twitter-link.user.js) 33 | 34 | 35 | ### Development Info ### 36 | 37 | - Cloning the repo: 38 | 39 | git@github.com:skratchdot/github-enhancement-suite.git 40 | 41 | - Updating submodules: 42 | 43 | git submodule foreach git pull 44 | 45 | 46 | ## See Also 47 | 48 | - [Github Discussion](https://github.com/isaacs/github/issues/128) 49 | - [Reddit Enhancement Suite](https://github.com/honestbleeps/Reddit-Enhancement-Suite) 50 | - [Octotree](https://github.com/buunguyen/octotree) 51 | - [Refined Github](https://github.com/sindresorhus/refined-github) 52 | - https://github.com/jayson/github-enhancement-suite 53 | - https://github.com/EvanDotPro/github-enhancement-suite 54 | - https://github.com/mduan/Github-Enhancement-Suite 55 | - https://github.com/rsbrown/ghes 56 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var gulp = require('gulp'); 3 | var ejs = require('ejs'); 4 | var exec = require('child_process').exec; 5 | var fs = require('fs'); 6 | var insert = require('gulp-insert'); 7 | var nodeunit = require('gulp-nodeunit'); 8 | var size = require('gulp-size'); 9 | var uglify = require('gulp-uglify'); 10 | var reactify = require('reactify'); 11 | var rename = require('gulp-rename'); 12 | var through2 = require('through2'); 13 | var browserify = require('browserify'); 14 | 15 | var getPackage = function () { 16 | return JSON.parse(fs.readFileSync('./package.json', 'utf-8')); 17 | }; 18 | 19 | var getHeader = function () { 20 | return ejs.render(fs.readFileSync('./userscript.header.ejs', 'utf-8'), getPackage()); 21 | }; 22 | 23 | var displaySize = function (title) { 24 | return size({ 25 | title: title || '', 26 | showFiles: true 27 | }); 28 | }; 29 | 30 | gulp.task('plugins', function () { 31 | var plugins = []; 32 | var contents = [ 33 | '/* autogenerated by running `gulp plugins` (see gulpfile.js) */', 34 | '"use strict";' 35 | ]; 36 | fs.readdirSync('./lib/plugins').forEach(function (file) { 37 | var pluginKey; 38 | file = file.replace('.js', ''); 39 | plugins.push(file); 40 | pluginKey = file; 41 | if (pluginKey.indexOf('-') >= 0) { 42 | pluginKey = '["' + pluginKey + '"]'; 43 | } else { 44 | pluginKey = '.' + pluginKey; 45 | } 46 | contents.push('exports' + pluginKey + ' = require("../plugins/' + file + '");'); 47 | }); 48 | contents.push('exports.pluginNames = ' + JSON.stringify(plugins) + ';\n'); 49 | fs.writeFileSync('./lib/core/plugins.js', contents.join('\n'), 'utf-8'); 50 | }); 51 | 52 | gulp.task('userscript', function () { 53 | var header = getHeader() + '\n\n'; 54 | return gulp.src('./lib/index.js') 55 | .pipe(through2.obj(function (file, enc, next) { 56 | browserify(file.path) 57 | .transform('reactify') 58 | .bundle(function (err, res) { 59 | // assumes file.contents is a Buffer 60 | file.contents = res; 61 | next(null, file); 62 | }); 63 | })) 64 | .pipe(rename('github-enhancement-suite.debug.user.js')) 65 | .pipe(insert.prepend(header)) 66 | .pipe(gulp.dest('./build/')) 67 | .pipe(displaySize('Userscript (Debug)')) 68 | .pipe(uglify({output:{max_line_len: 80}})) 69 | .pipe(rename('github-enhancement-suite.user.js')) 70 | .pipe(insert.prepend(header)) 71 | .pipe(gulp.dest('./build/')) 72 | .pipe(displaySize('Userscript (Minified)')); 73 | }); 74 | 75 | gulp.task('lint', function () { 76 | exec([ 77 | 'node', 78 | './node_modules/jsxhint/cli.js', 79 | //'--show-non-errors', 80 | '--exclude-path', 81 | './.jshintignore', 82 | '--config', 83 | './.jshint', 84 | './lib/**/*.js', 85 | './test', 86 | './gulpfile.js' 87 | ].join(' '), 88 | function (err, stdout, stderr) { 89 | if (stdout) { 90 | console.log(stdout); 91 | } 92 | }); 93 | }); 94 | 95 | gulp.task('test', function () { 96 | gulp.src('test.js') 97 | .pipe(nodeunit()); 98 | }); 99 | 100 | gulp.task('watch', function () { 101 | gulp.watch(['./lib/**/*.js', './test/**/*.js'], ['lint', 'plugins', 'userscript', 'test']); 102 | }); 103 | 104 | // setup default task 105 | gulp.task('default', ['lint', 'plugins', 'userscript', 'test', 'watch']); 106 | 107 | // handle errors 108 | process.on('uncaughtException', function (e) { 109 | console.error(e); 110 | }); 111 | -------------------------------------------------------------------------------- /lib/plugins/settings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var React = require('react'); 3 | var jQuery = require('jquery'); 4 | var plugins = require('../core/plugins'); 5 | var utils = require('../core/utils'); 6 | var packageInfo = require('../../package.json'); 7 | // config 8 | var containerId = 'github-enhancement-suite-settings'; 9 | var appId = containerId + '-app'; 10 | 11 | exports.name = 'Settings'; 12 | exports.description = 'A plugin that controls all the Github Enhancement Suite plugin settings and whether or not a plugin is enabled.'; 13 | exports.enabledSelector = 'a.js-selected-navigation-item.selected[href="/settings/profile"]'; 14 | 15 | exports.onPage = function () { 16 | var App, PluginDisplay, injectContainer; 17 | 18 | injectContainer = function () { 19 | if (jQuery('#' + containerId).length === 0) { 20 | jQuery('.column.three-fourths').append('' + 21 | '
    ' + 22 | '

    Github Enhancement Suite

    ' + 23 | '
    ' + 24 | '
    ' 25 | ); 26 | } 27 | }; 28 | 29 | App = React.createClass({ 30 | getDefaultProps: function () { 31 | return { 32 | pluginNames: plugins.pluginNames.filter(function (plugin) { 33 | return plugin !== 'settings'; 34 | }) 35 | }; 36 | }, 37 | getInitialState: function () { 38 | return { 39 | lastAction: Date.now() 40 | }; 41 | }, 42 | onEnableButton: function (pluginName) { 43 | utils.togglePluginEnabled(pluginName); 44 | this.setState({lastAction: Date.now()}); 45 | }, 46 | render: function () { 47 | var $this = this; 48 | return ( 49 |
    50 |

    51 | Settings 52 |   53 | 54 | Github Enhancement Suite (version {packageInfo.version}) 55 | 56 |

    57 |
    58 | {this.props.pluginNames.map(function (pluginName) { 59 | var plugin = plugins[pluginName]; 60 | return ( 61 |
    62 | 69 |
    70 |
    71 | ); 72 | })} 73 |
    74 | ); 75 | } 76 | }); 77 | 78 | PluginDisplay = React.createClass({ 79 | getDefaultProps: function () { 80 | return { 81 | name: '', 82 | description: '', 83 | enabled: false, 84 | onEnableButton: function () {} 85 | }; 86 | }, 87 | render: function () { 88 | return ( 89 |
    90 |
    91 |
    92 |

    {this.props.name}

    93 |
    94 |
    101 | {this.props.description} 102 |
    103 |
    104 |
    105 | 108 |
    109 |
    110 |
     
    111 |
    112 | ); 113 | } 114 | }); 115 | 116 | // setup page 117 | injectContainer(); 118 | React.render(, document.getElementById(appId)); 119 | }; 120 | -------------------------------------------------------------------------------- /plugins-invalid/editor-theme.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var jQuery = require('jquery'); 3 | 4 | exports.enabledSelector = '.file.js-code-editor .file-actions'; 5 | 6 | exports.onPage = function () { 7 | // config variables 8 | var $actions, 9 | brightThemes = { 10 | "ace/theme/chrome" : "Chrome", 11 | "ace/theme/clouds" : "Clouds", 12 | "ace/theme/crimson_editor" : "Crimson Editor", 13 | "ace/theme/dawn" : "Dawn", 14 | "ace/theme/dreamweaver" : "Dreamweaver", 15 | "ace/theme/eclipse" : "Eclipse", 16 | "ace/theme/github" : "GitHub", 17 | "ace/theme/solarized_light" : "Solarized Light", 18 | "ace/theme/textmate" : "TextMate", 19 | "ace/theme/tomorrow" : "Tomorrow", 20 | "ace/theme/xcode" : "XCode" 21 | }, 22 | darkThemes = { 23 | "ace/theme/ambiance" : "Ambiance", 24 | "ace/theme/clouds_midnight" : "Clouds Midnight", 25 | "ace/theme/cobalt" : "Cobalt", 26 | "ace/theme/idle_fingers" : "idleFingers", 27 | "ace/theme/kr_theme" : "krTheme", 28 | "ace/theme/merbivore" : "Merbivore", 29 | "ace/theme/merbivore_soft" : "Merbivore Soft", 30 | "ace/theme/mono_industrial" : "Mono Industrial", 31 | "ace/theme/monokai" : "Monokai", 32 | "ace/theme/pastel_on_dark" : "Pastel on dark", 33 | "ace/theme/solarized_dark" : "Solarized Dark", 34 | "ace/theme/twilight" : "Twilight", 35 | "ace/theme/tomorrow_night" : "Tomorrow Night", 36 | "ace/theme/tomorrow_night_blue" : "Tomorrow Night Blue", 37 | "ace/theme/tomorrow_night_bright" : "Tomorrow Night Bright", 38 | "ace/theme/tomorrow_night_eighties" : "Tomorrow Night 80s", 39 | "ace/theme/vibrant_ink" : "Vibrant Ink" 40 | }, 41 | defaultTheme = 'ace/theme/twilight', 42 | localStorageKey = 'SKRATCHDOT_EDITOR_THEME', 43 | selectId = 'skratchdot-editor-theme', 44 | setTimeoutCount = 0, 45 | setTimeoutCountMax = 50, 46 | setTimeoutDelay = 100, 47 | // functions 48 | createSelect, 49 | createSelectHelper, 50 | initEditorTheme, 51 | getEditorTheme, 52 | setEditorTheme, 53 | init; 54 | 55 | createSelect = function (selectedTheme) { 56 | var select = ''; 60 | return select; 61 | }; 62 | 63 | createSelectHelper = function (selectedTheme, themes, label) { 64 | var theme, str = '\n'; 65 | for (theme in themes) { 66 | if (themes.hasOwnProperty(theme)) { 67 | str += '\n'; 73 | } 74 | } 75 | str += '\n'; 76 | return str; 77 | }; 78 | 79 | getEditorTheme = function () { 80 | var theme; 81 | if (window.localStorage) { 82 | theme = window.localStorage.getItem(localStorageKey); 83 | } 84 | if (typeof theme !== 'string' || 85 | 'undefined' === typeof ace || 86 | !ace.config.modules.hasOwnProperty(theme)) { 87 | theme = defaultTheme; 88 | setEditorTheme(theme); 89 | } 90 | return theme; 91 | }; 92 | 93 | setEditorTheme = function (theme) { 94 | if (window.localStorage) { 95 | window.localStorage.setItem(localStorageKey, theme); 96 | } 97 | if ('undefined' !== typeof CodeEditor) { 98 | CodeEditor.ace.setTheme(theme); 99 | } 100 | }; 101 | 102 | initEditorTheme = function () { 103 | var theme, $select; 104 | setTimeoutCount += 1; 105 | if (jQuery('.ace_editor').length === 1) { 106 | theme = getEditorTheme(); 107 | $actions.prepend(createSelect(theme)); 108 | $select = jQuery('#' + selectId); 109 | $select.change(function () { 110 | setEditorTheme(jQuery(this).val()); 111 | }); 112 | $select.change(); 113 | setTimeoutCount = setTimeoutCountMax; 114 | } 115 | if (setTimeoutCount < setTimeoutCountMax) { 116 | setTimeout(initEditorTheme, setTimeoutDelay); 117 | } 118 | }; 119 | 120 | init = function () { 121 | $actions = jQuery('.file.js-code-editor .file-actions'); 122 | 123 | // only do something when we are editing blobs 124 | if ($actions.length) { 125 | try { 126 | setTimeout(initEditorTheme, setTimeoutDelay); 127 | } catch (e) {} 128 | 129 | } 130 | }; 131 | 132 | jQuery(document).ready(init); 133 | }; 134 | -------------------------------------------------------------------------------- /lib/extras/ay-pie-chart.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Pie Chart v0.1.0 3 | * https://github.com/gajus/pie-chart 4 | * 5 | * Licensed under the BSD. 6 | * https://github.com/gajus/pie-chart/blob/master/LICENSE 7 | * 8 | * Author: Gajus Kuizinas 9 | */ 10 | /** 11 | * Slightly modified for use in node/browserify by @skratchdot 12 | */ 13 | var d3 = require('d3'); 14 | var ay = {}; 15 | 16 | ay.pie_chart = function (name, data, options) { 17 | 'use strict'; 18 | var svg = d3.select('svg.' + name), 19 | chart_size = svg[0][0].clientWidth || svg[0][0].parentNode.clientWidth, 20 | settings = { 21 | radius_inner: 0, 22 | radius_outer: chart_size / 3, 23 | radius_label: chart_size / 3 + 20, 24 | percentage: true, 25 | value: false, 26 | label_margin: 10, 27 | group_data: 0 28 | }, 29 | donut, 30 | arc, 31 | slices, 32 | labels_group, 33 | grouped_labels = {left: [], right: []}, 34 | labels, 35 | label_boxes, 36 | label_texts, 37 | parameter, 38 | reposition_colliding_labels = function (group) { 39 | group 40 | .sort(function (a, b) { 41 | return (a.y + a.height) - (b.y + b.height); 42 | }) 43 | .forEach(function (e, i) { 44 | if (group[i + 1]) { 45 | if (group[i + 1].y - (e.y + e.height) < settings.label_margin) { 46 | group[i + 1].y = (e.y + e.height) + settings.label_margin; 47 | } 48 | } 49 | if (e.x < settings.label_margin) { 50 | e.x = settings.label_margin; 51 | } else if (e.x + e.width > chart_size - settings.label_margin) { 52 | e.x = chart_size - e.width - settings.label_margin; 53 | } 54 | d3.select(labels[0][e.index]) 55 | .attr('transform', 'translate(' + e.x + ', ' + e.y + ')'); 56 | d3.select(label_boxes[0][e.index]) 57 | .attr('x', 0) 58 | .attr('y', -e.height + 2) 59 | .attr('width', e.width + 4) 60 | .attr('height', e.height + 4); 61 | e.textNode 62 | .attr('x', 2) 63 | .attr('y', 2); 64 | }); 65 | }, 66 | group_data = function (data) { 67 | var data_size = 0, 68 | removed_data_size = 0, 69 | i; 70 | data.forEach(function (e) { 71 | data_size += e.value; 72 | }); 73 | // Check if it is worth grouping the data. 74 | for (i = data.length-1; i >= 0; i--) { 75 | if ((data[i].value / data_size) * 100 < settings.group_data) { 76 | removed_data_size++; 77 | } 78 | } 79 | if(removed_data_size > 1) { 80 | removed_data_size = 0; 81 | for (i = data.length-1; i >= 0; i--) { 82 | if ((data[i].value / data_size) * 100 < settings.group_data) { 83 | removed_data_size += data.splice(i, 1)[0].value; 84 | } 85 | } 86 | } 87 | data.push({index: 0, name: 'Other', value: removed_data_size}); 88 | return data; 89 | }; 90 | if (data.map(function (d) { return d.index; }).indexOf(0) !== -1) { 91 | throw '0 index is reserved for grouped data.'; 92 | } 93 | if (options !== undefined) { 94 | for (parameter in options) { 95 | if (options.hasOwnProperty(parameter) && settings[parameter] !== undefined) { 96 | settings[parameter] = options[parameter]; 97 | } 98 | } 99 | } 100 | if (settings.group_data) { 101 | data = group_data(data); 102 | } 103 | donut = svg 104 | .append('g') 105 | .attr('class', 'donut') 106 | .attr('transform', 'translate(' + (chart_size / 2) + ', ' + (chart_size / 2) + ')'); 107 | arc = d3.svg.arc() 108 | .innerRadius(settings.radius_inner) 109 | .outerRadius(settings.radius_outer); 110 | data = d3.layout.pie() 111 | .value(function (e) { 112 | return e.value; 113 | }) 114 | .sort(function (a, b) { 115 | return b.index - a.index; 116 | })(data); 117 | slices = donut 118 | .selectAll('path') 119 | .data(data) 120 | .enter() 121 | .append('path') 122 | .attr('class', function (d) { 123 | return 'g-' + d.data.index; 124 | }) 125 | .attr('d', arc) 126 | .on('mouseover', function (d, i) { 127 | d3.select(labels[0][i]) 128 | .classed('active', true); 129 | }) 130 | .on('mouseout', function (d, i) { 131 | d3.select(labels[0][i]) 132 | .classed('active', false); 133 | }); 134 | 135 | labels_group = svg 136 | .append('g') 137 | .attr('class', 'labels'); 138 | labels = labels_group 139 | .selectAll('g.label') 140 | .data(data) 141 | .enter() 142 | .append('g') 143 | .filter(function (e) { 144 | if (settings.percentage) { 145 | return true; 146 | } 147 | return e.data.name !== undefined; 148 | }) 149 | .attr('class', 'label'); 150 | label_boxes = labels 151 | .append('rect'); 152 | label_texts = labels 153 | .append('text').text(function (e) { 154 | var percentage = (((e.endAngle - e.startAngle) / (2 * Math.PI)) * 100).toFixed(2), 155 | label = []; 156 | if (e.data.name !== undefined) { 157 | label.push(e.data.name); 158 | } 159 | if (settings.value) { 160 | label.push(' - ' + e.data.value); 161 | } 162 | if (settings.percentage) { 163 | label.push(' (' +percentage + '%)'); 164 | } 165 | 166 | return label.join(' '); 167 | }) 168 | .each(function (d, i) { 169 | var center = arc.centroid(d), 170 | x = center[0], 171 | y = center[1], 172 | h = Math.sqrt(x * x + y * y), 173 | lx = x / h * settings.radius_label + chart_size / 2, 174 | ly = y / h * settings.radius_label + chart_size / 2, 175 | left_aligned = (d.endAngle - d.startAngle) * 0.5 + d.startAngle > Math.PI, 176 | text = d3.select(this), 177 | bb = this.getBBox(); 178 | grouped_labels[left_aligned ? 'left' : 'right'].push({ 179 | index: i, 180 | width: bb.width, 181 | height: bb.height, 182 | x: left_aligned ? lx - bb.width : lx, 183 | y: ly, 184 | textNode: text 185 | }); 186 | }); 187 | reposition_colliding_labels(grouped_labels.left); 188 | reposition_colliding_labels(grouped_labels.right); 189 | }; 190 | exports.ay = ay; -------------------------------------------------------------------------------- /plugins-invalid/code-search.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var jQuery = require('jquery'); 3 | 4 | exports.enabledSelector = ''; 5 | 6 | exports.onPage = function () {}; 7 | 8 | var implementMe = function () { 9 | 10 | // Declare a namespace to store functions in 11 | var SKRATCHDOT = window.SKRATCHDOT || {}; 12 | 13 | // GitHub.nameWithOwner used to exist on the page, but was removed. 14 | // Now constructing this with some jQuery selectors 15 | SKRATCHDOT.nameWithOwner = ''; 16 | SKRATCHDOT.getNameWithOwner = function () { 17 | var returnValue = '', 18 | repoLink = jQuery('a.js-current-repository'); 19 | if (repoLink.length > 0) { 20 | returnValue = repoLink.attr('href').substr(1); 21 | } 22 | return returnValue; 23 | }; 24 | 25 | // The function that will be called when repo search is used 26 | SKRATCHDOT.performCodeSearch = function (searchText, startValue) { 27 | jQuery.ajax({ 28 | url: '/search', 29 | dataType: 'html', 30 | type: 'GET', 31 | data: { 32 | type: 'Code', 33 | q: searchText + ' repo:' + SKRATCHDOT.nameWithOwner.toLowerCase(), 34 | p: startValue 35 | }, 36 | success: function (data) { 37 | try { 38 | var noResultsSelector = '.blankslate', 39 | resultHtml = jQuery(data).find('#code_search_results, ' + noResultsSelector), 40 | resultContainer = jQuery('#skratchdot-result-container'), 41 | reQuotes = new RegExp('([^\\\\])(\\")', 'g'), 42 | newSearchText, 43 | newSearchLink; 44 | 45 | resultContainer.html(resultHtml); 46 | 47 | resultContainer.find('#code_search_results div.pagination a').click(function (e) { 48 | var pageNumber, url, regex, results; 49 | 50 | pageNumber = 1; 51 | url = jQuery(this).attr('href'); 52 | regex = new RegExp('(.)*(search\\?p\\=)([0-9]+)', 'gi'); 53 | results = regex.exec(url); 54 | if (results.length >= 4) { 55 | pageNumber = parseInt(results[3], 10); 56 | } 57 | 58 | resultContainer.empty(); 59 | 60 | // Refresh with the results from the pagination 61 | SKRATCHDOT.performCodeSearch(searchText, pageNumber); 62 | 63 | e.preventDefault(); 64 | }); 65 | 66 | // search for unescaped quotes. if we find some, create a link 67 | // that will re-try the search with escaped quotes 68 | if (resultHtml.is(noResultsSelector) && searchText.match(reQuotes)) { 69 | newSearchText = searchText.replace(reQuotes, function ($1, $2) { 70 | return $2 + '\\"'; 71 | }); 72 | newSearchLink = jQuery('searching with escaped quotes'); 73 | newSearchLink.click(function (e) { 74 | e.preventDefault(); 75 | jQuery('#skratchdot-code-search').find('input:first').val(newSearchText); 76 | SKRATCHDOT.performCodeSearch(newSearchText, 1); 77 | }); 78 | jQuery(noResultsSelector).append('

    Your search contained unescaped quotes. You can also try:

    '); 79 | jQuery(noResultsSelector).append(newSearchLink); 80 | } 81 | } catch (e) {} 82 | } 83 | }); 84 | }; 85 | 86 | SKRATCHDOT.codeSearchInit = function () { 87 | var siteContainer = jQuery('div.site div.container'), 88 | repohead = siteContainer.find('div.tabnav'), 89 | jsRepoPjaxContainer = jQuery('#js-repo-pjax-container'), 90 | codeTabSelected, 91 | tabsOnRight; 92 | SKRATCHDOT.nameWithOwner = SKRATCHDOT.getNameWithOwner(); 93 | if (repohead.length > 0 && typeof SKRATCHDOT.nameWithOwner === 'string' && SKRATCHDOT.nameWithOwner.length > 0) { 94 | // Do nothing if code tab isn't selected 95 | codeTabSelected = siteContainer.find('ul.tabs li:eq(1) a.selected'); 96 | if (codeTabSelected.length === 0) { 97 | return; 98 | } 99 | 100 | tabsOnRight = repohead.find('.tabnav-right ul'); 101 | 102 | // Do nothing if there's already a search box 103 | if (repohead.find('input[type=text]').length > 1) { 104 | return; 105 | } 106 | 107 | // Create Search Bar 108 | tabsOnRight.prepend( 109 | jQuery('
  • ') 110 | .attr('class', 'search') 111 | .append( 112 | jQuery('
    ') 113 | .attr('id', 'skratchdot-code-search') 114 | .attr('method', 'get') 115 | .attr('action', 'search') 116 | .append( 117 | jQuery('') 118 | .attr('class', 'fieldwrap') 119 | .append( 120 | jQuery('') 121 | .attr('type', 'text') 122 | .attr('placeholder', 'Search Source Code...') 123 | .attr('style', 'border:1px solid #ccc;border-radius:3px;color:#666;min-height:26px;height:26px;padding:0 5px;') 124 | ) 125 | .append( 126 | jQuery('