├── LICENSE ├── README.md ├── github-better-header.js ├── header.html ├── manifest.json └── style.css /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 itchyny 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Better Header 2 | ## We love the previous header! 3 |  4 | 5 | ## Why? 6 | - The Pull request and Issues links are welcome. 7 | - But we miss the profile link in the header. 8 | - There are some other projects which aim as same as this project, but their scripts are loaded after the contents are loaded. Thus the header gets better after a few hundreds of milliseconds. On the other hand, this extension replaces the elements before the document is loaded. 9 | 10 | ## Requirements 11 | - Google Chrome 12 | 13 | ## Installation 14 | - Clone this repository 15 | - Click Load unpacked extension button in the chrome://extensions/. 16 | 17 | ## Author 18 | itchyny (https://github.com/itchyny) 19 | 20 | ## License 21 | This software is released under the MIT License, see LICENSE. 22 | -------------------------------------------------------------------------------- /github-better-header.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | 3 | // Promise-like object of gathering variables 4 | function variablesPool(variables) { 5 | var pool = {}; 6 | var values = {}; 7 | var resolved = false; 8 | var callbacks = []; 9 | pool.set = function(name, value) { 10 | values[name] = value; 11 | if (Object.keys(values).length === variables.filter(function(v) { 12 | return !v.when || values.hasOwnProperty(v.when) && values[v.when]; 13 | }).length) { 14 | resolved = true; 15 | callbacks.forEach(function(callback) { 16 | callback(values); 17 | }); 18 | } 19 | }; 20 | pool.then = function(callback) { 21 | if (resolved) { 22 | callback(values); 23 | } else { 24 | callbacks.push(callback); 25 | } 26 | }; 27 | return pool; 28 | } 29 | 30 | // Mutation observer witness with the settings injected 31 | function witness(setting) { 32 | var varpool = variablesPool(setting.variables); 33 | var htmlpromise = setting.overwrites.map(function(overwrite) { 34 | return [ overwrite.fileName, fetch(chrome.extension.getURL(overwrite.fileName)).then(function(response) { return response.text(); }) ]; 35 | }).reduce(function (o, v) { o[v[0]] = v[1]; return o; }, {}); 36 | return function(mutations) { 37 | [].forEach.call(mutations, function(mutation) { 38 | [].forEach.call(mutation.addedNodes || [], function(node) { 39 | // we firstly check variables 40 | setting.variables.forEach(function(variable) { 41 | if (!variable.done && node.getAttribute && node.getAttribute(variable.attribute) 42 | && (node.getAttribute('name') === variable.name 43 | || node.className && node.className.toString().indexOf(variable.className) >= 0)) { 44 | varpool.set(variable.title, node.getAttribute(variable.attribute)); 45 | variable.done = true; 46 | } 47 | }); 48 | // then check whether or not we overwrite the element 49 | setting.overwrites.forEach(function(overwrite) { 50 | if (!overwrite.done && node.className && node.className.toString().indexOf(overwrite.className) >= 0) { 51 | node.style.display = 'none'; 52 | htmlpromise[overwrite.fileName].then(function(innerHTML) { 53 | varpool.then(function(variables) { 54 | if (!overwrite.when || overwrite.when.split(',').every(x => x.match('^(.*)=(.*)$') ? variables[RegExp.$1] === RegExp.$2 : variables[x])) { 55 | node.innerHTML = Object.keys(variables).reduce(function(innerHTML, name) { 56 | return innerHTML.replace(new RegExp('{{ *' + name + ' *}}', 'g'), variables[name]); 57 | }, innerHTML); 58 | } 59 | node.style.display = ''; 60 | }); 61 | }); 62 | overwrite.done = true; 63 | } 64 | }); 65 | }); 66 | }); 67 | // if all things done, stop observation 68 | if (setting.variables.every(function(variable) { return variable.done; }) 69 | && setting.overwrites.every(function(overwrite) { return overwrite.done; })) { 70 | this.disconnect(); 71 | } 72 | }; 73 | } 74 | 75 | // overwrite the page with the settings injected 76 | function start(setting) { 77 | var observer = new MutationObserver(witness(setting)); 78 | observer.observe(setting.target, setting.config); 79 | } 80 | 81 | // we start the process 82 | start({ 83 | target: document, 84 | config: { childList: true, subtree: true }, 85 | overwrites: [ 86 | { 87 | className: 'js-header-wrapper', 88 | fileName: 'header.html', 89 | when: 'user,hostname=github.com' 90 | } 91 | ], 92 | variables: [ 93 | { 94 | title: 'hostname', 95 | name: 'hostname', 96 | attribute: 'content' 97 | }, 98 | { 99 | title: 'user', 100 | name: 'user-login', 101 | attribute: 'content' 102 | }, 103 | { 104 | title: 'avatar', 105 | className: 'avatar', 106 | attribute: 'src', 107 | when: 'user' 108 | }, 109 | { 110 | title: 'search_action', 111 | className: 'js-site-search-form', 112 | attribute: 'action', 113 | when: 'user' 114 | }, 115 | { 116 | title: 'search_url', 117 | className: 'js-site-search-form', 118 | attribute: 'data-unscoped-search-url', 119 | when: 'user' 120 | }, 121 | { 122 | title: 'authenticity_token', 123 | name: 'authenticity_token', 124 | attribute: 'value', 125 | when: 'user' 126 | }, 127 | { 128 | title: 'form_nonce', 129 | name: 'html-safe-nonce', 130 | attribute: 'content', 131 | when: 'user' 132 | } 133 | ] 134 | }); 135 | 136 | })(); 137 | -------------------------------------------------------------------------------- /header.html: -------------------------------------------------------------------------------- 1 |