├── .gitignore ├── .gitmodules ├── Makefile ├── README.mkd ├── github-stars.crx ├── github-stars.js └── src ├── bg.js ├── github-stars.js └── manifest.json /.gitignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | *.pem 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "gm2chrome"] 2 | path = gm2chrome 3 | url = https://github.com/bcho/gm2chrome.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean convert 2 | 3 | CHROME_RUNTIME=`ls /usr/bin | grep 'chrom' | head -1` 4 | GM_CONVERTER=$(realpath gm2chrome/converter.py) 5 | PYTHON_RUNTIME=`which python3` 6 | 7 | EXT_NAME=github-stars 8 | 9 | EXT_SRC=$(realpath ./src) 10 | EXT_SOURCE=${EXT_SRC}/${EXT_NAME}.js 11 | EXT_PRE_MANI=${EXT_SRC}/manifest.json 12 | 13 | EXT_TMP=`realpath ./tmp` 14 | 15 | EXT_CRX_BUILD=$(realpath .) 16 | EXT_CRX_BUILD_PEM=${EXT_CRX_BUILD}/${EXT_NAME}.pem 17 | EXT_CRX_BUILD_CRX=${EXT_CRX_BUILD}/${EXT_NAME}.crx 18 | 19 | EXT_GM_BUILD=$(realpath .) 20 | EXT_GM_BUILD_SOURCE=${EXT_GM_BUILD}/${EXT_NAME}.js 21 | 22 | 23 | all: build_crx build_gm clean_tmp 24 | 25 | 26 | build_crx: convert_crx pack_crx 27 | 28 | convert_crx: 29 | ${PYTHON_RUNTIME} ${GM_CONVERTER} ${EXT_SOURCE} ${EXT_PRE_MANI} ${EXT_TMP} 30 | cp ${EXT_SRC}/*.js ${EXT_TMP}/ 31 | 32 | pack_crx: 33 | if [ -a ${EXT_CRX_BUILD_PEM} ]; \ 34 | then \ 35 | ${CHROME_RUNTIME} \ 36 | --pack-extension=${EXT_TMP} \ 37 | --pack-extension-key=${EXT_CRX_BUILD_PEM}; \ 38 | else \ 39 | ${CHROME_RUNTIME} --pack-extension=${EXT_TMP}; \ 40 | mv -f ${EXT_TMP}.pem ${EXT_CRX_BUILD_PEM}; \ 41 | fi; 42 | mv -f ${EXT_TMP}.crx ${EXT_CRX_BUILD_CRX} 43 | 44 | 45 | build_gm: 46 | cp ${EXT_SOURCE} ${EXT_GM_BUILD_SOURCE} 47 | 48 | 49 | clean_tmp: 50 | rm -rf ${EXT_TMP} 51 | 52 | clean: clean_tmp 53 | -------------------------------------------------------------------------------- /README.mkd: -------------------------------------------------------------------------------- 1 | # GitHub Stars 2 | 3 | Manage your github starred projects like a boss. 4 | 5 | 6 | ## Installation 7 | 8 | This Grease Monkey script can be used in both Firefox and Chrome / Chromium. 9 | 10 | ### Firefox Users 11 | 12 | You can enable this [script](github-stars.js) via [greasemonkey](https://addons.mozilla.org/en-US/firefox/addon/greasemonkey/) addons. 13 | 14 | ### Chrome / Chromium Users 15 | 16 | You can enable this script via: 17 | 18 | - [Tampermonkey](https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=en), 19 | - or install [it](github-stars.crx) manually. 20 | 21 | 22 | ## Usage 23 | 24 | ### Repository 25 | 26 | When you star a repository you can add extra comments for this repository, which may help you recall it after some days. 27 | 28 | 29 | Also, you can update your comments in repository's page. 30 | 31 | 32 | ### Search by comments 33 | 34 | In [stars page](https://github.com/stars)'s search box, you can filter your starred repositories by some words. 35 | 36 | 37 | ## TODO 38 | 39 | * demo 40 | * clear TODO tag in the code 41 | * comments persistent (using localStorage currently) 42 | 43 | 44 | ## Contributing 45 | 46 | Contribution is welcome, feel free to open an issue and fork. Waiting for your pull request. <3 47 | 48 | And ping me up with [larrydarrelc@gmail.com](mailto:larrydarrelc@gmail.com). 49 | -------------------------------------------------------------------------------- /github-stars.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bcho/github-stars/34094a5ad42397088649df485619a6ebaa1e02c6/github-stars.crx -------------------------------------------------------------------------------- /github-stars.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name GitHub Stars 3 | // @namespace https://github.com/larrydarrelc/github-stars 4 | // @version 0.0.1 5 | // @description Manage your github starred projects like a boss. 6 | // 7 | // @match https://github.com/stars* 8 | // @match https://github.com/* 9 | // @require http://code.jquery.com/jquery-2.0.3.min.js 10 | // @permissions tabs 11 | // ==/UserScript== 12 | 13 | /*global $*/ 14 | 15 | (function () { 16 | 17 | // Helper Setup 18 | // ------------ 19 | 20 | // Local storage keys' prefix. 21 | var PREFIX = 'gs-'; 22 | 23 | // Script running environment (gm or crx). 24 | var MODE_GM = 0, 25 | MODE_CRX = 1, 26 | MODE = MODE_CRX; 27 | if ($.hasOwnProperty('facebox')) { 28 | MODE = MODE_GM; 29 | } 30 | 31 | // Templating settings. 32 | var TEMPLATE = { 33 | interpolate: /<%=([\w ]+)%>/g 34 | }; 35 | 36 | // Add comments box markup. 37 | var ADD_COMMENTS_BOX = '' + 38 | '

