├── demo
├── nested
│ ├── .editorconfig
│ ├── bar.js
│ └── foo.js
├── bar.js
├── foo.js
└── .editorconfig
├── src
├── chrome
│ └── extension_info.json
├── firefox
│ └── extension_info.json
├── opera
│ └── extension_info.json
├── common
│ ├── icons
│ │ ├── button.png
│ │ ├── icon32.png
│ │ ├── icon48.png
│ │ ├── icon100.png
│ │ └── icon128.png
│ ├── res
│ │ └── default.editorconfig
│ ├── options.js
│ ├── fs-http.js
│ ├── options.html
│ ├── background.js
│ └── content.js
└── safari
│ └── extension_info.json
├── .gitignore
├── webpack.config.js
├── generate-manifest.js
├── LICENSE
├── package.json
└── README.md
/demo/nested/.editorconfig:
--------------------------------------------------------------------------------
1 | [foo.js]
2 | tab_width = 3
3 |
--------------------------------------------------------------------------------
/src/chrome/extension_info.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "bppnolhdpdfmmpeefopdbpmabdpoefjh"
3 | }
--------------------------------------------------------------------------------
/demo/bar.js:
--------------------------------------------------------------------------------
1 | function bar() {
2 | var FOO = 'FOO',
3 | barBazQux = 'BAR-BAZ-QUX';
4 | }
5 |
--------------------------------------------------------------------------------
/demo/foo.js:
--------------------------------------------------------------------------------
1 | function foo() {
2 | var foo = 'FOO',
3 | barBazQux = 'BAR-BAZ-QUX';
4 | }
5 |
--------------------------------------------------------------------------------
/src/firefox/extension_info.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "{E9686429-E445-4B89-9516-322DAE1E0389}"
3 | }
--------------------------------------------------------------------------------
/demo/nested/bar.js:
--------------------------------------------------------------------------------
1 | function bar() {
2 | var FOO = 'FOO',
3 | barBazQux = 'BAR-BAZ-QUX';
4 | }
5 |
--------------------------------------------------------------------------------
/demo/nested/foo.js:
--------------------------------------------------------------------------------
1 | function foo() {
2 | var FOO = 'FOO',
3 | barBazQux = 'BAR-BAZ-QUX';
4 | }
5 |
--------------------------------------------------------------------------------
/src/opera/extension_info.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "http://kangoextensions.com/extensions/githubeditorconfig/"
3 | }
--------------------------------------------------------------------------------
/src/common/icons/button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cougar/github-editorconfig/master/src/common/icons/button.png
--------------------------------------------------------------------------------
/src/common/icons/icon32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cougar/github-editorconfig/master/src/common/icons/icon32.png
--------------------------------------------------------------------------------
/src/common/icons/icon48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cougar/github-editorconfig/master/src/common/icons/icon48.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /certificates
2 | /*.log
3 | *.built.*
4 | /src/common/extension_info.json
5 | /node_modules
6 | /output
7 |
--------------------------------------------------------------------------------
/src/common/icons/icon100.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cougar/github-editorconfig/master/src/common/icons/icon100.png
--------------------------------------------------------------------------------
/src/common/icons/icon128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Cougar/github-editorconfig/master/src/common/icons/icon128.png
--------------------------------------------------------------------------------
/src/safari/extension_info.json:
--------------------------------------------------------------------------------
1 | {
2 | "developer_id": "YOUR_SAFARI_DEVELOPER_ID",
3 | "id": "com.kangoextensions.githubeditorconfig"
4 | }
--------------------------------------------------------------------------------
/demo/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | indent_style = tab
5 |
6 | [foo.js]
7 | tab_width = 4
8 |
9 | [bar.js]
10 | tab_width = 3
11 |
--------------------------------------------------------------------------------
/src/common/res/default.editorconfig:
--------------------------------------------------------------------------------
1 | # This will be used as default EditorConfig for repos that don't have their own.
2 | # It's saved automatically as you type.
3 | # You can find docs at http://editorconfig.org
4 |
5 | [*]
6 | tab_width = 4
7 |
8 | [*.rb]
9 | indent_style = space
10 | indent_size = 2
11 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: './src/common/background.js',
3 | output: {
4 | path: 'src/common',
5 | filename: 'background.built.js'
6 | },
7 | module: {
8 | loaders: [
9 | {test: /\.json$/, loader: 'json-loader'}
10 | ],
11 | noParse: /fnmatch/
12 | },
13 | resolve: {
14 | root: process.cwd(),
15 | alias: {
16 | fs: 'src/common/fs-http.js'
17 | }
18 | }
19 | };
--------------------------------------------------------------------------------
/src/common/options.js:
--------------------------------------------------------------------------------
1 | KangoAPI.onReady(function () {
2 | var storage = kango.storage;
3 | var area = document.getElementById('config');
4 |
5 | area.value = storage.getItem('editorconfig');
6 |
7 | function store() {
8 | storage.setItem('editorconfig', area.value);
9 | }
10 |
11 | var lastTimeout = 0;
12 |
13 | area.addEventListener('keyup', function () {
14 | clearTimeout(lastTimeout);
15 | lastTimeout = setTimeout(store, 100);
16 | });
17 | });
--------------------------------------------------------------------------------
/src/common/fs-http.js:
--------------------------------------------------------------------------------
1 | var resolvePath = require('path').resolve;
2 |
3 | exports.readFile = function (path) {
4 | var callback = arguments[arguments.length - 1];
5 | kango.xhr.send({
6 | method: 'GET',
7 | async: true,
8 | url: 'https://raw.githubusercontent.com' + resolvePath(path)
9 | }, function (data) {
10 | if (data.status === 200) {
11 | callback(null, data.response);
12 | } else {
13 | callback(new Error(data.response));
14 | }
15 | });
16 | };
--------------------------------------------------------------------------------
/generate-manifest.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 | var pkg = require('./package.json');
3 |
4 | var manifest = {};
5 |
6 | ['name', 'version', 'description'].forEach(function (name) {
7 | manifest[name] = pkg[name];
8 | });
9 |
10 | Object.keys(pkg.extension_info).forEach(function (name) {
11 | manifest[name] = pkg.extension_info[name];
12 | });
13 |
14 | manifest.creator = pkg.author;
15 | manifest.homepage_url = pkg.homepage;
16 |
17 | fs.writeFileSync('src/common/extension_info.json', JSON.stringify(manifest, null, 4));
18 |
--------------------------------------------------------------------------------
/src/common/options.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Default EditorConfig for GitHub
5 |
6 |
7 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Ingvar Stepanyan
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 |
23 |
--------------------------------------------------------------------------------
/src/common/background.js:
--------------------------------------------------------------------------------
1 | var Promise = require('bluebird');
2 | var pathUtils = require('path');
3 | var ec = require('editorconfig');
4 |
5 | if (!kango.storage.getItem('editorconfig')) {
6 | kango.xhr.send({
7 | method: 'GET',
8 | async: true,
9 | url: 'res/default.editorconfig'
10 | }, function (data) {
11 | kango.storage.setItem('editorconfig', data.response);
12 | });
13 | }
14 |
15 | global.getEditorConfig = function (path, callback) {
16 | var defaultConfig = ec.parseFromFiles(path.relative, [{
17 | name: pathUtils.resolve('.editorconfig'),
18 | contents: kango.storage.getItem('editorconfig') || ''
19 | }]);
20 |
21 | var repoConfig = ec.parse(pathUtils.join(path.root, path.relative), {
22 | root: path.root
23 | });
24 |
25 | Promise.settle([defaultConfig, repoConfig])
26 | .reduce(function (merged, current) {
27 | if (current.isFulfilled()) {
28 | current = current.value();
29 | for (var name in current) {
30 | merged[name] = current[name];
31 | }
32 | }
33 | return merged;
34 | }, {$path: path})
35 | .done(callback);
36 | };
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "github-editorconfig",
3 | "version": "1.1.1",
4 | "description": "EditorConfig support for GitHub",
5 | "dependencies": {
6 | "bluebird": "^2.3.6",
7 | "editorconfig": "^0.12.0"
8 | },
9 | "devDependencies": {
10 | "json-loader": "^0.5.1",
11 | "webpack": "^1.4.8"
12 | },
13 | "scripts": {
14 | "kango": "python ../kango/kango.py build .",
15 | "prepublish": "node generate-manifest && webpack -p && npm run kango",
16 | "watch": "webpack --watch --debug --devtool eval --output-path=output/chrome",
17 | "dev": "npm run prepublish && npm run watch"
18 | },
19 | "extension_info": {
20 | "name": "GitHub EditorConfig",
21 | "background_scripts": [
22 | "background.built.js"
23 | ],
24 | "content_scripts": [
25 | "content.js"
26 | ],
27 | "options_page": "options.html",
28 | "permissions": {
29 | "content_scripts": [
30 | "https://github.com/*"
31 | ],
32 | "xhr": [
33 | "https://raw.githubusercontent.com/*"
34 | ]
35 | }
36 | },
37 | "repository": {
38 | "type": "git",
39 | "url": "https://github.com/RReverser/github-editorconfig"
40 | },
41 | "author": "Ingvar Stepanyan (http://rreverser.com)",
42 | "license": "MIT",
43 | "bugs": {
44 | "url": "https://github.com/RReverser/github-editorconfig/issues"
45 | },
46 | "homepage": "https://github.com/RReverser/github-editorconfig",
47 | "private": true
48 | }
49 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | github-editorconfig
2 | ===================
3 |
4 | This is a browser extension that provides [EditorConfig](http://editorconfig.org/) support for GitHub.
5 |
6 | Download links
7 | --------------
8 |
9 | You can download extension for your browser from the corresponding store:
10 |
11 | * [Chrome](https://chrome.google.com/webstore/detail/github-editorconfig/bppnolhdpdfmmpeefopdbpmabdpoefjh)
12 | * [Firefox](https://github.com/RReverser/github-editorconfig/releases/download/1.1.0/githubeditorconfig_1.1.0.xpi)
13 | * [Opera](https://addons.opera.com/extensions/details/github-editorconfig/)
14 |
15 | Description
16 | -----------
17 |
18 | Extension looks for [`.editorconfig`](http://editorconfig.org/#example-file) files in the repository the current file belongs to, and applies it's settings to code viewer and editor. Branch is always taken into account.
19 |
20 | On options page you can also set [default editorconfig](src/common/res/default.editorconfig).
21 |
22 | You can test extension on files in [demo](demo) folder of this repo.
23 |
24 | Extension is built with [Kango - cross-browser extension framework](http://kangoextensions.com/).
25 |
26 | Screenshots
27 | -----------
28 |
29 | Sample .editorconfig:
30 |
31 | 
32 |
33 | Code viewer (tabs are set to preconfigured width of 4 instead of GitHub's default 8):
34 |
35 | 
36 |
37 | Code editor (preconfigured options are chosen and marked as **(auto)**; `trim_trailing_whitespace` and `insert_final_newline` are taken into account on commit):
38 |
39 | 
40 |
41 | Options page (just a default `.editorconfig`):
42 |
43 | 
44 |
--------------------------------------------------------------------------------
/src/common/content.js:
--------------------------------------------------------------------------------
1 | // ==UserScript==
2 | // @name github-editorconfig
3 | // @include https://github.com/*
4 | // ==/UserScript==
5 |
6 | function $(query, context) {
7 | return (context || document).querySelector(query);
8 | }
9 |
10 | function reselect(selectQuery, newValue, insert) {
11 | var select = $(selectQuery), option;
12 | // not applicable for current page
13 | if (!select) {
14 | return;
15 | }
16 | // remove ' (auto)' in old option
17 | option = select.options[select.selectedIndex];
18 | option.textContent = option.textContent.replace(' (auto)', '');
19 | // set new value
20 | select.value = newValue;
21 | option = select.options[select.selectedIndex];
22 | // if such option doesn't exist, create it
23 | if (!option) {
24 | option = document.createElement('option');
25 | option.value = newValue;
26 | insert(option, select);
27 | option.selected = true;
28 | }
29 | // add ' (auto)' to editorconfig'ured option
30 | option.textContent += ' (auto)';
31 | // manually fire 'change' event on select (as it doesn't by default)
32 | var e = document.createEvent('HTMLEvents');
33 | e.initEvent('change', false, true);
34 | select.dispatchEvent(e);
35 | return option;
36 | }
37 |
38 | var config = {};
39 |
40 | function setEditorConfig(newConfig) {
41 | config = newConfig;
42 |
43 | var viewer = $('.highlight');
44 |
45 | // set 'tab-size' CSS property
46 | if (viewer && config.tab_width) {
47 | ['tabSize', 'MozTabSize', 'OTabSize', 'WebkitTabSize'].some(function (propName) {
48 | if (propName in this) {
49 | this[propName] = config.tab_width;
50 | return true;
51 | }
52 | }, viewer.style);
53 | }
54 |
55 | if (config.indent_style) {
56 | reselect('.js-code-indent-mode', config.indent_style + 's');
57 | }
58 |
59 | if (config.indent_size) {
60 | reselect('.js-code-indent-width', config.indent_size, function (option, select) {
61 | var optgroup = $('optgroup', select);
62 | option.textContent = option.value;
63 |
64 | // find place to insert new option and keep list sorted
65 | var options = select.options;
66 | for (var beforeIndex = 0; beforeIndex < options.length; beforeIndex++) {
67 | if (options[beforeIndex].value > option.value) {
68 | break;
69 | }
70 | }
71 | optgroup.insertBefore(option, options[beforeIndex]);
72 | });
73 | }
74 | }
75 |
76 | // bind once for navigating through History API
77 | document.addEventListener('submit', function (event) {
78 | if (event.target !== $('.edit-file>form')) {
79 | return;
80 | }
81 |
82 | var editor = $('#blob_contents');
83 | var text = editor.value;
84 |
85 | if (config.trim_trailing_whitespace) {
86 | text = text.replace(/\s*?$/mg, '');
87 | }
88 |
89 | if (config.insert_final_newline && text.slice(-1) !== '\n') {
90 | text += '\n';
91 | }
92 |
93 | editor.value = text;
94 | });
95 |
96 | function getEditorConfig(pathname, callback) {
97 | var path = pathname.slice(1).split('/');
98 |
99 | var repo = path.slice(0, 2);
100 | var action = path[2]; //
101 | var commit = path[3]; // use branch name by default
102 |
103 | if (action !== 'blob' && action !== 'edit') {
104 | return;
105 | }
106 |
107 | // try to find exact commit SHA on page
108 | var commitElement;
109 | if (commitElement = $('.js-permalink-shortcut')) {
110 | commit = commitElement.pathname.split('/')[4];
111 | } else if (commitElement = $('input[name="commit"]')) {
112 | commit = commitElement.value;
113 | }
114 |
115 | kango.invokeAsyncCallback('getEditorConfig', {
116 | absolute: pathname,
117 | root: repo.concat([commit]).join('/'),
118 | relative: path.slice(4).join('/')
119 | }, callback);
120 | }
121 |
122 | var lastPathName = '';
123 |
124 | function update() {
125 | var newPathName = location.pathname;
126 | if (newPathName === lastPathName) {
127 | return;
128 | }
129 | lastPathName = newPathName;
130 | getEditorConfig(newPathName, function (config) {
131 | if (config.$path.absolute === location.pathname) {
132 | setEditorConfig(config);
133 | }
134 | });
135 | }
136 |
137 | var pjaxContainer = $('#js-repo-pjax-container');
138 |
139 | if (pjaxContainer) {
140 | // use MutationObserver as we can't inject into History API
141 | new MutationObserver(update).observe(pjaxContainer, {childList: true});
142 |
143 | // initial "update"
144 | update();
145 | }
--------------------------------------------------------------------------------