Add comments

' + 39 | '

' + 42 | '' + 43 | ''; 46 | 47 | // Edit comments button markup. 48 | var EDIT_COMMENTS_BTN = '' + 49 | '
  • ' + 50 | '
    ' + 51 | '' + 52 | 'Comments' + 53 | '' + 54 | '' + 55 | '
    ' + 56 | '
  • '; 57 | 58 | // Comments fuzzy match threshold 59 | var MATCH_THRESHOLD = 0.4; 60 | 61 | // Comments fuzzy match shortest length, used for improving performance 62 | var MATCH_LENGTH = 2; 63 | 64 | 65 | // Utilities 66 | // --------- 67 | 68 | // k-v based comments service (using localStorage) 69 | var Comments = { 70 | realName: function (name) { 71 | return PREFIX + name; 72 | }, 73 | 74 | get: function (projectName) { 75 | projectName = Comments.realName(projectName); 76 | return localStorage[projectName] || ''; 77 | }, 78 | 79 | set: function (projectName, comments) { 80 | projectName = Comments.realName(projectName); 81 | localStorage[projectName] = comments; 82 | } 83 | }; 84 | 85 | // a not so fast templating helper 86 | var tmpl = function (markup, data) { 87 | var pattern = TEMPLATE.interpolate, 88 | key; 89 | 90 | while ((key = pattern.exec(markup)) !== null) { 91 | markup = markup.replace(key[0], data[key[1].trim()]); 92 | } 93 | 94 | return markup; 95 | }; 96 | 97 | // Compare two string and get a score for similarity. 98 | // Original code from https://github.com/joshaven/string_score 99 | // TODO test cases 100 | var cmp = function (a, b) { 101 | if (a === b) return 1; 102 | if (b === "") return 0; 103 | 104 | var runningScore = 0, charScore, finalScore, 105 | aString = a.toLowerCase(), aLength = a.length, 106 | bString = b.toLowerCase(), bLength = b.length, 107 | idxOf, startAt = 0, 108 | fuzzies = 1; 109 | 110 | for (var i = 0;i < bLength;i++) { 111 | idxOf = aString.indexOf(bString[i], startAt); 112 | 113 | if (idxOf === -1) { 114 | return 0; 115 | } else if (startAt === idxOf) { 116 | charScore = 0.7; 117 | } else { 118 | charScore = 0.1; 119 | if (a[idxOf - 1] === ' ') charScore += 0.8; 120 | } 121 | 122 | if (a[idxOf] === b[i]) charScore += 0.1; 123 | 124 | runningScore += charScore; 125 | startAt = idxOf + 1; 126 | } 127 | 128 | finalScore = 0.5 * (runningScore / aLength + runningScore / bLength) / fuzzies; 129 | 130 | if (aString[0] === bString[0] && finalScore < 0.85) finalScore += 0.15; 131 | 132 | return finalScore; 133 | }; 134 | 135 | // Run code via `chrome.tabs.executeScript` 136 | var chromeRunCode = function (snippet, cb) { 137 | cb = cb || function () {}; 138 | 139 | chrome.runtime.sendMessage({ 140 | snippet: snippet 141 | }, cb); 142 | }; 143 | 144 | 145 | // Components Setup 146 | // ---------------- 147 | 148 | var addComments = function (projectName) { 149 | var originalComments = Comments.get(projectName), 150 | boxTmpl = tmpl(ADD_COMMENTS_BOX, { comments: originalComments }); 151 | 152 | var bindAddBtn = function() { 153 | var addBtn = document.querySelector('#add-comments-btn'), 154 | commentsInp = document.querySelector('#add-comments-inp'); 155 | 156 | addBtn.addEventListener('click', function () { 157 | var comments = commentsInp.value; 158 | 159 | if (comments != originalComments) { 160 | Comments.set(projectName, comments); 161 | } 162 | 163 | if (MODE === MODE_GM) { 164 | $(document).trigger('close.facebox'); 165 | } else { 166 | chromeRunCode(tmpl('$(document).trigger("close.facebox");')); 167 | } 168 | }); 169 | }; 170 | 171 | if (MODE === MODE_GM) { 172 | $.facebox(boxTmpl); 173 | bindAddBtn(); 174 | } else { 175 | chromeRunCode(tmpl("$.facebox('<%= tmpl %>')", {tmpl: boxTmpl}), bindAddBtn); 176 | } 177 | }; 178 | 179 | 180 | // Page Manipuliation 181 | // ------------------ 182 | 183 | var isStarsPage = function (path) { 184 | return path === 'stars'; 185 | }; 186 | 187 | var isProjectPage = function (path) { 188 | return path.split('/').length === 2; 189 | }; 190 | 191 | var starsPage = function () { 192 | var starred_repos = document.querySelectorAll('.repo_list li'), 193 | starred_repos_count = starred_repos.length, 194 | repos = [], curRepo, 195 | ele, name; 196 | 197 | // TODO loops function oops? 198 | var curried = function (name) { 199 | return function () { 200 | addComments(name); 201 | }; 202 | }; 203 | 204 | for (var i = 0;i < starred_repos_count;i++) { 205 | ele = starred_repos[i]; 206 | name = ele.querySelector('h3 a').innerText; 207 | curRepo = { 208 | ele: ele, 209 | name: name, 210 | comments: Comments.get(name) 211 | }; 212 | repos.push(curRepo); 213 | 214 | // bind the :star: buttons 215 | curRepo.ele 216 | .querySelector('.unstarred') 217 | .addEventListener('click', curried(curRepo.name)); 218 | 219 | // inject comments 220 | if (curRepo.comments) { 221 | curRepo.ele.appendChild($('

    ' + curRepo.comments + '

    ')[0]); 222 | } 223 | } 224 | 225 | // bind search box 226 | var q = document.querySelector('input#star-repo-search'); 227 | 228 | // TODO performance check 229 | q.addEventListener('keyup', function () { 230 | var needle = q.value; 231 | 232 | if (needle.length === 0) { 233 | repos.forEach(function (repo) { 234 | repo.ele.classList.remove('hidden'); 235 | }) 236 | } 237 | 238 | if (needle.length < MATCH_LENGTH) return; 239 | 240 | // compare project name and comments 241 | var cmpRepo = function (repo) { 242 | return ([repo.name, repo.comments].filter(function (a) { 243 | return (cmp(a, needle) >= MATCH_THRESHOLD); 244 | }).length > 0); 245 | }; 246 | 247 | repos.forEach(function (repo) { 248 | if (cmpRepo(repo)) { 249 | repo.ele.classList.remove('hidden'); 250 | } else { 251 | repo.ele.classList.add('hidden'); 252 | } 253 | }); 254 | }); 255 | }; 256 | 257 | var projectPage = function (projectName) { 258 | // inject edit comments box 259 | document 260 | .querySelector('.pagehead-actions') 261 | .appendChild($(EDIT_COMMENTS_BTN)[0]); 262 | 263 | // bind the :star: buttons 264 | var btn = document.querySelectorAll('.unstarred'), 265 | l = btn.length, 266 | e; 267 | for (var i = 0;i < l;i++) { 268 | e = btn[i]; 269 | e.addEventListener('click', function () { 270 | addComments(projectName); 271 | }) 272 | } 273 | }; 274 | 275 | 276 | // Helper Initialization 277 | // --------------------- 278 | 279 | var kick = function () { 280 | var path = location.pathname.substring(1); 281 | 282 | // faking a route 283 | if (isStarsPage(path)) { 284 | starsPage(); 285 | } else if (isProjectPage(path)) { 286 | projectPage(path); 287 | } else { 288 | console.log('reaching ', path); 289 | } 290 | }; 291 | 292 | kick(); 293 | })(); 294 | -------------------------------------------------------------------------------- /src/bg.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var _ = function (markup, data) { 3 | var pattern = /<%=([\w ]+)%>/g, 4 | key; 5 | 6 | while ((key = pattern.exec(markup)) !== null) { 7 | markup = markup.replace(key[0], data[key[1].trim()]); 8 | } 9 | 10 | return markup; 11 | }; 12 | 13 | var runCodeWrapper = function (snippet) { 14 | var tmpl = [ 15 | '(function () {', 16 | 'try {', 17 | 'var script = document.createElement("script");', 18 | 'script.type = "text/javascript";', 19 | 'script.innerHTML = "<%= snippet %>";', 20 | 'document.body.appendChild(script);', 21 | 'window.setTimeout(function () {', 22 | 'script.parentElement.removeChild(script);', 23 | '}, 10);', 24 | '} catch (e) {', 25 | 'console.error(e);', 26 | '}', 27 | '})();'].join('\n'); 28 | 29 | return _(tmpl, {snippet: snippet.split('"').join('\\"')}); 30 | }; 31 | 32 | chrome.runtime.onMessage.addListener(function (req, sender, sendResp) { 33 | console.log('Execute code in the background.'); 34 | console.debug(req.snippet); 35 | 36 | chrome.tabs.executeScript({ 37 | code: runCodeWrapper(req.snippet) 38 | }); 39 | 40 | sendResp(); 41 | }); 42 | })(); 43 | -------------------------------------------------------------------------------- /src/github-stars.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name GitHub Stars 3 | // @namespace https://github.com/larrydarrelc/github-stars 4 | // @version 0.0.1 5 | // @description Manage your github starred projects like a boss. 6 | // 7 | // @match https://github.com/stars* 8 | // @match https://github.com/* 9 | // @require http://code.jquery.com/jquery-2.0.3.min.js 10 | // @permissions tabs 11 | // ==/UserScript== 12 | 13 | /*global $*/ 14 | 15 | (function () { 16 | 17 | // Helper Setup 18 | // ------------ 19 | 20 | // Local storage keys' prefix. 21 | var PREFIX = 'gs-'; 22 | 23 | // Script running environment (gm or crx). 24 | var MODE_GM = 0, 25 | MODE_CRX = 1, 26 | MODE = MODE_CRX; 27 | if ($.hasOwnProperty('facebox')) { 28 | MODE = MODE_GM; 29 | } 30 | 31 | // Templating settings. 32 | var TEMPLATE = { 33 | interpolate: /<%=([\w ]+)%>/g 34 | }; 35 | 36 | // Add comments box markup. 37 | var ADD_COMMENTS_BOX = '' + 38 | '

    Add comments

    ' + 39 | '

    ' + 42 | '' + 43 | ''; 46 | 47 | // Edit comments button markup. 48 | var EDIT_COMMENTS_BTN = '' + 49 | '
  • ' + 50 | '
    ' + 51 | '' + 52 | 'Comments' + 53 | '' + 54 | '' + 55 | '
    ' + 56 | '
  • '; 57 | 58 | // Comments fuzzy match threshold 59 | var MATCH_THRESHOLD = 0.4; 60 | 61 | // Comments fuzzy match shortest length, used for improving performance 62 | var MATCH_LENGTH = 2; 63 | 64 | 65 | // Utilities 66 | // --------- 67 | 68 | // k-v based comments service (using localStorage) 69 | var Comments = { 70 | realName: function (name) { 71 | return PREFIX + name; 72 | }, 73 | 74 | get: function (projectName) { 75 | projectName = Comments.realName(projectName); 76 | return localStorage[projectName] || ''; 77 | }, 78 | 79 | set: function (projectName, comments) { 80 | projectName = Comments.realName(projectName); 81 | localStorage[projectName] = comments; 82 | } 83 | }; 84 | 85 | // a not so fast templating helper 86 | var tmpl = function (markup, data) { 87 | var pattern = TEMPLATE.interpolate, 88 | key; 89 | 90 | while ((key = pattern.exec(markup)) !== null) { 91 | markup = markup.replace(key[0], data[key[1].trim()]); 92 | } 93 | 94 | return markup; 95 | }; 96 | 97 | // Compare two string and get a score for similarity. 98 | // Original code from https://github.com/joshaven/string_score 99 | // TODO test cases 100 | var cmp = function (a, b) { 101 | if (a === b) return 1; 102 | if (b === "") return 0; 103 | 104 | var runningScore = 0, charScore, finalScore, 105 | aString = a.toLowerCase(), aLength = a.length, 106 | bString = b.toLowerCase(), bLength = b.length, 107 | idxOf, startAt = 0, 108 | fuzzies = 1; 109 | 110 | for (var i = 0;i < bLength;i++) { 111 | idxOf = aString.indexOf(bString[i], startAt); 112 | 113 | if (idxOf === -1) { 114 | return 0; 115 | } else if (startAt === idxOf) { 116 | charScore = 0.7; 117 | } else { 118 | charScore = 0.1; 119 | if (a[idxOf - 1] === ' ') charScore += 0.8; 120 | } 121 | 122 | if (a[idxOf] === b[i]) charScore += 0.1; 123 | 124 | runningScore += charScore; 125 | startAt = idxOf + 1; 126 | } 127 | 128 | finalScore = 0.5 * (runningScore / aLength + runningScore / bLength) / fuzzies; 129 | 130 | if (aString[0] === bString[0] && finalScore < 0.85) finalScore += 0.15; 131 | 132 | return finalScore; 133 | }; 134 | 135 | // Run code via `chrome.tabs.executeScript` 136 | var chromeRunCode = function (snippet, cb) { 137 | cb = cb || function () {}; 138 | 139 | chrome.runtime.sendMessage({ 140 | snippet: snippet 141 | }, cb); 142 | }; 143 | 144 | 145 | // Components Setup 146 | // ---------------- 147 | 148 | var addComments = function (projectName) { 149 | var originalComments = Comments.get(projectName), 150 | boxTmpl = tmpl(ADD_COMMENTS_BOX, { comments: originalComments }); 151 | 152 | var bindAddBtn = function() { 153 | var addBtn = document.querySelector('#add-comments-btn'), 154 | commentsInp = document.querySelector('#add-comments-inp'); 155 | 156 | addBtn.addEventListener('click', function () { 157 | var comments = commentsInp.value; 158 | 159 | if (comments != originalComments) { 160 | Comments.set(projectName, comments); 161 | } 162 | 163 | if (MODE === MODE_GM) { 164 | $(document).trigger('close.facebox'); 165 | } else { 166 | chromeRunCode(tmpl('$(document).trigger("close.facebox");')); 167 | } 168 | }); 169 | }; 170 | 171 | if (MODE === MODE_GM) { 172 | $.facebox(boxTmpl); 173 | bindAddBtn(); 174 | } else { 175 | chromeRunCode(tmpl("$.facebox('<%= tmpl %>')", {tmpl: boxTmpl}), bindAddBtn); 176 | } 177 | }; 178 | 179 | 180 | // Page Manipuliation 181 | // ------------------ 182 | 183 | var isStarsPage = function (path) { 184 | return path === 'stars'; 185 | }; 186 | 187 | var isProjectPage = function (path) { 188 | return path.split('/').length === 2; 189 | }; 190 | 191 | var starsPage = function () { 192 | var starred_repos = document.querySelectorAll('.repo_list li'), 193 | starred_repos_count = starred_repos.length, 194 | repos = [], curRepo, 195 | ele, name; 196 | 197 | // TODO loops function oops? 198 | var curried = function (name) { 199 | return function () { 200 | addComments(name); 201 | }; 202 | }; 203 | 204 | for (var i = 0;i < starred_repos_count;i++) { 205 | ele = starred_repos[i]; 206 | name = ele.querySelector('h3 a').innerText; 207 | curRepo = { 208 | ele: ele, 209 | name: name, 210 | comments: Comments.get(name) 211 | }; 212 | repos.push(curRepo); 213 | 214 | // bind the :star: buttons 215 | curRepo.ele 216 | .querySelector('.unstarred') 217 | .addEventListener('click', curried(curRepo.name)); 218 | 219 | // inject comments 220 | if (curRepo.comments) { 221 | curRepo.ele.appendChild($('

    ' + curRepo.comments + '

    ')[0]); 222 | } 223 | } 224 | 225 | // bind search box 226 | var q = document.querySelector('input#star-repo-search'); 227 | 228 | // TODO performance check 229 | q.addEventListener('keyup', function () { 230 | var needle = q.value; 231 | 232 | if (needle.length === 0) { 233 | repos.forEach(function (repo) { 234 | repo.ele.classList.remove('hidden'); 235 | }) 236 | } 237 | 238 | if (needle.length < MATCH_LENGTH) return; 239 | 240 | // compare project name and comments 241 | var cmpRepo = function (repo) { 242 | return ([repo.name, repo.comments].filter(function (a) { 243 | return (cmp(a, needle) >= MATCH_THRESHOLD); 244 | }).length > 0); 245 | }; 246 | 247 | repos.forEach(function (repo) { 248 | if (cmpRepo(repo)) { 249 | repo.ele.classList.remove('hidden'); 250 | } else { 251 | repo.ele.classList.add('hidden'); 252 | } 253 | }); 254 | }); 255 | }; 256 | 257 | var projectPage = function (projectName) { 258 | // inject edit comments box 259 | document 260 | .querySelector('.pagehead-actions') 261 | .appendChild($(EDIT_COMMENTS_BTN)[0]); 262 | 263 | // bind the :star: buttons 264 | var btn = document.querySelectorAll('.unstarred'), 265 | l = btn.length, 266 | e; 267 | for (var i = 0;i < l;i++) { 268 | e = btn[i]; 269 | e.addEventListener('click', function () { 270 | addComments(projectName); 271 | }) 272 | } 273 | }; 274 | 275 | 276 | // Helper Initialization 277 | // --------------------- 278 | 279 | var kick = function () { 280 | var path = location.pathname.substring(1); 281 | 282 | // faking a route 283 | if (isStarsPage(path)) { 284 | starsPage(); 285 | } else if (isProjectPage(path)) { 286 | projectPage(path); 287 | } else { 288 | console.log('reaching ', path); 289 | } 290 | }; 291 | 292 | kick(); 293 | })(); 294 | -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "background": { 3 | "scripts": ["bg.js"] 4 | } 5 | } 6 | --------------------------------------------------------------------------------