!1 合并 octotree/master 最新代码,解决冲突

Merge pull request !1 from liu/new_version
This commit is contained in:
liu 2018-03-09 20:37:31 +08:00 committed by inu1255
parent 130db7e943
commit 56e7a2e8a7
44 changed files with 3410 additions and 1024 deletions

View File

@ -1,3 +1,30 @@
### v2.4.3
* Fix issue branch path not encoded in file navigation
### v2.4.2
* Fix issue download icon not shown upon hover
### v2.4.1
* Support file icons
### v2.3.3
* Handle local storage error
### v2.3.2
* Improve file navigation speed in pull requests
### v2.3.1
* Fix bug PR mode doesn't work with lazy loading
* Fix bug sidebar width is not stable in Firefox
### v2.3.0
* Support PR viewing mode
* Handle reserved chars in URLs
### v2.2.0
* Support BitBucket (experimental)
* Fix minor bugs
### v2.1.0
* Remove GitLab support
* Improve GitHub styling

View File

@ -1,20 +1,30 @@
[![OpenCollective](https://opencollective.com/octotree/backers/badge.svg)](#backers)
[![OpenCollective](https://opencollective.com/octotree/sponsors/badge.svg)](#sponsors)
[![OpenCollective](https://opencollective.com/octotree/backers/badge.svg)](#backers)
## GitCodeTree
fork from [https://github.com/buunguyen/octotree](https://github.com/buunguyen/octotree)
### 二次开发
## 安装
### 从预先构建的包安装(适用于所有浏览器)
**鉴于国内墙的原因,你可以通过下载已经构建好的包来安装**
预先构建的包可以从 [https://gitee.com/oschina/GitCodeTree/tree/master/dist](https://gitee.com/oschina/GitCodeTree/tree/master/dist) 下载。出于安全原因,请不要从其它地方下载。
__注意__: Firefox 43 + 需要签名。因此您需要从Mozilla商店安装GitCodeTree。出于某种原因,如果你想安装预先构建的包,请参考 [disable sign-check](https://github.com/buunguyen/octotree/issues/220#issuecomment-166012724)。
### 在 Chrome, Firefox 和 Opera 上安装
* 从 [Chrome Web Store](https://chrome.google.com/webstore/detail/gitcodetree/inaaldjpdbkaodlmdcplgpoibohcmmlj)[Mozilla Add-ons Store](https://addons.mozilla.org/zh-CN/firefox/addon/GitCodeTree/) or [Opera Add-ons Store](https://addons.opera.com/en/extensions/details/gitcodetree/) 安装GitCodeTree
* 导航到任何Gitee、GitHub库(或者刷新这个页面作为一个例子)
* 代码树将显示在页面左边
### 在 Safari 上安装
GitCodeTree在Safari gallery中不可用所以您必须使用预先构建的包 或者 从源代码构建一个。
* 下载 [Safari 预先构建的包](https://gitee.com/oschina/GitCodeTree/blob/master/dist/safari.safariextz?raw=true)
* 双击或者拖拽到Safari窗口
* 将项目clone到本地
* 在`src/adapters/`中为你想要支持的网站添加一个类(可复制`src/adapters/github.js`并修改)
* 根据情况实现 [`_getTree`](https://gitee.com/inu1255/GitCodeTree/blob/master/src/adapters/github.js#L149-154) 或 `_get` 方法,用于获取项目树
* 实现 [`updateLayout`](https://gitee.com/inu1255/GitCodeTree/blob/master/src/adapters/github.js#L65-73) 方法,用于修改页面布局
* 实现 [`selectFile`](https://gitee.com/inu1255/GitCodeTree/blob/master/src/adapters/github.js#L135-138) 指定pjax替换的html元素,用于不刷新切换文件
* 在 [`src/octotree.js`](https://gitee.com/inu1255/GitCodeTree/blob/master/src/octotree.js#L30)中添加你修改好的类
* 在 `src/config/` 插件配置文件中添加你想要支持的网站
* __chrome中调试__: 使用`gulp chrome`命令,打开[chrome://extensions/](chrome://extensions/),点击`加载已解压的扩展程度`,选择`src/tmp/chrome`
* __打包__: 使用 `gulp dist` 命令打包
## 介绍
@ -28,44 +38,41 @@ fork from [https://github.com/buunguyen/octotree](https://github.com/buunguyen/o
![GitCodeTree on GitHub](docs/chrome-github.png)
### 在 Chrome, Firefox 和 Opera 上安装
* 从 [Chrome Web Store](https://chrome.google.com/webstore/detail/gitcodetree/inaaldjpdbkaodlmdcplgpoibohcmmlj), [Mozilla Add-ons Store](https://addons.mozilla.org/zh-CN/firefox/addon/GitCodeTree/) or [Opera Add-ons Store](https://addons.opera.com/zh-cn/extensions/details/gitcodetree/?display=en) 安装GitCodeTree
* 导航到任何Gitee、GitHub库(或者刷新这个页面作为一个例子)
* 代码树将显示在页面左边
### 二次开发
### 在 Safari 上安装
* 将项目clone到本地
* 在`src/adapters/`中为你想要支持的网站添加一个类(可复制`src/adapters/github.js`并修改)
* 根据情况实现 [`_getTree`](https//gitee.com/inu1255/GitCodeTree/blob/master/src/adapters/github.js#L149-154) 或 `_get` 方法,用于获取项目树
* 实现 [`updateLayout`](https//gitee.com/inu1255/GitCodeTree/blob/master/src/adapters/github.js#L65-73) 方法,用于修改页面布局
* 实现 [`selectFile`](https//gitee.com/inu1255/GitCodeTree/blob/master/src/adapters/github.js#L135-138) 指定pjax替换的html元素用于不刷新切换文件
* 在 [`src/octotree.js`](https//gitee.com/inu1255/GitCodeTree/blob/master/src/octotree.js#L30)中添加你修改好的类
* 在 `src/config/` 插件配置文件中添加你想要支持的网站
* __chrome中调试__: 使用`gulp chrome`命令,打开[chrome://extensions/](chrome://extensions/),点击`加载已解压的扩展程度`,选择`src/tmp/chrome`
* __打包__: 使用 `gulp dist` 命令打包
GitCodeTree在Safari gallery中不可用所以,您必须使用预先构建的包 或者 从源代码构建一个。
* 下载 [Safari 预先构建的包](https://gitee.com/inu1255/GitCodeTree/blob/master/dist/safari.safariextz?raw=true)
* 双击或者拖拽到Safari窗口
### 从预先构建的包安装(所有浏览器)
预先构建的包可以从 [这里](https://gitee.com/inu1255/GitCodeTree/tree/master/dist) 下载. 出于安全原因,请不要从其它地方下载.
__注意__: Firefox 43 + 需要签名。因此您需要从Mozilla商店安装GitCodeTree。出于某种原因,如果你想安装预先构建的包, 请参考 [disable sign-check](https://github.com/buunguyen/octotree/issues/220#issuecomment-166012724).
## 设置
### Access Token
__注意__: GitCodeTree 访问令牌在浏览器本地存储并不会上传到任何地方。如果你想验证,查看源代码,开始 [请参考这里](https://gitee.com/inu1255/GitCodeTree/blob/master/src/view.options.js#L77).
__注意__: GitCodeTree 访问令牌在浏览器本地存储并不会上传到任何地方。如果你想验证,查看源代码,开始 [请参考这里](https//gitee.com/inu1255/GitCodeTree/blob/master/src/view.options.js#L77).
#### GitHub
GitCodeTree 使用 [GitHub API](https://developer.github.com/v3/) 检索代码树。默认情况下,它使未经身份验证的请求到GitHub API。然而,有两种情况时必须经过身份验证的请求:
GitCodeTree 使用 [GitHub API](https://developer.github.com/v3/) 检索代码树。默认情况下它使未经身份验证的请求到GitHub API。然而有两种情况时必须经过身份验证的请求:
* 你访问一个私人存储库
* 你超过 [请求频率限制限制](https://developer.github.com/v3/#rate-limiting)
当这种情况发生时,GitCodeTree会询问你 [GitHub 私人 access token](https://help.github.com/articles/creating-an-access-token-for-command-line-use). 如果你没有, [点此创建](https://github.com/settings/tokens/new), 然后复制粘贴到文本框中。注意,至少要允许"public_repo","repo" (如果你需要访问私人仓库).
当这种情况发生时GitCodeTree会询问你 [GitHub 私人 access token](https://help.github.com/articles/creating-an-access-token-for-command-line-use). 如果你没有[点此创建](https://github.com/settings/tokens/new) 然后复制粘贴到文本框中。注意,至少要允许"public_repo""repo" (如果你需要访问私人仓库).
![Settings](docs/settings.jpg)
### 其它
* __热键__: GitCodeTree 使用 [keymaster](https://github.com/madrobby/keymaster) 注册热键。查看 [支持的按键](https://github.com/madrobby/keymaster#supported-keys).
* __记得栏可见性__: 如果勾选此项,基于其可见性显示或隐藏GitCodeTree.
* __在非代码页__: 如果勾选此项,让GitCodeTree等非代码页的问题和请求.
* __一次加载整个树__: (仅支持github) 如果勾选此项,进入项目页面时GitCodeTree将加载整个项目树。如果您经常访问非常大的项目为了避免长时间加载,请勿勾选此项.
* __记得栏可见性__: 如果勾选此项基于其可见性显示或隐藏GitCodeTree.
* __在非代码页__: 如果勾选此项让GitCodeTree等非代码页的问题和请求.
* __一次加载整个树__: (仅支持github) 如果勾选此项进入项目页面时GitCodeTree将加载整个项目树。如果您经常访问非常大的项目为了避免长时间加载请勿勾选此项.
## Credits
* [@crashbell](https://github.com/crashbell) for helping with GitLab and others

27
chrome_test_key.pem Normal file
View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA7pVSB0UiadYCxCjf8zngckEBVtOASXjxdJKVlTtbBxApg873
0wujcqSZRQrs39m4rV/C0qoqWAnGpfYL7ACqrsZn/FSFfdiGFU9wxQnbPbNIm3zy
xVWuD2sHbp7UtAwIZknQxjkwHV3qoPTRGoXkQ6brLH/khi6B7Ps7y2xURyoCyDmb
f2XjhCSbVRGhsPSZpfekVz4WGiLDMZV0av6d1RuXoXJXjb1sKuDF1xwTUEc+Q8BL
eY3cfzuIvo5KoyB2F0x1mzJrg8KBcTbtXZSJ8PqQlIHeJfiv/NEYHSNy4qpnhp/5
IbsvuOr3XpXPlcd4GUsYswsNrLWgTvedtyuhMwIDAQABAoIBAFT9WFVCqXmuRRn/
vZd1fKD2yzVU7pQ9wzXVqfeZR1oCxecwaVQDB1ylifvwYmVzt7f7LvnAqIjoIp2o
QqL1sdfE0fIvYcZsxYb935wxYKpgHXNWVV06omDXrbQYXIku6CcYEIksghlUBWsF
CyA5phD7ezdE7/Fky9/Rt4FFu9gFTRi2KlrQc5oE7kXbSk84IUKKgnJaiEWTHpRm
n4EFeq7NhQrHuEs/EUV9M4jreP3WqrUE3UwVS0jmxLbVzNbahjv9WuU9vCq8pbHZ
pLZC0RmLBcyxL0KFZJbsU0ZjEg7i0Wlp4N4L415/dCUUm+hVo7ZxycnyZ8OgJXzk
dnY8hBECgYEA9zffVrDE82T09DMBLtgaiiPIUsu1nLe59OKvtg6khxTqyVac1Bd0
KE1a7tHJ9V/AAv7AmJD9i287/R/xwHEd/4SfOX7wzHPiZAzTT4f2wHFcHM4j9jK+
OA5S6uOoZJPrhdtUSdVdqrOQBgYExsSG242IDdP11mHsZA1l2Nc1U30CgYEA9w7s
mjIOBJmkiqh2QBJDIGNUnLygMlOzTbt24jjE5VZT5aU/OxnrhsrgVnXQxOhO0bKG
1XuxJ3qxFKYEvMLOA6vKybvig2bjdywIqagBURf40KI2vOcjJMm0Y0bJ+wJDRoFO
P2Kux7sLTe69zn4d+2V51ZqiXePosa9bNkANhm8CgYBo7l0spaEjEiHqmM+Su4Ug
RynbAr4WQ+F5MNKV+scY0ucJCGtOlpnmFVdYcNFBBiAw5UKIP1pu2Yrj2LA7F4g9
EWSn+dppXXKlNtBmPHaenj8TtueKs2UL1ACb1H+AGUzppvuyzw6PyuGBvQI//+Ch
xgQOon4BPdUhbpJhJI9s3QKBgQCLAImcg/chHXTXUssdZ2j/KwPrH7GePzuNy6Cq
zTfytKyFc4Ds9rtuXmz0GFt6vh3CnAiCypI03TIOpQ3bSltw81IoSJ68QcJPRaYm
FlGPV8Z1VB8qD33Vb+sfRC/WG0LQw2XMAfDGjXVhxqZiLNEfpctzpAoge19rwXa3
QjiZ3wKBgQCZ3qAbCWKf5GHoBfs+MpsfbAmzkenkbEGa/JZO3TJLkz34b+aB2irj
zblnQT3o3ljtXroF/Mgw4dF+fozAWi4NjDLAlX/tImwB/jBF02xukaa51tDbchqv
gtU/3j05xaH5H46zSD9sE7qR1PSudI3etvKxFQcAYT5c/qVeKe5S/Q==
-----END RSA PRIVATE KEY-----

BIN
dist/chrome.crx vendored

Binary file not shown.

BIN
dist/chrome.zip vendored

Binary file not shown.

BIN
dist/firefox.xpi vendored

Binary file not shown.

BIN
dist/firefox.zip vendored Normal file

Binary file not shown.

BIN
dist/opera.nex vendored

Binary file not shown.

BIN
docs/chrome-github.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 564 KiB

BIN
docs/opera-github.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 561 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,209 +1,220 @@
import gulp from 'gulp'
import fs from 'fs'
import os from 'os'
import path from 'path'
import { merge } from 'event-stream'
import map from 'map-stream'
import { spawn } from 'child_process'
const $ = require('gulp-load-plugins')()
const version = require('./package.json').version
// Tasks
gulp.task('clean', () => {
return pipe('./tmp', $.clean())
return pipe('./tmp', $.clean())
})
gulp.task('build', (cb) => {
$.runSequence('clean', 'styles', 'chrome', 'opera', 'safari', 'firefox', cb)
$.runSequence('clean', 'styles', 'chrome', 'opera', 'safari', 'firefox', cb)
})
gulp.task('default', ['build'], () => {
gulp.watch(['./libs/**/*', './src/**/*'], ['default'])
gulp.watch(['./libs/**/*', './src/**/*', './package.json'], ['default'])
})
gulp.task('dist', ['build'], (cb) => {
$.runSequence('firefox:xpi', 'chrome:zip', 'chrome:crx', 'opera:nex', cb)
$.runSequence('firefox:zip', 'chrome:zip', 'chrome:crx', 'opera:nex', cb)
})
gulp.task('test', ['build'], (cb) => {
const ps = spawn(
'./node_modules/.bin/mocha',
['--harmony', '--reporter', 'spec', '--bail', '--recursive', '--timeout', '-1']
)
ps.stdout.pipe(process.stdout);
ps.stderr.pipe(process.stderr);
ps.on('close', cb)
const ps = spawn(
'./node_modules/.bin/mocha',
['--harmony', '--reporter', 'spec', '--bail', '--recursive', '--timeout', '-1']
)
ps.stdout.pipe(process.stdout);
ps.stderr.pipe(process.stderr);
ps.on('close', cb)
})
gulp.task('styles', () => {
return pipe(
'./src/styles/octotree.less',
$.plumber(),
$.less({ relativeUrls: true }),
$.autoprefixer({ cascade: true }),
'./tmp'
)
return pipe(
'./src/styles/octotree.less',
$.plumber(),
$.less({ relativeUrls: true }),
$.autoprefixer({ cascade: true }),
'./tmp'
)
})
gulp.task('lib:ondemand', (cb) => {
const dir = './libs/ondemand'
const code = fs.readdirSync(dir).map(file => {
return `window['${file}'] = function () {
${fs.readFileSync(path.join(dir, file))}
};\n`
}).join('')
const dir = './libs/ondemand'
const code = fs.readdirSync(dir).map(file => {
return `window['${file}'] = function () {
${fs.readFileSync(path.join(dir, file))}
};\n`
}).join('')
fs.writeFileSync('./tmp/ondemand.js', code)
fs.writeFileSync('./tmp/ondemand.js', code)
cb()
cb()
})
// Chrome
gulp.task('chrome:template', () => {
return buildTemplate({ CHROME: true })
return buildTemplate({SUPPORT_FILE_ICONS: true, SUPPORT_GHE: true})
})
gulp.task('chrome:js', ['chrome:template', 'lib:ondemand'], () => {
return buildJs(['./src/config/chrome/overrides.js'], { CHROME: true })
return buildJs(['./src/config/chrome/overrides.js'], {SUPPORT_FILE_ICONS: true, SUPPORT_GHE: true})
})
gulp.task('chrome', ['chrome:js'], () => {
return merge(
pipe('./icons/**/*', './tmp/chrome/icons'),
pipe('./fonts/**/*', './tmp/chrome/fonts'),
pipe(['./libs/**/*', '!./libs/ondemand{,/**}', './tmp/octotree.*', './tmp/ondemand.js'], './tmp/chrome/'),
pipe('./src/config/chrome/background.js', $.babel(), './tmp/chrome/'),
pipe('./src/config/chrome/manifest.json', $.replace('$VERSION', version), './tmp/chrome/')
)
return merge(
pipe('./icons/**/*', './tmp/chrome/icons'),
pipe(['./libs/**/*', '!./libs/ondemand{,/**}', './tmp/octotree.*', './tmp/ondemand.js'], './tmp/chrome/'),
pipe('./libs/file-icons.css', $.replace('../fonts', 'chrome-extension://__MSG_@@extension_id__/fonts'), './tmp/chrome/'),
pipe('./src/config/chrome/background.js', $.babel(), './tmp/chrome/'),
pipe('./src/config/chrome/manifest.json', $.replace('$VERSION', getVersion()), './tmp/chrome/')
)
})
gulp.task('chrome:zip', () => {
return pipe('./tmp/chrome/**/*', $.zip('chrome.zip'), './dist')
return pipe('./tmp/chrome/**/*', $.zip('chrome.zip'), './dist')
})
gulp.task('chrome:_crx', (cb) => {
$.run('"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"' +
' --pack-extension=' + path.join(__dirname, './tmp/chrome') +
' --pack-extension-key=' + path.join(process.env.HOME, '.ssh/chrome.pem')
).exec(cb)
})
gulp.task('chrome:crx', () => {
// This will package the crx using a private key.
// For the convenience of people who want to build locally without having to
// manage their own Chrome key, this code will use the bundled test key if
// a real key is not found in ~/.ssh.
const real = path.join(os.homedir() + '.ssh/chrome.pem')
const test = './chrome_test_key.pem'
const privateKey = fs.existsSync(real) ? fs.readFileSync(real) : fs.readFileSync(test)
gulp.task('chrome:crx', ['chrome:_crx'], () => {
return pipe('./tmp/chrome.crx', './dist')
return pipe('./tmp/chrome', $.crxPack({
privateKey: privateKey,
filename: 'chrome.crx'
}), './dist')
})
// Opera
gulp.task('opera', ['chrome'], () => {
return pipe('./tmp/chrome/**/*', './tmp/opera')
return pipe('./tmp/chrome/**/*', './tmp/opera')
})
gulp.task('opera:nex', () => {
return pipe('./dist/chrome.crx', $.rename('opera.nex'), './dist')
return pipe('./dist/chrome.crx', $.rename('opera.nex'), './dist')
})
// Firefox
gulp.task('firefox:template', () => {
return buildTemplate({ FIREFOX: true })
return buildTemplate({SUPPORT_FILE_ICONS: true})
})
gulp.task('firefox:js', ['firefox:template', 'lib:ondemand'], () => {
return buildJs([], { FIREFOX: true })
return buildJs([], {SUPPORT_FILE_ICONS: true})
})
gulp.task('firefox', ['firefox:js'], () => {
return merge(
pipe('./icons/**/*', './tmp/firefox/data/icons'),
pipe(['./libs/**/*', '!./libs/ondemand{,/**}', './tmp/octotree.*', './tmp/ondemand.js'], './tmp/firefox/data'),
pipe('./src/config/firefox/firefox.js', $.babel(), './tmp/firefox/lib'),
pipe('./src/config/firefox/package.json', $.replace('$VERSION', version), './tmp/firefox')
)
return merge(
pipe('./icons/**/*', './tmp/firefox/icons'),
pipe(['./libs/**/*', '!./libs/ondemand{,/**}', './tmp/octotree.*', './tmp/ondemand.js'], './tmp/firefox'),
pipe('./libs/file-icons.css', $.replace('../fonts', 'moz-extension://__MSG_@@extension_id__/fonts'), './tmp/firefox/'),
pipe('./src/config/firefox/background.js', $.babel(), './tmp/firefox/'),
pipe('./src/config/firefox/manifest.json', $.replace('$VERSION', getVersion()), './tmp/firefox')
)
})
gulp.task('firefox:xpi', (cb) => {
$.run('cd ./tmp/firefox && ../../node_modules/.bin/jpm xpi && mkdir -p ../../dist && mv GitCodeTree.xpi ../../dist/firefox.xpi').exec(cb)
gulp.task('firefox:zip', () => {
return pipe('./tmp/firefox/**/*', $.zip('firefox.zip'), './dist')
})
// Safari
gulp.task('safari:template', () => {
return buildTemplate({ SAFARI: true })
return buildTemplate({})
})
gulp.task('safari:js', ['safari:template', 'lib:ondemand'], () => {
return buildJs([], { SAFARI: true })
return buildJs([], {})
})
gulp.task('safari', ['safari:js'], () => {
return merge(
pipe('./icons/icon64.png', $.rename('Icon-64.png'), './tmp/safari/octotree.safariextension'),
pipe(
['./libs/**/*', '!./libs/ondemand{,/**}', './tmp/octotree.*', './tmp/ondemand.js'],
'./tmp/safari/octotree.safariextension/'
),
pipe('./src/config/safari/Info.plist', $.replace('$VERSION', version), './tmp/safari/octotree.safariextension')
)
return merge(
pipe('./icons/icon64.png', $.rename('Icon-64.png'), './tmp/safari/octotree.safariextension'),
pipe(
['./libs/**/*', '!./libs/ondemand{,/**}', './tmp/octotree.*', './tmp/ondemand.js'],
'./tmp/safari/octotree.safariextension/'
),
pipe('./src/config/safari/Info.plist', $.replace('$VERSION', getVersion()), './tmp/safari/octotree.safariextension')
)
})
// Helpers
function pipe(src, ...transforms) {
return transforms.reduce((stream, transform) => {
const isDest = typeof transform === 'string'
return stream.pipe(isDest ? gulp.dest(transform) : transform)
}, gulp.src(src))
return transforms.reduce((stream, transform) => {
const isDest = typeof transform === 'string'
return stream.pipe(isDest ? gulp.dest(transform) : transform)
}, gulp.src(src))
}
function html2js(template) {
return map(escape)
return map(escape)
function escape(file, cb) {
const path = $.util.replaceExtension(file.path, '.js')
const content = file.contents.toString()
const escaped = content
.replace(/\\/g, "\\\\")
.replace(/'/g, "\\'")
.replace(/\r?\n/g, "\\n' +\n '")
const body = template.replace('$$', escaped)
function escape(file, cb) {
const path = $.util.replaceExtension(file.path, '.js')
const content = file.contents.toString()
const escaped = content
.replace(/\\/g, "\\\\")
.replace(/'/g, "\\'")
.replace(/\r?\n/g, "\\n' +\n '")
const body = template.replace('$$', escaped)
file.path = path
file.contents = new Buffer(body)
cb(null, file)
}
file.path = path
file.contents = new Buffer(body)
cb(null, file)
}
}
function buildJs(overrides, ctx) {
const src = [
'./tmp/template.js',
'./src/constants.js',
'./src/adapters/adapter.js',
'./src/adapters/bitbucket.js',
'./src/adapters/github.js',
'./src/adapters/oschina.js',
'./src/view.help.js',
'./src/view.error.js',
'./src/view.tree.js',
'./src/view.options.js',
'./src/util.location.js',
'./src/util.module.js',
'./src/util.async.js',
'./src/util.storage.js'
].concat(overrides)
.concat('./src/octotree.js')
const src = [
'./tmp/template.js',
'./src/constants.js',
'./src/adapters/adapter.js',
'./src/adapters/pjax.js',
'./src/adapters/bitbucket.js',
'./src/adapters/github.js',
'./src/adapters/oschina.js',
'./src/view.help.js',
'./src/view.error.js',
'./src/view.tree.js',
'./src/view.options.js',
'./src/util.location.js',
'./src/util.module.js',
'./src/util.async.js',
'./src/util.storage.js'
].concat(overrides)
.concat('./src/octotree.js')
return pipe(
src,
$.babel(),
$.concat('octotree.js'),
$.preprocess({ context: ctx }),
'./tmp'
)
return pipe(
src,
$.babel(),
$.concat('octotree.js'),
$.preprocess({context: ctx}),
'./tmp'
)
}
function buildTemplate(ctx) {
const LOTS_OF_SPACES = new Array(500).join(' ')
const LOTS_OF_SPACES = new Array(500).join(' ')
return pipe(
'./src/template.html',
$.preprocess({ context: ctx }),
$.replace('__SPACES__', LOTS_OF_SPACES),
html2js('const TEMPLATE = \'$$\''),
'./tmp'
)
return pipe(
'./src/template.html',
$.preprocess({ context: ctx }),
$.replace('__SPACES__', LOTS_OF_SPACES),
html2js('const TEMPLATE = \'$$\''),
'./tmp'
)
}
function getVersion() {
delete require.cache[require.resolve('./package.json')]
return require('./package.json').version
}

642
libs/file-icons.css Normal file
View File

@ -0,0 +1,642 @@
/*
| File Icons
| @link https://github.com/file-icons
| @author Daniel Brooker https://github.com/DanBrooker
*/
/* ----------------------------[ Colors ]---------------------------------- */
/*============================================================================*
PALETTE
Base16 colours from https://github.com/chriskempson/base16
/*============================================================================*/
.light-red:before {
color: #c97071;
}
.medium-red:before {
color: #ac4142;
}
.dark-red:before {
color: #742c2d;
}
.light-green:before {
color: #a6ba7b;
}
.medium-green:before {
color: #90a959;
}
.dark-green:before {
color: #66783e;
}
.light-yellow:before {
color: #fae0bc;
}
.medium-yellow:before {
color: #ee9e2e;
}
.dark-yellow:before {
color: #d88511;
}
.light-blue:before {
color: #6098b0;
}
.medium-blue:before {
color: #6a9fb5;
}
.dark-blue:before {
color: #46788d;
}
.light-maroon:before {
color: #be7953;
}
.medium-maroon:before {
color: #8f5536;
}
.dark-maroon:before {
color: #573421;
}
.light-purple:before {
color: #c7a4c0;
}
.medium-purple:before {
color: #aa759f;
}
.dark-purple:before {
color: #825078;
}
.light-orange:before {
color: #d99762;
}
.medium-orange:before {
color: #d28445;
}
.dark-orange:before {
color: #a35f27;
}
.light-cyan:before {
color: #6bb0a4;
}
.medium-cyan:before {
color: #75b5aa;
}
.dark-cyan:before {
color: #4d9085;
}
.light-pink:before {
color: #ff4ddb;
}
.medium-pink:before {
color: #ff00cc;
}
.dark-pink:before {
color: #b3008f;
}
.theme-colour-check {
background: #ffffff;
}
/* ----------------------------[ Fonts ]---------------------------------- */
@font-face {
font-family: FontAwesome;
font-weight: normal;
font-style: normal;
src: url("../fonts/fontawesome.woff2");
}
@font-face {
font-family: Mfizz;
src: url("../fonts/mfixx.woff2");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: Devicons;
src: url("../fonts/devopicons.woff2");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: file-icons;
src: url("../fonts/file-icons.woff2");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: octicons;
src: url("../fonts/octicons.woff2");
font-weight: normal;
font-style: normal;
}
/* ----------------------------[ Icons ]---------------------------------- */
.icon:before{
font-weight: normal;
font-style: normal;
text-align: center;
width: 16px;
line-height: 1;
position: relative;
display: inline-block;
-webkit-font-smoothing: antialiased;
}
/*============================================================================*
Octicons
https://github.com/github/octicons
/*============================================================================*/
.binary-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f094"; }
.book-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f007"; }
.brew-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f069"; font-size: 15px; left: 1px; }
.checklist-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f076"; font-size: 17px; left: 1px; }
.code-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f05f"; }
.database-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f096"; }
.gear-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f02f"; }
.git-commit-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f01f"; }
.git-merge-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f023"; }
.github-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f00a"; }
.graph-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f043"; }
.image-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f012"; }
.key-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f049"; }
.link-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f0b0"; }
.markdown-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f0c9"; }
.package-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f0c4"; }
.ruby-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f047"; }
.secret-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f08c"; }
.squirrel-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f0b2"; font-size: 15px; }
.text-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f011"; }
.zip-icon:before { font-family: octicons; font-size: 16px; top: 1px; content: "\f013"; }
/*============================================================================*
FontAwesome
http://fortawesome.github.io/Font-Awesome/cheatsheet
/*============================================================================*/
.android-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f17b"; font-size: 16px; top: 1px; }
.at-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f1fa"; font-size: 15px; top: 1px; }
.audio-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f028"; font-size: 15px; top: 1px; }
.bullhorn-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f0a1"; font-size: 16px; top: 2px; }
.calc-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f1ec"; font-size: 14px; }
.coffee-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f0f4"; font-size: 14px; top: 1px; }
.css3-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f13c"; top: 0; }
.circle-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f111"; font-size: 16px; top: 1px; }
.earth-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f0ac"; font-size: 15px; }
.gears-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f085"; font-size: 15px; }
.html5-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f13b"; font-size: 15px; top: 1px; }
.mobile-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f10b"; font-size: 20px; top: 2px; }
.moon-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f186"; font-size: 16px; top: 1px; }
.music-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f001"; font-size: 15px; }
.print-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f02f"; font-size: 15px; top: 2px; }
.recycle-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f1b8"; font-size: 15px; top: 2px; }
.rss-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f143"; font-size: 16px; top: 2px; }
.smarty-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f0eb"; font-size: 15px; }
.sourcemap-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f279"; font-size: 14px; }
.sun-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f185"; font-size: 14px; -webkit-font-smoothing: subpixel-antialiased; }
.toc-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f03a"; font-size: 15px; top: 2px; }
.twig-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f1bb"; font-size: 14px; }
.pdf-icon:before { font-family: FontAwesome; font-size: 13px; content: "\f1c1"; font-size: 14px; }
/*============================================================================*
Mfizz
http://mfizz.com/oss/font-mfizz
/*============================================================================*/
.apache-icon:before { font-family: Mfizz; font-size: 14px; content: "\f102"; top: 3px; font-size: 15px; }
.archlinux-icon:before { font-family: Mfizz; font-size: 14px; content: "A"; top: 1px; font-size: 15px; }
.c-icon:before { font-family: Mfizz; font-size: 14px; content: "\f106"; top: 1px; font-size: 13px; }
.cpp-icon:before { font-family: Mfizz; font-size: 14px; content: "\f10b"; top: 1px; }
.csharp-icon:before { font-family: Mfizz; font-size: 14px; content: "\f10c"; top: 1px; }
.debian-icon:before { font-family: Mfizz; font-size: 14px; content: "\f111"; top: 1px; }
.elixir-icon:before { font-family: Mfizz; font-size: 14px; content: "\f113"; top: 1px; }
.gnome-icon:before { font-family: Mfizz; font-size: 14px; content: "\f119"; top: 1px; }
.haskell-icon:before { font-family: Mfizz; font-size: 14px; content: "\f121"; top: 2px; font-size: 16px; }
.java-icon:before { font-family: Mfizz; font-size: 14px; content: "\f126"; top: 2px; font-size: 16px; }
.js-icon:before { font-family: Mfizz; font-size: 14px; content: "\f129"; top: 1px; font-size: 14px; }
.msql-icon:before { font-family: Mfizz; font-size: 14px; content: "\f136"; top: 2px; font-size: 15px; text-shadow: 0 0 0; }
.objc-icon:before { font-family: Mfizz; font-size: 14px; content: "\f13e"; top: 2px; font-size: 16px; }
.osx-icon:before { font-family: Mfizz; font-size: 14px; content: "\f141"; top: 1px; }
.perl-icon:before { font-family: Mfizz; font-size: 14px; content: "\f142"; top: 1px; }
.python-icon:before { font-family: Mfizz; font-size: 14px; content: "\f14c"; top: 1px; }
.red-hat-icon:before { font-family: Mfizz; font-size: 14px; content: "\f14e"; top: 2px; }
.scala-icon:before { font-family: Mfizz; font-size: 14px; content: "\f154"; top: 1px; }
.sql-icon:before { font-family: Mfizz; font-size: 14px; content: "\f10e"; top: 1px; }
.svg-icon:before { font-family: Mfizz; font-size: 14px; content: "\f15c"; top: 1px; }
.tt-icon:before { font-family: Mfizz; font-size: 14px; content: "TT"; }
.x11-icon:before { font-family: Mfizz; font-size: 14px; content: "\f16e"; top: 1px; font-size: 13px; }
/*============================================================================*
Devicons
http://vorillaz.github.io/devicons
/*============================================================================*/
.angular-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e653"; }
.appcelerator-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e6ab"; }
.appstore-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e613"; }
.asp-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e67f"; }
.atom-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e664"; -webkit-font-smoothing: subpixel-antialiased; }
.backbone-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e652"; }
.bootstrap-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e647"; font-size: 15px; top: 2px; }
.bower-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e64d"; text-shadow: 0 0 0; }
.chrome-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e643"; }
.clojure-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e668"; -webkit-font-smoothing: subpixel-antialiased; }
.compass-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e661"; font-size: 14px; top: 2px; }
.dart-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e698"; font-size: 15px; top: 2px; }
.dlang-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e6af"; }
.dojo-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e61c"; font-size: 16px; top: 4px; transform: scale(1.2); -webkit-font-smoothing: subpixel-antialiased; }
.dropbox-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e607"; }
.eclipse-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e69e"; }
.erlang-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e6b1"; }
.extjs-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e68e"; }
.fsharp-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e6a7"; left: 1px; top: 2px; }
.git-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e602"; font-size: 15px; top: 2px; }
.heroku-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e67b"; }
.jquery-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e650"; font-size: 15px; top: 2px; }
.jqueryui-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e654"; font-size: 15px; top: 2px; }
.laravel-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e63f"; -webkit-font-smoothing: subpixel-antialiased; }
.materialize-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e6b6"; transform: scale(1.2); -webkit-font-smoothing: subpixel-antialiased; }
.modernizr-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e620"; }
.mootools-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e68f"; text-shadow: 0 0 0; }
.node-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e618"; }
.pod-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e669"; font-size: 15px; top: 2px; }
.prolog-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e6a1"; }
.rails-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e63b"; }
.raphael-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e65f"; font-size: 15px; }
.requirejs-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e670"; }
.rust-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e6a8"; }
.sass-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e64b"; }
.sencha-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e68c"; }
.snapsvg-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e65e"; }
.swift-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e655"; left: -1px; }
.travis-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e67e"; font-size: 15px; top: 2px; }
.uikit-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e673"; font-size: 15px; top: 2px; }
.unity3d-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e621"; }
.vim-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e6c5"; }
.vs-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e60c"; font-size: 14px; top: 2px; }
.windows-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e60f"; font-size: 14px; top: 2px; }
.yeoman-icon:before { font-family: Devicons; font-size: 16px; top: 3px; content: "\e67a"; }
/*============================================================================*
Custom file icons
See https://github.com/file-icons/source/#adding-new-icons
/*============================================================================*/
._1c-icon:before { font-family: file-icons; font-size: 15px; content: "\a5ea"; top: 3px; font-size: 16px; }
._1c-alt-icon:before { font-family: file-icons; font-size: 15px; content: "\ea28"; top: 3px; font-size: 16px; }
.abap-icon:before { font-family: file-icons; font-size: 15px; content: "\e92b"; top: 2px; }
.access-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ea"; top: 2px; }
.ada-icon:before { font-family: file-icons; font-size: 15px; content: "\e90b"; top: 3px; font-size: 17px; }
.ae-icon:before { font-family: file-icons; font-size: 15px; content: "\e9f3"; top: 2px; }
.ahk-icon:before { font-family: file-icons; font-size: 15px; content: "\e932"; top: 2px; }
.ai-icon:before { font-family: file-icons; font-size: 15px; content: "\e6b4"; top: 2px; }
.alloy-icon:before { font-family: file-icons; font-size: 15px; content: "\e935"; top: 2px; }
.alpine-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ff"; top: 2px; font-size: 16px; }
.ampl-icon:before { font-family: file-icons; font-size: 15px; content: "\e94e"; top: 3px; font-size: 16px; left: 1px; }
.amx-icon:before { font-family: file-icons; font-size: 15px; content: "\e99b"; top: 3px; font-size: 16px; }
.ant-icon:before { font-family: file-icons; font-size: 15px; content: "\e93e"; top: 4px; font-size: 18px; transform: scale(1.1); }
.antlr-icon:before { font-family: file-icons; font-size: 15px; content: "\e92c"; top: 3px; }
.api-icon:before { font-family: file-icons; font-size: 15px; content: "\e92d"; top: 2px; }
.apl-icon:before { font-family: file-icons; font-size: 15px; content: "\234b"; top: 2px; }
.apple-icon:before { font-family: file-icons; font-size: 15px; content: "\e925"; top: 1px; }
.appveyor-icon:before { font-family: file-icons; font-size: 15px; content: "\e923"; top: 2px; }
.arc-icon:before { font-family: file-icons; font-size: 15px; content: "\e92f"; top: 2px; }
.arduino-icon:before { font-family: file-icons; font-size: 15px; content: "\e930"; top: 3px; font-size: 16px; }
.arttext-icon:before { font-family: file-icons; font-size: 15px; content: "\24d0"; top: 2px; }
.as-icon:before { font-family: file-icons; font-size: 15px; content: "\e92e"; top: 1px; font-size: 14px; }
.asciidoc-icon:before { font-family: file-icons; font-size: 15px; content: "\e918"; top: 1px; font-size: 14px; }
.ats-icon:before { font-family: file-icons; font-size: 15px; content: "\e934"; top: 2px; }
.audacity-icon:before { font-family: file-icons; font-size: 15px; content: "\e9f9"; top: 2px; }
.augeas-icon:before { font-family: file-icons; font-size: 15px; content: "\e931"; top: 2px; }
.autoit-icon:before { font-family: file-icons; font-size: 15px; content: "\e933"; top: 2px; font-size: 16px; }
.babel-icon:before { font-family: file-icons; font-size: 15px; content: "\e91f"; top: 2px; left: 1px; }
.bibtex-icon:before { font-family: file-icons; font-size: 15px; content: "\e601"; top: 2px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; }
.blender-icon:before { font-family: file-icons; font-size: 15px; content: "\e9fa"; top: 2px; }
.bluespec-icon:before { font-family: file-icons; font-size: 15px; content: "\e93c"; top: 1px; font-size: 13px; left: 1px; }
.boo-icon:before { font-family: file-icons; font-size: 15px; content: "\e939"; top: 2px; }
.boot-icon:before { font-family: file-icons; font-size: 15px; content: "\f103"; top: 2px; font-size: 16px; }
.brain-icon:before { font-family: file-icons; font-size: 15px; content: "\e93a"; top: 2px; }
.brakeman-icon:before { font-family: file-icons; font-size: 15px; content: "\e9d6"; top: 2px; }
.bro-icon:before { font-family: file-icons; font-size: 15px; content: "\e93b"; top: 3px; font-size: 16px; }
.broccoli-icon:before { font-family: file-icons; font-size: 15px; content: "\e922"; top: 1px; font-size: 14px; }
.byond-icon:before { font-family: file-icons; font-size: 15px; content: "\e962"; top: 2px; }
.cabal-icon:before { font-family: file-icons; font-size: 15px; content: "\e9c2"; top: 2px; }
.cake-icon:before { font-family: file-icons; font-size: 15px; content: "\e9e3"; top: 2px; }
.cakefile-icon:before { font-family: file-icons; font-size: 15px; content: "\e924"; top: 2px; }
.cakephp-icon:before { font-family: file-icons; font-size: 15px; content: "\e9d3"; top: 1px; font-size: 14px; }
.cc-icon:before { font-family: file-icons; font-size: 15px; content: "\e9d5"; top: 2px; font-size: 16px; }
.ceylon-icon:before { font-family: file-icons; font-size: 15px; content: "\e94f"; top: 2px; }
.cf-icon:before { font-family: file-icons; font-size: 15px; content: "\e929"; top: 2px; }
.chai-icon:before { font-family: file-icons; font-size: 15px; content: "c"; top: 3px; font-size: 16px; }
.chapel-icon:before { font-family: file-icons; font-size: 15px; content: "\e950"; top: 2px; }
.chartjs-icon:before { font-family: file-icons; font-size: 15px; content: "\ea0b"; top: 2px; }
.chuck-icon:before { font-family: file-icons; font-size: 15px; content: "\e943"; top: 2px; }
.circleci-icon:before { font-family: file-icons; font-size: 15px; content: "\ea12"; top: 2px; font-size: 14px; }
.cirru-icon:before { font-family: file-icons; font-size: 15px; content: "\e951"; top: 2px; text-shadow: 0 0 0; }
.cl-icon:before { font-family: file-icons; font-size: 15px; content: "\e972"; top: 2px; text-shadow: 0 0 0; }
.clarion-icon:before { font-family: file-icons; font-size: 15px; content: "\e952"; top: 1px; font-size: 14px; left: 1px; }
.clean-icon:before { font-family: file-icons; font-size: 15px; content: "\e95b"; top: 2px; font-size: 16px; }
.click-icon:before { font-family: file-icons; font-size: 15px; content: "\e95c"; top: 2px; }
.clips-icon:before { font-family: file-icons; font-size: 15px; content: "\e940"; top: 3px; font-size: 18px; }
.cljs-icon:before { font-family: file-icons; font-size: 15px; content: "\f104"; top: 2px; }
.cmake-icon:before { font-family: file-icons; font-size: 15px; content: "\e93f"; top: 1px; font-size: 14px; }
.codecov-icon:before { font-family: file-icons; font-size: 15px; content: "\2602"; top: 2px; }
.composer-icon:before { font-family: file-icons; font-size: 15px; content: "\e683"; top: 3px; font-size: 17px; }
.config-icon:before { font-family: file-icons; font-size: 15px; content: "\f07c"; top: 2px; font-size: 14px; }
.cordova-icon:before { font-family: file-icons; font-size: 15px; content: "\ea11"; top: 2px; }
.coq-icon:before { font-family: file-icons; font-size: 15px; content: "\e95f"; top: 2px; font-size: 16px; left: 1px; }
.cp-icon:before { font-family: file-icons; font-size: 15px; content: "\e942"; top: 3px; font-size: 17px; }
.creole-icon:before { font-family: file-icons; font-size: 15px; content: "\e95e"; top: 2px; }
.crystal-icon:before { font-family: file-icons; font-size: 15px; content: "\e902"; top: 2px; left: 1px; }
.csound-icon:before { font-family: file-icons; font-size: 15px; content: "\e9f0"; top: 2px; }
.csscript-icon:before { font-family: file-icons; font-size: 15px; content: "\e9e2"; top: 2px; }
.cucumber-icon:before { font-family: file-icons; font-size: 15px; content: "\f02b"; top: 3px; }
.cython-icon:before { font-family: file-icons; font-size: 15px; content: "\e963"; top: 2px; }
.d3-icon:before { font-family: file-icons; font-size: 15px; content: "\ea10"; top: 2px; }
.darcs-icon:before { font-family: file-icons; font-size: 15px; content: "\e964"; top: 2px; }
.dashboard-icon:before { font-family: file-icons; font-size: 15px; content: "\f07d"; top: 2px; font-size: 13px; }
.dbase-icon:before { font-family: file-icons; font-size: 15px; content: "\e9f1"; top: 2px; }
.default-icon:before { font-family: file-icons; font-size: 15px; content: "\1f5cc";top: 2px; font-size: 14px; }
.diff-icon:before { font-family: file-icons; font-size: 15px; content: "\e960"; top: 2px; }
.docker-icon:before { font-family: file-icons; font-size: 15px; content: "\f106"; top: 3px; font-size: 18px; }
.doxygen-icon:before { font-family: file-icons; font-size: 15px; content: "\e928"; top: 1px; font-size: 13px; }
.doge-icon:before { font-family: file-icons; font-size: 15px; content: "\e946"; top: 2px; }
.dyalog-icon:before { font-family: file-icons; font-size: 15px; content: "\e90c"; top: 1px; font-size: 14px; left: 1px; }
.dylib-icon:before { font-family: file-icons; font-size: 15px; content: "\ea15"; top: 2px; }
.e-icon:before { font-family: file-icons; font-size: 15px; content: "E"; top: 1px; font-size: 14px; }
.eagle-icon:before { font-family: file-icons; font-size: 15px; content: "\e965"; top: 2px; }
.ec-icon:before { font-family: file-icons; font-size: 15px; content: "\e9c9"; top: 2px; }
.ecere-icon:before { font-family: file-icons; font-size: 15px; content: "\e966"; top: 3px; font-size: 16px; }
.editorconfig-icon:before { font-family: file-icons; font-size: 15px; content: "\ea1b"; top: 3px; }
.eiffel-icon:before { font-family: file-icons; font-size: 15px; content: "\e967"; top: 2px; font-size: 16px; }
.electron-icon:before { font-family: file-icons; font-size: 15px; content: "\ea27"; top: 3px; font-size: 16px; text-shadow: 0 0 0; }
.elm-icon:before { font-family: file-icons; font-size: 15px; content: "\f102"; top: 2px; }
.em-icon:before { font-family: file-icons; font-size: 15px; content: "\e968"; top: 3px; font-size: 16px; }
.ember-icon:before { font-family: file-icons; font-size: 15px; content: "\e61b"; top: 2px; font-size: 14px; }
.emacs-icon:before { font-family: file-icons; font-size: 15px; content: "\e926"; top: 2px; }
.eq-icon:before { font-family: file-icons; font-size: 15px; content: "\ea0a"; top: 5px; }
.eslint-icon:before { font-family: file-icons; font-size: 15px; content: "\ea0f"; top: 3px; font-size: 16px; }
.excel-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ee"; top: 2px; }
.fabfile-icon:before { font-family: file-icons; font-size: 15px; content: "\e94b"; top: 2px; font-size: 16px; }
.factor-icon:before { font-family: file-icons; font-size: 15px; content: "\e96a"; top: 3px; font-size: 18px; left: -2px; transform: scale(1.2); }
.fancy-icon:before { font-family: file-icons; font-size: 15px; content: "\e96b"; top: 2px; font-size: 16px; }
.fantom-icon:before { font-family: file-icons; font-size: 15px; content: "\e96f"; top: 2px; left: 1px; }
.fbx-icon:before { font-family: file-icons; font-size: 15px; content: "\e9fc"; top: 2px; }
.ff-icon:before { font-family: file-icons; font-size: 15px; content: "\fb00"; top: 3px; }
.finder-icon:before { font-family: file-icons; font-size: 15px; content: "\e9e9"; top: 3px; font-size: 16px; }
.flow-icon:before { font-family: file-icons; font-size: 15px; content: "\e921"; top: 1px; }
.flux-icon:before { font-family: file-icons; font-size: 15px; content: "\e969"; top: 2px; }
.font-icon:before { font-family: file-icons; font-size: 15px; content: "\e90f"; top: 1px; font-size: 14px; left: 1px; }
.fortran-icon:before { font-family: file-icons; font-size: 15px; content: "\e90a"; top: 1px; font-size: 14px; left: 1px; }
.freemarker-icon:before { font-family: file-icons; font-size: 15px; content: "\e970"; top: 2px; font-size: 16px; left: 1px; }
.frege-icon:before { font-family: file-icons; font-size: 15px; content: "\e96e"; top: 2px; font-size: 16px; left: 1px; }
.fuelux-icon:before { font-family: file-icons; font-size: 15px; content: "\ea09"; top: 3px; font-size: 16px; left: 2px; transform: scale(1.15); text-shadow: 0 0 0; }
.gams-icon:before { font-family: file-icons; font-size: 15px; content: "\e973"; top: 2px; left: 1px; }
.gap-icon:before { font-family: file-icons; font-size: 15px; content: "\e971"; top: 3px; font-size: 16px; left: 1px; }
.gdb-icon:before { font-family: file-icons; font-size: 15px; content: "\ea08"; top: 3px; font-size: 16px; transform: scale(1.15); text-shadow: 0 0 0; }
.genshi-icon:before { font-family: file-icons; font-size: 15px; content: "\e976"; top: 3px; }
.gentoo-icon:before { font-family: file-icons; font-size: 15px; content: "\e96d"; top: 1px; font-size: 14px; left: 1px; }
.gf-icon:before { font-family: file-icons; font-size: 15px; content: "\e978"; top: 2px; }
.glade-icon:before { font-family: file-icons; font-size: 15px; content: "\e938"; top: 2px; }
.glyphs-icon:before { font-family: file-icons; font-size: 15px; content: "G"; top: 3px; }
.gml-icon:before { font-family: file-icons; font-size: 15px; content: "\e975"; top: 3px; font-size: 16px; }
.gn-icon:before { font-family: file-icons; font-size: 15px; content: "\ea25"; top: 2px; }
.gnu-icon:before { font-family: file-icons; font-size: 15px; content: "\e679"; top: 2px; font-size: 16px; text-shadow: 0 0 0; }
.go-icon:before { font-family: file-icons; font-size: 15px; content: "\e624"; top: 3px; }
.godot-icon:before { font-family: file-icons; font-size: 15px; content: "\e974"; top: 2px; }
.golo-icon:before { font-family: file-icons; font-size: 15px; content: "\e979"; top: 2px; }
.gosu-icon:before { font-family: file-icons; font-size: 15px; content: "\e97a"; top: 2px; }
.gradle-icon:before { font-family: file-icons; font-size: 15px; content: "\e903"; top: 3px; font-size: 16px; left: 1px; }
.graphql-icon:before { font-family: file-icons; font-size: 15px; content: "\e97c"; top: 2px; }
.graphviz-icon:before { font-family: file-icons; font-size: 15px; content: "\e97d"; top: 4px; font-size: 17px; left: 1px; }
.groovy-icon:before { font-family: file-icons; font-size: 15px; content: "\e904"; top: 4px; font-size: 17px; left: -1px; }
.grunt-icon:before { font-family: file-icons; font-size: 15px; content: "\e611"; top: 1px; font-size: 14px; }
.gulp-icon:before { font-family: file-icons; font-size: 15px; content: "\e610"; top: 2px; font-size: 16px; }
.hack-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ce"; top: 2px; }
.haml-icon:before { font-family: file-icons; font-size: 15px; content: "\f15b"; top: 2px; }
.harbour-icon:before { font-family: file-icons; font-size: 15px; content: "\e97b"; top: 2px; font-size: 16px; text-shadow: 0 0 0; }
.hashicorp-icon:before { font-family: file-icons; font-size: 15px; content: "\e97e"; top: 2px; }
.haxe-icon:before { font-family: file-icons; font-size: 15px; content: "\e907"; top: 2px; }
.hy-icon:before { font-family: file-icons; font-size: 15px; content: "\e97f"; top: 2px; }
.idl-icon:before { font-family: file-icons; font-size: 15px; content: "\e947"; top: 3px; font-size: 18px; }
.idris-icon:before { font-family: file-icons; font-size: 15px; content: "\e983"; top: 2px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; }
.igorpro-icon:before { font-family: file-icons; font-size: 15px; content: "\e980"; top: 2px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; }
.indesign-icon:before { font-family: file-icons; font-size: 15px; content: "\e9f4"; top: 2px; }
.inform7-icon:before { font-family: file-icons; font-size: 15px; content: "\e984"; top: 2px; font-size: 16px; text-shadow: 0 0 0; }
.inno-icon:before { font-family: file-icons; font-size: 15px; content: "\e985"; top: 2px; }
.io-icon:before { font-family: file-icons; font-size: 15px; content: "\e981"; top: 1px; font-size: 13px; -webkit-font-smoothing: subpixel-antialiased; }
.ioke-icon:before { font-family: file-icons; font-size: 15px; content: "\e982"; top: 2px; }
.ionic-icon:before { font-family: file-icons; font-size: 15px; content: "\f14b"; top: 2px; }
.isabelle-icon:before { font-family: file-icons; font-size: 15px; content: "\e945"; top: 2px; font-size: 16px; }
.j-icon:before { font-family: file-icons; font-size: 15px; content: "\e937"; top: 1px; font-size: 13px; }
.jade-icon:before { font-family: file-icons; font-size: 15px; content: "\e90d"; top: 1px; font-size: 14px; }
.jake-icon:before { font-family: file-icons; font-size: 15px; content: "\e948"; top: 3px; font-size: 16px; }
.jenkins-icon:before { font-family: file-icons; font-size: 15px; content: "\e667"; top: 3px; font-size: 18px; text-shadow: 0 0 0; }
.jinja-icon:before { font-family: file-icons; font-size: 15px; content: "\e944"; top: 2px; }
.jsonld-icon:before { font-family: file-icons; font-size: 15px; content: "\e958"; top: 3px; font-size: 17px; }
.jsx-icon:before { font-family: file-icons; font-size: 15px; content: "\e9e6"; top: 1px; font-size: 14px; }
.julia-icon:before { font-family: file-icons; font-size: 15px; content: "\26ec"; top: 1px; font-size: 14px; }
.jupyter-icon:before { font-family: file-icons; font-size: 15px; content: "\e987"; top: 3px; font-size: 16px; }
.karma-icon:before { font-family: file-icons; font-size: 15px; content: "\e9cd"; top: 2px; }
.keynote-icon:before { font-family: file-icons; font-size: 15px; content: "\e9e5"; top: 2px; }
.khronos-icon:before { font-family: file-icons; font-size: 15px; content: "\e9f8"; top: 2px; }
.kivy-icon:before { font-family: file-icons; font-size: 15px; content: "\e901"; top: 2px; }
.knockout-icon:before { font-family: file-icons; font-size: 15px; content: "\4B"; top: 2px; }
.kotlin-icon:before { font-family: file-icons; font-size: 15px; content: "\e989"; top: 1px; font-size: 14px; }
.krl-icon:before { font-family: file-icons; font-size: 15px; content: "\e988"; top: 1px; font-size: 14px; }
.labview-icon:before { font-family: file-icons; font-size: 15px; content: "\e98a"; top: 2px; font-size: 16px; }
.lasso-icon:before { font-family: file-icons; font-size: 15px; content: "\e98c"; top: 2px; left: 1px; }
.leaflet-icon:before { font-family: file-icons; font-size: 15px; content: "\ea07"; top: 2px; }
.lean-icon:before { font-family: file-icons; font-size: 15px; content: "L"; top: 1px; font-size: 13px; }
.lein-icon:before { font-family: file-icons; font-size: 15px; content: "\f105"; top: 3px; font-size: 16px; text-shadow: 0 0 0; transform: scale(1.15); }
.lfe-icon:before { font-family: file-icons; font-size: 15px; content: "\e94c"; top: 2px; font-size: 16px; }
.lightwave-icon:before { font-family: file-icons; font-size: 15px; content: "\e9fb"; top: 2px; }
.lisp-icon:before { font-family: file-icons; font-size: 15px; content: "\e908"; top: 3px; font-size: 17px; }
.llvm-icon:before { font-family: file-icons; font-size: 15px; content: "\e91d"; top: 3px; font-size: 17px; }
.logtalk-icon:before { font-family: file-icons; font-size: 15px; content: "\e98d"; top: 2px; text-shadow: 0 0 0; }
.lookml-icon:before { font-family: file-icons; font-size: 15px; content: "\e98e"; top: 2px; font-size: 16px; text-shadow: 0 0 0; }
.ls-icon:before { font-family: file-icons; font-size: 15px; content: "\e914"; top: 2px; font-size: 14px; }
.lsl-icon:before { font-family: file-icons; font-size: 15px; content: "\e98b"; top: 1px; }
.lua-icon:before { font-family: file-icons; font-size: 15px; content: "\e91b"; top: 2px; font-size: 14px; }
.mako-icon:before { font-family: file-icons; font-size: 15px; content: "\e98f"; top: 4px; font-size: 16px; }
.mapbox-icon:before { font-family: file-icons; font-size: 15px; content: "\e941"; top: 1px; font-size: 13px; }
.marko-icon:before { font-family: file-icons; font-size: 15px; content: "\e920"; top: 4px; font-size: 18px; left: -1px; transform: scale(1.05); }
.mathematica-icon:before { font-family: file-icons; font-size: 15px; content: "\e990"; top: 2px; font-size: 16px; }
.mathjax-icon:before { font-family: file-icons; font-size: 15px; content: "\ea06"; top: 2px; }
.matlab-icon:before { font-family: file-icons; font-size: 15px; content: "\e991"; top: 2px; }
.max-icon:before { font-family: file-icons; font-size: 15px; content: "\e993"; top: 2px; }
.maxscript-icon:before { font-family: file-icons; font-size: 15px; content: "\e900"; top: 2px; }
.maya-icon:before { font-family: file-icons; font-size: 15px; content: "\e9f6"; top: 2px; font-size: 16px; }
.manpage-icon:before { font-family: file-icons; font-size: 15px; content: "\e936"; top: 3px; }
.mediawiki-icon:before { font-family: file-icons; font-size: 15px; content: "\e954"; top: 2px; font-size: 16px; }
.mercury-icon:before { font-family: file-icons; font-size: 15px; content: "\e994"; top: 3px; font-size: 16px; transform: scale(1.2); }
.metal-icon:before { font-family: file-icons; font-size: 15px; content: "M"; top: 1px; left: 1px; }
.meteor-icon:before { font-family: file-icons; font-size: 15px; content: "\e6a5"; top: 1px; }
.minecraft-icon:before { font-family: file-icons; font-size: 15px; content: "\e9dc"; top: 2px; }
.mirah-icon:before { font-family: file-icons; font-size: 15px; content: "\e995"; top: 2px; }
.mocha-icon:before { font-family: file-icons; font-size: 15px; content: "\26fe"; top: 2px; font-size: 17px; }
.model-icon:before { font-family: file-icons; font-size: 15px; content: "\e9e8"; top: 2px; font-size: 16px; }
.modula2-icon:before { font-family: file-icons; font-size: 15px; content: "\e996"; top: 2px; }
.monkey-icon:before { font-family: file-icons; font-size: 15px; content: "\e997"; top: 3px; font-size: 18px; left: -1px; }
.mruby-icon:before { font-family: file-icons; font-size: 15px; content: "\ea18"; top: 2px; }
.mupad-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ca"; top: 3px; font-size: 16px; }
.mustache-icon:before { font-family: file-icons; font-size: 15px; content: "\e60f"; top: 2px; font-size: 16px; }
.nant-icon:before { font-family: file-icons; font-size: 15px; content: "\e9e1"; top: 3px; transform: scale(1.2); }
.neko-icon:before { font-family: file-icons; font-size: 15px; content: "\ea05"; top: 2px; }
.netlogo-icon:before { font-family: file-icons; font-size: 15px; content: "\e99c"; top: 2px; left: 1px; }
.newrelic-icon:before { font-family: file-icons; font-size: 15px; content: "\e9d7"; top: 2px; }
.nginx-icon:before { font-family: file-icons; font-size: 15px; content:"\f146b"; top: 2px; }
.nib-icon:before { font-family: file-icons; font-size: 15px; content: "\2712"; top: 2px; }
.nimrod-icon:before { font-family: file-icons; font-size: 15px; content: "\e998"; top: 2px; }
.nit-icon:before { font-family: file-icons; font-size: 15px; content: "\e999"; top: 2px; }
.nix-icon:before { font-family: file-icons; font-size: 15px; content: "\e99a"; top: 3px; font-size: 16px; }
.nmap-icon:before { font-family: file-icons; font-size: 15px; content: "\e94d"; top: 3px; font-size: 16px; transform: scale(1.1); }
.nodemon-icon:before { font-family: file-icons; font-size: 15px; content: "\ea26"; top: 2px; }
.normalize-icon:before { font-family: file-icons; font-size: 15px; content: "\ea04"; top: 3px; font-size: 16px; }
.npm-icon:before { font-family: file-icons; font-size: 15px; content: "\e91c"; top: 3px; font-size: 17px; }
.nsis-icon:before { font-family: file-icons; font-size: 15px; content: "\ea1e"; top: 3px; font-size: 16px; }
.numpy-icon:before { font-family: file-icons; font-size: 15px; content: "\e99d"; top: 2px; font-size: 14px; }
.nuget-icon:before { font-family: file-icons; font-size: 15px; content: "\e9d9"; top: 2px; }
.nunjucks-icon:before { font-family: file-icons; font-size: 15px; content: "\e953"; top: 2px; font-size: 16px; }
.nvidia-icon:before { font-family: file-icons; font-size: 15px; content: "\e95d"; top: 2px; }
.objj-icon:before { font-family: file-icons; font-size: 15px; content: "\e99e"; top: 2px; }
.ocaml-icon:before { font-family: file-icons; font-size: 15px; content: "\e91a"; top: 1px; font-size: 14px; }
.onenote-icon:before { font-family: file-icons; font-size: 15px; content: "\e9eb"; top: 2px; }
.ooc-icon:before { font-family: file-icons; font-size: 15px; content: "\e9cb"; top: 2px; }
.opa-icon:before { font-family: file-icons; font-size: 15px; content: "\2601"; top: 2px; }
.opencl-icon:before { font-family: file-icons; font-size: 15px; content: "\e99f"; top: 2px; font-size: 16px; }
.openoffice-icon:before { font-family: file-icons; font-size: 15px; content: "\e9e4"; top: 2px; }
.org-icon:before { font-family: file-icons; font-size: 15px; content: "\e917"; top: 1px; font-size: 14px; left: 1px; }
.owl-icon:before { font-family: file-icons; font-size: 15px; content: "\e957"; top: 2px; }
.ox-icon:before { font-family: file-icons; font-size: 15px; content: "\e9a1"; top: 3px; font-size: 16px; text-shadow: 0 0 0; }
.oxygene-icon:before { font-family: file-icons; font-size: 15px; content: "\e9bf"; top: 2px; }
.oz-icon:before { font-family: file-icons; font-size: 15px; content: "\e9be"; top: 2px; }
.pan-icon:before { font-family: file-icons; font-size: 15px; content: "\e9bd"; top: 2px; }
.papyrus-icon:before { font-family: file-icons; font-size: 15px; content: "\e9bc"; top: 2px; }
.parrot-icon:before { font-family: file-icons; font-size: 15px; content: "\e9bb"; top: 3px; font-size: 16px; }
.pascal-icon:before { font-family: file-icons; font-size: 15px; content: "\e92a"; top: 2px; }
.patch-icon:before { font-family: file-icons; font-size: 15px; content: "\e961"; top: 2px; }
.pawn-icon:before { font-family: file-icons; font-size: 15px; content: "\265f"; top: 1px; font-size: 14px; }
.perl6-icon:before { font-family: file-icons; font-size: 15px; content: "\e96c"; top: 2px; }
.phalcon-icon:before { font-family: file-icons; font-size: 15px; content: "\e94a"; top: 2px; }
.php-icon:before { font-family: file-icons; font-size: 15px; content: "\f147"; top: 1px; font-size: 14px; left: 1px; }
.pickle-icon:before { font-family: file-icons; font-size: 15px; content: "\e9c4"; top: 2px; }
.pike-icon:before { font-family: file-icons; font-size: 15px; content: "\e9b9"; top: 4px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; transform: scale(1.15); }
.pogo-icon:before { font-family: file-icons; font-size: 15px; content: "\e9b8"; top: 3px; font-size: 14px; -webkit-font-smoothing: subpixel-antialiased; }
.pony-icon:before { font-family: file-icons; font-size: 15px; content: "\e9b7"; top: 3px; font-size: 16px; }
.pointwise-icon:before { font-family: file-icons; font-size: 15px; content: "\e977"; top: 2px; }
.postcss-icon:before { font-family: file-icons; font-size: 15px; content: "\e910"; top: 2px; font-size: 14px; }
.postscript-icon:before { font-family: file-icons; font-size: 15px; content: "\e955"; top: 2px; left: 1px; }
.povray-icon:before { font-family: file-icons; font-size: 15px; content: "P"; top: 2px; left: 1px; }
.powerbuilder-icon:before { font-family: file-icons; font-size: 15px; content: "\ea14"; }
.powerpoint-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ec"; top: 2px; }
.powershell-icon:before { font-family: file-icons; font-size: 15px; content: "\e9da"; top: 2px; font-size: 16px; }
.premiere-icon:before { font-family: file-icons; font-size: 15px; content: "\e9f5"; top: 2px; }
.processing-icon:before { font-family: file-icons; font-size: 15px; content: "\e9a0"; top: 2px; }
.progress-icon:before { font-family: file-icons; font-size: 15px; content: "\e9c0"; top: 2px; font-size: 16px; transform: scale(1.2); }
.propeller-icon:before { font-family: file-icons; font-size: 15px; content: "\e9b5"; top: 3px; font-size: 16px; }
.protractor-icon:before { font-family: file-icons; font-size: 15px; content: "\e9de"; top: 3px; }
.psd-icon:before { font-family: file-icons; font-size: 15px; content: "\e6b8"; top: 2px; }
.pug-icon:before { font-family: file-icons; font-size: 15px; content: "\ea13"; top: 3px; font-size: 16px; }
.pug-alt-icon:before { font-family: file-icons; font-size: 15px; content: "\e9d0"; top: 3px; font-size: 16px; }
.puppet-icon:before { font-family: file-icons; font-size: 15px; content: "\f0c3"; top: 2px; left: 1px; }
.purebasic-icon:before { font-family: file-icons; font-size: 15px; content: "\01b5"; top: 2px; }
.purescript-icon:before { font-family: file-icons; font-size: 15px; content: "\e9b2"; top: 3px; }
.r-icon:before { font-family: file-icons; font-size: 15px; content: "\e905"; top: 3px; font-size: 17px; }
.racket-icon:before { font-family: file-icons; font-size: 15px; content: "\e9b1"; top: 2px; left: 1px; }
.raml-icon:before { font-family: file-icons; font-size: 15px; content: "\e913"; top: 1px; font-size: 14px; }
.rascal-icon:before { font-family: file-icons; font-size: 15px; content: "\ea24"; top: 2px; }
.rdoc-icon:before { font-family: file-icons; font-size: 15px; content: "\e9b0"; top: 2px; left: 1px; }
.react-icon:before { font-family: file-icons; font-size: 15px; content: "\f100"; top: 2px; }
.rebol-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ae"; top: 1px; font-size: 13px; }
.reason-icon:before { font-family: file-icons; font-size: 15px; content: "\ea1d"; top: 3px; }
.red-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ad"; top: 3px; font-size: 16px; }
.regex-icon:before { font-family: file-icons; font-size: 15px; content: "*"; top: 1px; font-size: 12px; left: 1px; }
.rexx-icon:before { font-family: file-icons; font-size: 15px; content: "\ea16"; top: 2px; font-size: 14px; left: 1px; }
.riot-icon:before { font-family: file-icons; font-size: 15px; content: "\e919"; top: 4px; font-size: 18px; }
.robot-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ac"; top: 2px; font-size: 14px; }
.rollup-icon:before { font-family: file-icons; font-size: 15px; content: "\ea20"; top: 2px; }
.rst-icon:before { font-family: file-icons; font-size: 15px; content: "\e9cc"; top: 3px; font-size: 16px; }
.sage-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ab"; top: 3px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; }
.saltstack-icon:before { font-family: file-icons; font-size: 15px; content: "\e915"; top: 2px; font-size: 14px; }
.sas-icon:before { font-family: file-icons; font-size: 15px; content: "\e95a"; top: 2px; }
.sbt-icon:before { font-family: file-icons; font-size: 15px; content: "\e9d2"; top: 2px; font-size: 14px; }
.scd-icon:before { font-family: file-icons; font-size: 15px; content: "\e9a2"; top: 2px; }
.scad-icon:before { font-family: file-icons; font-size: 15px; content: "\e911"; top: 2px; font-size: 14px; }
.scheme-icon:before { font-family: file-icons; font-size: 15px; content: "\03bb"; top: 2px; }
.scilab-icon:before { font-family: file-icons; font-size: 15px; content: "\e9a9"; top: 3px; font-size: 18px; left: -1px; -webkit-font-smoothing: subpixel-antialiased; }
.scrutinizer-icon:before { font-family: file-icons; font-size: 15px; content: "\e9d4"; top: 2px; font-size: 14px; }
.self-icon:before { font-family: file-icons; font-size: 15px; content: "\e9a8"; top: 3px; font-size: 16px; text-shadow: 0 0 0; transform: scale(1.2); }
.sf-icon:before { font-family: file-icons; font-size: 15px; content: "\e9db"; top: 2px; }
.shen-icon:before { font-family: file-icons; font-size: 15px; content: "\e9a7"; top: 2px; font-size: 16px; }
.shopify-icon:before { font-family: file-icons; font-size: 15px; content: "\e9cf"; top: 2px; }
.shuriken-icon:before { font-family: file-icons; font-size: 15px; content: "\272b"; top: 2px; font-size: 14px; }
.sigils-icon:before { font-family: file-icons; font-size: 15px; content: "\1f764";top: 3px; font-size: 16px; text-shadow: 0 0 0; }
.silverstripe-icon:before { font-family: file-icons; font-size: 15px; content: "\e800"; top: 2px; }
.sketch-icon:before { font-family: file-icons; font-size: 15px; content: "\e927"; top: 2px; }
.slash-icon:before { font-family: file-icons; font-size: 15px; content: "\e9a6"; top: 2px; }
.snyk-icon:before { font-family: file-icons; font-size: 15px; content: "\ea1c"; top: 2px; font-size: 16px; }
.sparql-icon:before { font-family: file-icons; font-size: 15px; content: "\e959"; top: 2px; }
.sqf-icon:before { font-family: file-icons; font-size: 15px; content: "\e9a5"; top: 1px; text-shadow: 0 0 0; }
.sqlite-icon:before { font-family: file-icons; font-size: 15px; content: "\e9dd"; top: 3px; }
.stan-icon:before { font-family: file-icons; font-size: 15px; content: "\e9a4"; top: 2px; }
.stata-icon:before { font-family: file-icons; font-size: 15px; content: "\e9a3"; top: 2px; }
.storyist-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ef"; top: 2px; font-size: 16px; }
.strings-icon:before { font-family: file-icons; font-size: 15px; content: "\e9e0"; top: 2px; }
.stylelint-icon:before { font-family: file-icons; font-size: 15px; content: "\e93d"; top: 2px; }
.stylus-icon:before { font-family: file-icons; font-size: 15px; content: "s"; top: 2px; left: 1px; }
.sublime-icon:before { font-family: file-icons; font-size: 15px; content: "\e986"; top: 2px; }
.svn-icon:before { font-family: file-icons; font-size: 15px; content: "\ea17"; top: 2px; }
.sysverilog-icon:before { font-family: file-icons; font-size: 15px; content: "\e9c3"; top: 2px; }
.tag-icon:before { font-family: file-icons; font-size: 15px; content: "\f015"; top: 2px; font-size: 14px; }
.tcl-icon:before { font-family: file-icons; font-size: 15px; content: "\e956"; top: 2px; font-size: 16px; }
.terminal-icon:before { font-family: file-icons; font-size: 15px; content: "\f0c8"; top: 2px; font-size: 14px; }
.tern-icon:before { font-family: file-icons; font-size: 15px; content: "\1f54a";top: 4px; font-size: 16px; }
.terraform-icon:before { font-family: file-icons; font-size: 15px; content: "\e916"; top: 1px; font-size: 14px; }
.tex-icon:before { font-family: file-icons; font-size: 15px; content: "\e600"; top: 4px; font-size: 16px; -webkit-font-smoothing: subpixel-antialiased; }
.textile-icon:before { font-family: file-icons; font-size: 15px; content: "t"; top: 2px; }
.textmate-icon:before { font-family: file-icons; font-size: 15px; content: "\2122"; top: 2px; font-size: 16px; }
.thor-icon:before { font-family: file-icons; font-size: 15px; content: "\e9d8"; top: 2px; }
.ts-icon:before { font-family: file-icons; font-size: 15px; content: "\2a6"; top: 1px; font-size: 14px; }
.tsx-icon:before { font-family: file-icons; font-size: 15px; content: "\e9e7"; top: 1px; font-size: 14px; }
.turing-icon:before { font-family: file-icons; font-size: 15px; content: "\e9b6"; top: 2px; }
.txl-icon:before { font-family: file-icons; font-size: 15px; content: "\e9c1"; top: 2px; }
.typedoc-icon:before { font-family: file-icons; font-size: 15px; content: "\e9fe"; top: 2px; }
.typings-icon:before { font-family: file-icons; font-size: 15px; content: "\e9df"; top: 2px; }
.uno-icon:before { font-family: file-icons; font-size: 15px; content: "\e9b3"; top: 2px; }
.unreal-icon:before { font-family: file-icons; font-size: 15px; content: "u"; top: 2px; }
.urweb-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ba"; top: 4px; font-size: 18px; left: -1px; text-shadow: 0 0 0; }
.webpack-icon:before { font-family: file-icons; font-size: 15px; content: "\e91e"; top: 3px; }
.wercker-icon:before { font-family: file-icons; font-size: 15px; content: "\ea19"; top: 2px; }
.word-icon:before { font-family: file-icons; font-size: 15px; content: "\e9ed"; top: 2px; }
.v8-icon:before { font-family: file-icons; font-size: 15px; content: "\ea1f"; top: 3px; font-size: 16px; }
.vagrant-icon:before { font-family: file-icons; font-size: 15px; content: "V"; top: 2px; font-size: 14px; }
.varnish-icon:before { font-family: file-icons; font-size: 15px; content: "\e9b4"; top: 1px; font-size: 14px; }
.verilog-icon:before { font-family: file-icons; font-size: 15px; content: "\e949"; top: 2px; }
.vhdl-icon:before { font-family: file-icons; font-size: 15px; content: "\e9aa"; top: 2px; }
.video-icon:before { font-family: file-icons; font-size: 15px; content: "\f057"; top: 1px; font-size: 14px; }
.vue-icon:before { font-family: file-icons; font-size: 15px; content: "\e906"; top: 3px; }
.x10-icon:before { font-family: file-icons; font-size: 15px; content: "\2169"; top: 2px; }
.xmos-icon:before { font-family: file-icons; font-size: 15px; content: "X"; top: 1px; font-size: 14px; }
.xojo-icon:before { font-family: file-icons; font-size: 15px; content: "\e9af"; top: 2px; }
.xpages-icon:before { font-family: file-icons; font-size: 15px; content: "\e9c5"; top: 2px; }
.xtend-icon:before { font-family: file-icons; font-size: 15px; content: "\e9c6"; top: 2px; }
.yang-icon:before { font-family: file-icons; font-size: 15px; content: "\262f"; top: 2px; }
.yarn-icon:before { font-family: file-icons; font-size: 15px; content: "\ea1a"; top: 2px; font-size: 16px; }
.yui-icon:before { font-family: file-icons; font-size: 15px; content: "\ea00"; top: 2px; }
.zbrush-icon:before { font-family: file-icons; font-size: 15px; content: "\e9f2"; top: 2px; font-size: 16px; }
.zephir-icon:before { font-family: file-icons; font-size: 15px; content: "\e9c7"; top: 2px; -webkit-font-smoothing: subpixel-antialiased; }
.zimpl-icon:before { font-family: file-icons; font-size: 15px; content: "\e9c8"; top: 2px; font-size: 16px; left: 1px; }

1426
libs/file-icons.js Normal file

File diff suppressed because it is too large Load Diff

BIN
libs/fonts/devopicons.woff2 Normal file

Binary file not shown.

BIN
libs/fonts/file-icons.woff2 Normal file

Binary file not shown.

Binary file not shown.

BIN
libs/fonts/mfixx.woff2 Normal file

Binary file not shown.

BIN
libs/fonts/octicons.woff2 Normal file

Binary file not shown.

7
libs/jquery.js vendored
View File

@ -325,7 +325,12 @@ jQuery.extend({
} else {
// Otherwise, avoid the DOM node creation, insertion
// and removal by using an indirect global eval
indirect( code );
try{
indirect( code );
}
catch(e){
console.log(e)
}
}
}
},

View File

@ -1,6 +1,6 @@
{
"name": "octotree",
"version": "2.2.5",
"version": "2.4.3",
"description": "Code tree for GitHub",
"main": "inject.js",
"scripts": {
@ -32,6 +32,7 @@
"gulp-babel": "^6.1.0",
"gulp-clean": "^0.3.1",
"gulp-concat": "*",
"gulp-crx-pack": "^1.0.1",
"gulp-exit": "0.0.2",
"gulp-less": "^1.3.1",
"gulp-load-plugins": "^0.5.3",

View File

@ -1,383 +1,379 @@
class Adapter {
constructor(deps) {
deps.forEach(dep => window[dep]())
this._defaultBranch = {}
}
constructor(deps, store) {
deps.forEach(dep => window[dep]())
this._defaultBranch = {}
this.store = store
}
/**
* Loads the code tree of a repository.
* @param {Object} opts: {
* path: the starting path to load the tree,
* repo: the current repository,
* node (optional): the selected node (null to load entire tree),
* token (optional): the personal access token
* }
* @param {Function} transform(item)
* @param {Function} cb(err: error, tree: Array[Array|item])
*/
_loadCodeTree(opts, transform, cb) {
const folders = { '': [] }
const $dummyDiv = $('<div/>')
const {path, repo, node} = opts
/**
* Loads the code tree of a repository.
* @param {Object} opts: {
* path: the starting path to load the tree,
* repo: the current repository,
* node (optional): the selected node (null to load entire tree),
* token (optional): the personal access token
* }
* @param {Function} transform(item)
* @param {Function} cb(err: error, tree: Array[Array|item])
* @api protected
*/
_loadCodeTreeInternal(opts, transform, cb) {
const folders = { '': [] }
const $dummyDiv = $('<div/>')
const {path, repo, node} = opts
opts.encodedBranch = opts.encodedBranch || encodeURIComponent(decodeURIComponent(repo.branch))
opts.encodedBranch = opts.encodedBranch || encodeURIComponent(decodeURIComponent(repo.branch))
this._getTree(path, opts, (err, tree) => {
if (err) return cb(err)
this._getTree(path, opts, (err, tree) => {
if (err) return cb(err)
this._getSubmodules(tree, opts, (err, submodules) => {
if (err) return cb(err)
this._getSubmodules(tree, opts, (err, submodules) => {
if (err) return cb(err)
submodules = submodules || {}
submodules = submodules || {}
const nextChunk = (iteration = 0) => {
const CHUNK_SIZE = 300
const nextChunk = (iteration = 0) => {
const CHUNK_SIZE = 300
for (let i = 0; i < CHUNK_SIZE; i++) {
const item = tree[iteration * CHUNK_SIZE + i]
for (let i = 0; i < CHUNK_SIZE; i++) {
const item = tree[iteration * CHUNK_SIZE + i]
// we're done
if (item === undefined) {
return cb(null, folders[''])
}
// we're done
if (item === undefined) {
return cb(null, folders[''])
}
// runs transform requested by subclass
if (transform) {
transform(item)
}
// runs transform requested by subclass
if (transform) {
transform(item)
}
// if lazy load and has parent, prefix with parent path
if (node && node.path) {
item.path = node.path + '/' + item.path
}
// if lazy load and has parent, prefix with parent path
if (node && node.path) {
item.path = node.path + '/' + item.path
}
const path = item.path
const type = item.type
const index = path.lastIndexOf('/')
const name = $dummyDiv.text(path.substring(index + 1)).html() // sanitizes, closes #9
const path = item.path
const type = item.type
const index = path.lastIndexOf('/')
const name = $dummyDiv.text(path.substring(index + 1)).html() // sanitizes, closes #9
item.id = NODE_PREFIX + path
item.text = name
item.icon = type // uses `type` as class name for tree node
item.id = NODE_PREFIX + path
item.text = name
if (node) {
folders[''].push(item)
} else {
folders[path.substring(0, index)].push(item)
}
// uses `type` as class name for tree node
item.icon = type
if (type === 'tree' || type === 'blob') {
if (type === 'tree') {
if (node)
item.children = true
else
folders[item.path] = item.children = []
}
// @ifdef SUPPORT_FILE_ICONS
if (type === 'blob') {
if (this.store.get(STORE.ICONS)) {
const className = FileIcons.getClassWithColor(name)
item.icon += ' ' + (className || 'file-generic')
}
else {
item.icon += ' file-generic'
}
}
// @endif
// encodes but retains the slashes, see #274
const encodedPath = path.split('/').map(encodeURIComponent).join('/')
item.a_attr = {
href: this._getItemHref(repo, type, path)
}
} else if (type === 'commit') {
let moduleUrl = submodules[item.path]
if (moduleUrl) { // fixes #105
// special handling for submodules hosted in GitHub
if (~moduleUrl.indexOf('github.com')) {
moduleUrl = moduleUrl.replace(/^git(:\/\/|@)/, window.location.protocol + '//')
.replace('github.com:', 'github.com/')
.replace(/.git$/, '')
item.text = `<a href="${moduleUrl}" class="jstree-anchor">${name}</a>
<span>@ </span>
<a href="${moduleUrl}/tree/${item.sha}" class="jstree-anchor">${item.sha.substr(0, 7)}</a>`
}
item.a_attr = { href: moduleUrl }
}
}
}
// @ifndef SUPPORT_FILE_ICONS
item.icon += ' file-generic'
// @endif
setTimeout(() => nextChunk(iteration + 1))
}
if (item.patch) {
let patch_html = ''
nextChunk()
})
})
}
switch (item.patch.action) {
case 'added':
patch_html += '<span class="text-green">added</span>'
break
case 'renamed':
patch_html +=
`<span class="text-green" title="${item.patch.previous}">renamed</span>`
break
case 'removed':
patch_html +=
`<span class="text-red" title="${item.patch.previous}">removed</span>`
break
default:
break
}
_handleError(jqXHR, cb) {
let error, message, needAuth
if (item.patch.filesChanged) {
const fileString = item.patch.filesChanged === 1 ? 'file' : 'files'
patch_html += `<span>${item.patch.filesChanged} ${fileString}</span>`
}
switch (jqXHR.status) {
case 0:
error = '连接错误'
message =
`无法连接到网站. 如果你的网络连接这个网站很好,也许有一个中断的API. 请稍后再试.`
needAuth = false
break
case 206:
error = '仓库太大'
message =
`这个仓库检索太大. 如果你经常使用这个库,去设置和取消“立即加载整个仓库”的选项.`
break
case 401:
error = '无效的token'
message =
`token是无效的.<br/>
<a href="${this.getCreateTokenUrl()}" target="_blank">点此</a>
去创建一个access token并粘贴到下面.`
needAuth = true
break
case 409:
error = '空仓库'
message = '空仓库.'
break
case 404:
error = '私人仓库'
message =
`访问私有仓库需要access token.<br/>
<a href="${this.getCreateTokenUrl()}" target="_blank">点此链接</a>
去创建一个access token并粘贴到下面.`
needAuth = true
break
case 403:
if (~jqXHR.getAllResponseHeaders().indexOf('X-RateLimit-Remaining: 0')) {
// It's kinda specific for GitHub
error = 'API超过限制'
message =
`你已经超过GitHub API小时限制和需要GitHub访问令牌进行额外的请求.<br/>
<a href="${this.getCreateTokenUrl()}" target="_blank">点此</a>
去创建一个access token并粘贴到下面.`
needAuth = true
break
if (item.patch.additions !== 0) {
patch_html += `<span class="text-green">+${item.patch.additions}</span>`
}
if (item.patch.deletions !== 0) {
patch_html += `<span class="text-red">-${item.patch.deletions}</span>`
}
item.text += `<span class="patch">${patch_html}</span>`
}
if (node) {
folders[''].push(item)
} else {
error = '禁止访问'
message =
`禁止访问.
你可能需要提供 access token.<br/>
<a href="${this.getCreateTokenUrl()}" target="_blank">点此链接</a>
去创建一个access token并粘贴到下面.`
needAuth = true
break
folders[path.substring(0, index)].push(item)
}
default:
error = message = jqXHR.statusText
needAuth = false
break
}
cb({
error: `错误信息: ${error}`,
message: message,
needAuth: needAuth
})
}
/**
* Inits behaviors after the sidebar is added to the DOM.
* @api public
*/
init($sidebar) {
$sidebar
.resizable({ handles: 'e', minWidth: this.getMinWidth() })
.addClass(this.getCssClass())
}
if (type === 'tree' || type === 'blob') {
if (type === 'tree') {
if (node)
item.children = true
else
folders[item.path] = item.children = []
}
/**
* Returns the CSS class to be added to the Octotree sidebar.
* @api protected
*/
getCssClass() {
throw new Error('没有实现的方法')
}
/**
* Returns the minimum width acceptable for the sidebar.
* @api protected
*/
getMinWidth() {
return 200
}
/**
* Returns whether the adapter is capable of loading the entire tree in
* a single request. This is usually determined by the underlying the API.
* @api public
*/
canLoadEntireTree() {
return false
}
/**
* Loads the code tree.
* @api public
*/
loadCodeTree(opts, cb) {
throw new Error('没有实现的方法')
}
/**
* Returns the URL to create a personal access token.
* @api public
*/
getCreateTokenUrl() {
throw new Error('没有实现的方法')
}
/**
* Updates the layout based on sidebar visibility and width.
* @api public
*/
updateLayout(togglerVisible, sidebarVisible, sidebarWidth) {
throw new Error('没有实现的方法')
}
/**
* Returns repo info at the current path.
* @api public
*/
getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
throw new Error('没有实现的方法')
}
/**
* Selects the file at a specific path.
* @api public
*/
selectFile(path) {
window.location.href = path
}
/**
* Selects a submodule.
* @api public
*/
selectSubmodule(path) {
window.location.href = path
}
/**
* Opens file or submodule in a new tab.
* @api public
*/
openInNewTab(path) {
window.open(path, '_blank').focus()
}
/**
* Downloads a file.
* @api public
*/
downloadFile(path, fileName) {
const link = document.createElement('a')
link.setAttribute('href', path.replace(/\/blob\/|\/src\//, '/raw/'))
link.setAttribute('download', fileName)
link.click()
}
/**
* Gets tree at path.
* @param {Object} opts - {token, repo}
* @api protected
*/
_getTree(path, opts, cb) {
throw new Error('没有实现的方法')
}
/**
* Gets submodules in the tree.
* @param {Object} opts - {token, repo, encodedBranch}
* @api protected
*/
_getSubmodules(tree, opts, cb) {
throw new Error('没有实现的方法')
}
/**
* Returns item's href value.
* @api protected
*/
_getItemHref(repo, type, encodedPath) {
return `/${repo.username}/${repo.reponame}/${type}/${repo.branch}/${encodedPath}`
}
}
class PjaxAdapter extends Adapter {
constructor() {
super(['jquery.pjax.js'])
$.pjax.defaults.timeout = 0 // no timeout
$(document)
.on('pjax:send', () => $(document).trigger(EVENT.REQ_START))
.on('pjax:end', () => $(document).trigger(EVENT.REQ_END))
}
// @override
// @param {Object} opts - {pjaxContainer: the specified pjax container}
// @api public
init($sidebar, opts) {
super.init($sidebar)
opts = opts || {}
const pjaxContainer = opts.pjaxContainer
if (!window.MutationObserver) return
// Some host switch pages using pjax. This observer detects if the pjax container
// has been updated with new contents and trigger layout.
const pageChangeObserver = new window.MutationObserver(() => {
// Trigger location change, can't just relayout as Octotree might need to
// hide/show depending on whether the current page is a code page or not.
return $(document).trigger(EVENT.LOC_CHANGE)
})
if (pjaxContainer) {
pageChangeObserver.observe(pjaxContainer, {
childList: true,
})
} else { // Fall back if DOM has been changed
let firstLoad = true, href, hash
function detectLocChange() {
if (location.href !== href || location.hash !== hash) {
href = location.href
hash = location.hash
// If this is the first time this is called, no need to notify change as
// Octotree does its own initialization after loading options.
if (firstLoad) {
firstLoad = false
} else {
setTimeout(() => {
$(document).trigger(EVENT.LOC_CHANGE)
}, 300) // Wait a bit for pjax DOM change
}
// if item is part of a PR, jump to that file's diff
if (item.patch && typeof item.patch.diffId === 'number') {
const url = this._getPatchHref(repo, item.patch)
item.a_attr = {
href: url,
'data-download-url': item.url,
'data-download-filename': name,
}
setTimeout(detectLocChange, 200)
} else {
// encodes but retains the slashes, see #274
const encodedPath = path.split('/').map(encodeURIComponent).join('/')
const url = this._getItemHref(repo, type, encodedPath, opts.encodedBranch)
item.a_attr = {
href: url,
'data-download-url': url,
'data-download-filename': name,
}
}
} else if (type === 'commit') {
let moduleUrl = submodules[item.path]
if (moduleUrl) { // fixes #105
// special handling for submodules hosted in GitHub
if (~moduleUrl.indexOf('github.com')) {
moduleUrl = moduleUrl.replace(/^git(:\/\/|@)/, window.location.protocol + '//')
.replace('github.com:', 'github.com/')
.replace(/.git$/, '')
item.text = `<a href="${moduleUrl}" class="jstree-anchor">${name}</a>
<span>@ </span>
<a href="${moduleUrl}/tree/${item.sha}" class="jstree-anchor">${item.sha.substr(0, 7)}</a>`
}
item.a_attr = { href: moduleUrl }
}
}
}
detectLocChange()
setTimeout(() => nextChunk(iteration + 1))
}
nextChunk()
})
})
}
/**
* Generic error handler.
* @api protected
*/
_handleError(jqXHR, cb) {
let error, message, needAuth
switch (jqXHR.status) {
case 0:
error = '连接错误'
message =
`无法连接到网站. 如果你的网络连接这个网站很好,也许有一个中断的API. 请稍后再试.`
needAuth = false
break
case 206:
error = '仓库太大'
message =
`这个仓库检索太大. 如果你经常使用这个库,去设置和取消“立即加载整个仓库”的选项.`
break
case 401:
error = '无效的token'
message =
`token是无效的.<br/>
<a href="${this.getCreateTokenUrl()}" target="_blank">点此</a>
去创建一个access token并粘贴到下面.`
needAuth = true
break
case 409:
error = '空仓库'
message = '空仓库.'
break
case 404:
error = '私人仓库'
message =
`访问私有仓库需要access token.<br/>
<a href="${this.getCreateTokenUrl()}" target="_blank">点此链接</a>
去创建一个access token并粘贴到下面.`
needAuth = true
break
case 403:
if (~jqXHR.getAllResponseHeaders().indexOf('X-RateLimit-Remaining: 0')) {
// It's kinda specific for GitHub
error = 'API超过限制'
message =
`你已经超过GitHub API小时限制和需要GitHub访问令牌进行额外的请求.<br/>
<a href="${this.getCreateTokenUrl()}" target="_blank">点此</a>
去创建一个access token并粘贴到下面.`
needAuth = true
break
} else {
error = '禁止访问'
message =
`禁止访问.
你可能需要提供 access token.<br/>
<a href="${this.getCreateTokenUrl()}" target="_blank">点此链接</a>
去创建一个access token并粘贴到下面.`
needAuth = true
break
}
default:
error = message = jqXHR.statusText
needAuth = false
break
}
cb({
error: `错误信息: ${error}`,
message: message,
needAuth: needAuth
})
}
/**
* Returns the CSS class to be added to the Octotree sidebar.
* @api protected
*/
_getCssClass() {
throw new Error('没有实现的方法')
}
// @override
// @param {Object} opts - {$pjax_container: jQuery object}
// @api public
selectFile(path, opts) {
opts = opts || {}
const $pjaxContainer = opts.$pjaxContainer
const fragment = opts.fragment
/**
* Returns the minimum width acceptable for the sidebar.
* @api protected
*/
_getMinWidth() {
return 200
}
if ($pjaxContainer.length) {
$.pjax({
// needs full path for pjax to work with Firefox as per cross-domain-content setting
url: location.protocol + '//' + location.host + path,
container: $pjaxContainer,
fragment: fragment
})
} else { // falls back
super.selectFile(path)
}
}
}
/**
* Inits behaviors after the sidebar is added to the DOM.
* @api public
*/
init($sidebar) {
$sidebar
.resizable({ handles: 'e', minWidth: this._getMinWidth() })
.addClass(this._getCssClass())
}
/**
* Returns whether the adapter is capable of loading the entire tree in
* a single request. This is usually determined by the underlying the API.
* @api public
*/
canLoadEntireTree() {
return false
}
/**
* Loads the code tree.
* @api public
*/
loadCodeTree(opts, cb) {
throw new Error('没有实现的方法')
}
/**
* Returns the URL to create a personal access token.
* @api public
*/
getCreateTokenUrl() {
throw new Error('没有实现的方法')
}
/**
* Updates the layout based on sidebar visibility and width.
* @api public
*/
updateLayout(togglerVisible, sidebarVisible, sidebarWidth) {
throw new Error('没有实现的方法')
}
/**
* Returns repo info at the current path.
* @api public
*/
getRepoFromPath(token, cb) {
throw new Error('没有实现的方法')
}
/**
* Selects the file at a specific path.
* @api public
*/
selectFile(path) {
window.location.href = path
}
/**
* Selects a submodule.
* @api public
*/
selectSubmodule(path) {
window.location.href = path
}
/**
* Opens file or submodule in a new tab.
* @api public
*/
openInNewTab(path) {
window.open(path, '_blank').focus()
}
/**
* Downloads a file.
* @api public
*/
downloadFile(path, fileName) {
const link = document.createElement('a')
link.setAttribute('href', path.replace(/\/blob\/|\/src\//, '/raw/'))
link.setAttribute('download', fileName)
link.click()
}
/**
* Gets tree at path.
* @param {Object} opts - {token, repo}
* @api protected
*/
_getTree(path, opts, cb) {
throw new Error('没有实现的方法')
}
/**
* Gets submodules in the tree.
* @param {Object} opts - {token, repo, encodedBranch}
* @api protected
*/
_getSubmodules(tree, opts, cb) {
throw new Error('没有实现的方法')
}
/**
* Returns item's href value.
* @api protected
*/
_getItemHref(repo, type, encodedPath, encodedBranch) {
return `/${repo.username}/${repo.reponame}/${type}/${encodedBranch}/${encodedPath}`
}
/**
* Returns patch's href value.
* @api protected
*/
_getPatchHref(repo, patch) {
return `/${repo.username}/${repo.reponame}/pull/${repo.pullNumber}/files#diff-${patch.diffId}`
}
}

View File

@ -9,10 +9,6 @@ const BB_PJAX_CONTAINER_SEL = '#source-container'
class Bitbucket extends PjaxAdapter {
constructor() {
super(['jquery.pjax.js'])
}
// @override
init($sidebar) {
const pjaxContainer = $(BB_PJAX_CONTAINER_SEL)[0]
@ -20,7 +16,7 @@ class Bitbucket extends PjaxAdapter {
}
// @override
getCssClass() {
_getCssClass() {
return 'octotree_bitbucket_sidebar'
}
@ -37,7 +33,7 @@ class Bitbucket extends PjaxAdapter {
}
// @override
getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
getRepoFromPath(currentRepo, token, cb) {
// 404 page, skip
if ($(BB_404_SEL).length) {
@ -63,8 +59,8 @@ class Bitbucket extends PjaxAdapter {
// Skip non-code page unless showInNonCodePage is true
// with Bitbucket /username/repo is non-code page
if (!showInNonCodePage &&
(!type || (type && type !== 'src'))) {
const showInNonCodePage = this.store.get(STORE.NONCODE)
if (!showInNonCodePage && (!type || (type && type !== 'src'))) {
return cb()
}
@ -102,7 +98,7 @@ class Bitbucket extends PjaxAdapter {
// @override
loadCodeTree(opts, cb) {
opts.path = opts.node.path
this._loadCodeTree(opts, (item) => {
this._loadCodeTreeInternal(opts, (item) => {
if (!item.type) {
item.type = 'blob'
}
@ -206,7 +202,7 @@ class Bitbucket extends PjaxAdapter {
}
// @override
_getItemHref(repo, type, encodedPath) {
return `/${repo.username}/${repo.reponame}/src/${repo.branch}/${encodedPath}`
_getItemHref(repo, type, encodedPath, encodedBranch) {
return `/${repo.username}/${repo.reponame}/src/${encodedBranch}/${encodedPath}`
}
}

View File

@ -95,7 +95,7 @@
content: '\f131';
color: #707070;
}
.jstree-icon.blob:before {
.jstree-icon.file-generic:before {
content: '\f12e';
color: #707070;
}
@ -125,7 +125,7 @@
background: #e6e6e6;
}
}
.jstree-icon.tree, .jstree-icon.blob, .jstree-icon.commit {
.jstree-icon.tree, .jstree-icon.file-generic, .jstree-icon.commit {
font: normal normal 16px 'Atlassian Icons';
}
}

View File

@ -13,13 +13,13 @@ const GH_RESERVED_USER_NAMES = [
const GH_RESERVED_REPO_NAMES = ['followers', 'following', 'repositories']
const GH_404_SEL = '#parallax_wrapper'
const GH_PJAX_CONTAINER_SEL = '#js-repo-pjax-container, .context-loader-container, [data-pjax-container]'
const GH_CONTAINERS = '.container, .container-responsive'
const GH_CONTAINERS = '.container, .container-lg, .container-responsive'
const GH_RAW_CONTENT = 'body > pre'
class GitHub extends PjaxAdapter {
constructor() {
super(['jquery.pjax.js'])
constructor(store) {
super(store)
}
// @override
@ -47,7 +47,7 @@ class GitHub extends PjaxAdapter {
}
// @override
getCssClass() {
_getCssClass() {
return 'octotree_github_sidebar'
}
@ -58,7 +58,7 @@ class GitHub extends PjaxAdapter {
// @override
getCreateTokenUrl() {
return `${location.protocol}//${location.host}/settings/tokens/new`
return `${location.protocol}//${location.host}/settings/tokens/new?scopes=repo&description=Octotree%20browser%20extension`
}
// @override
@ -73,7 +73,9 @@ class GitHub extends PjaxAdapter {
}
// @override
getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
getRepoFromPath(currentRepo, token, cb) {
const showInNonCodePage = this.store.get(STORE.NONCODE)
const showOnlyChangedInPR = this.store.get(STORE.PR)
// 404 page, skip
if ($(GH_404_SEL).length) {
@ -85,14 +87,16 @@ class GitHub extends PjaxAdapter {
return cb()
}
// (username)/(reponame)[/(type)]
const match = window.location.pathname.match(/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?/)
// (username)/(reponame)[/(type)][/(typeId)]
const match = window.location.pathname.match(/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?(?:\/([^\/]+))?/)
if (!match) {
return cb()
}
const username = match[1]
const reponame = match[2]
let username = match[1]
let reponame = match[2]
let type = match[3]
let typeId = match[4]
// Not a repository, skip
if (~GH_RESERVED_USER_NAMES.indexOf(username) ||
@ -100,8 +104,12 @@ class GitHub extends PjaxAdapter {
return cb()
}
// Check if this is a PR and whether we should show changes
const isPR = type === 'pull'
const pullNumber = isPR && showOnlyChangedInPR ? typeId : null
// Skip non-code page unless showInNonCodePage is true
if (!showInNonCodePage && match[3] && !~['tree', 'blob'].indexOf(match[3])) {
if (!showInNonCodePage && type && !~['tree', 'blob'].indexOf(type)) {
return cb()
}
@ -117,8 +125,7 @@ class GitHub extends PjaxAdapter {
this._defaultBranch[username + '/' + reponame]
// Still no luck, get default branch for real
const repo = {username: username, reponame: reponame, branch: branch}
const repo = {username: username, reponame: reponame, branch: branch, pullNumber: pullNumber}
if (repo.branch) {
cb(null, repo)
}
@ -142,17 +149,106 @@ class GitHub extends PjaxAdapter {
opts.encodedBranch = encodeURIComponent(decodeURIComponent(opts.repo.branch))
opts.path = (opts.node && (opts.node.sha || opts.encodedBranch)) ||
(opts.encodedBranch + '?recursive=1')
this._loadCodeTree(opts, null, cb)
this._loadCodeTreeInternal(opts, null, cb)
}
// @override
_getTree(path, opts, cb) {
this._get(`/git/trees/${path}`, opts, (err, res) => {
if (err) cb(err)
else cb(null, res.tree)
})
if (opts.repo.pullNumber) {
this._getPatch(opts, cb)
}
else {
this._get(`/git/trees/${path}`, opts, (err, res) => {
// console.log('****', res.tree);
if (err) cb(err)
else cb(null, res.tree)
})
}
}
/**
* Get files that were patched in Pull Request.
* The diff map that is returned contains changed files, as well as the parents of the changed files.
* This allows the tree to be filtered for only folders that contain files with diffs.
* @param {Object} opts: {
* path: the starting path to load the tree,
* repo: the current repository,
* node (optional): the selected node (null to load entire tree),
* token (optional): the personal access token
* }
* @param {Function} cb(err: error, diffMap: Object)
*/
_getPatch(opts, cb) {
const {pullNumber} = opts.repo
this._get(`/pulls/${pullNumber}/files?per_page=300`, opts, (err, res) => {
if (err) cb(err)
else {
const diffMap = {}
res.forEach((file, index) => {
// record file patch info
diffMap[file.filename] = {
type: 'blob',
diffId: index,
action: file.status,
additions: file.additions,
blob_url: file.blob_url,
deletions: file.deletions,
filename: file.filename,
path: file.path,
sha: file.sha
}
// record ancestor folders
const folderPath = file.filename.split('/').slice(0, -1).join('/')
const split = folderPath.split('/')
// aggregate metadata for ancestor folders
split.reduce((path, curr) => {
if (path.length) path = `${path}/${curr}`
else path = `${curr}`
if (diffMap[path] == null) {
diffMap[path] = {
type: 'tree',
filename: path,
filesChanged: 1,
additions: file.additions,
deletions: file.deletions
}
}
else {
diffMap[path].additions += file.additions
diffMap[path].deletions += file.deletions
diffMap[path].filesChanged++
}
return path
}, '')
})
// transform to emulate response from get `tree`
const tree = Object.keys(diffMap).map(fileName => {
const patch = diffMap[fileName]
return {
patch,
path: fileName,
sha: patch.sha,
type: patch.type,
url: patch.blob_url,
}
})
// sort by path, needs to be alphabetical order (so parent folders come before children)
// note: this is still part of the above transform to mimic the behavior of get tree
tree.sort((a, b) => a.path.localeCompare(b.path))
cb(null, tree)
}
})
}
// @override
_getSubmodules(tree, opts, cb) {
const item = tree.filter((item) => /^\.gitmodules$/i.test(item.path))[0]

View File

@ -50,6 +50,25 @@
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/question.svg');
}
}
input[type=text], textarea {
padding: 4px 8px;
color: #24292e;
background-color: #fafbfc;
border: 1px solid #d1d5da;
border-radius: 3px;
outline: none;
box-shadow: rgba(27, 31, 35, 0.075) 0px 1px 2px inset;
&:focus {
border-color: #2188ff;
box-shadow: inset 0 1px 2px rgba(27,31,35,0.075), 0 0 0 0.2em rgba(3,102,214,0.3);
}
}
textarea {
padding: 6px 8px;
}
}
.octotree_treeview {
@ -70,18 +89,21 @@
top: 2px;
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/git-branch.svg');
}
.jstree-icon {
.jstree-icon.tree, .jstree-icon.commit, .jstree-icon.blob.file-generic {
margin-top: 3px;
}
.jstree-icon.tree:before {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/file-directory.svg');
}
.jstree-icon.blob:before {
.jstree-icon.file-generic:before {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/file-text.svg');
}
.jstree-node.jstree-leaf:hover {
.jstree-icon.blob:before {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/cloud-download.svg');
.jstree-icon.blob {
margin-top: 1px;
&:before {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/cloud-download.svg');
}
}
}
.jstree-icon.commit:before {
@ -101,6 +123,18 @@
background: #dbeeff;
}
}
.patch {
display: none;
}
.patch:last-of-type {
display: inline;
font-size: 80%;
span {
padding-left: 5px;
}
}
}
}

View File

@ -1,3 +1,3 @@
<svg style="fill: #777" height="16" width="16" xmlns="http://www.w3.org/2000/svg">
<svg style="fill: #AC4142" height="16" width="16" xmlns="http://www.w3.org/2000/svg">
<path d="M9 13h2l-3 3-3-3h2V8h2v5z m3-8c0-0.44-0.91-3-4.5-3-2.42 0-4.5 1.92-4.5 4C1.02 6 0 7.52 0 9c0 1.53 1 3 3 3 0.44 0 2.66 0 3 0v-1.3H3C1.38 10.7 1.3 9.28 1.3 9c0-0.17 0.05-1.7 1.7-1.7h1.3v-1.3c0-1.39 1.56-2.7 3.2-2.7 2.55 0 3.13 1.55 3.2 1.8v1.2h1.3c0.81 0 2.7 0.22 2.7 2.2 0 2.09-2.25 2.2-2.7 2.2H10v1.3c0.38 0 1.98 0 2 0 2.08 0 4-1.16 4-3.5 0-2.44-1.92-3.5-4-3.5z" />
</svg>

Before

Width:  |  Height:  |  Size: 467 B

After

Width:  |  Height:  |  Size: 470 B

View File

@ -7,187 +7,189 @@ const OSC_RAW_CONTENT = 'body > pre'
class Oschina extends PjaxAdapter {
constructor() {
super(['jquery.pjax.js'])
constructor(store) {
super(store)
}
// @override
init($sidebar) {
const pjaxContainer = $(OSC_PJAX_CONTAINER_SEL)[0]
super.init($sidebar, { 'pjaxContainer': pjaxContainer })
// Fix #151 by detecting when page layout is updated.
// In this case, split-diff page has a wider layout, so need to recompute margin.
// Note that couldn't do this in response to URL change, since new DOM via pjax might not be ready.
const diffModeObserver = new window.MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (~mutation.oldValue.indexOf('split-diff') ||
~mutation.target.className.indexOf('split-diff')) {
return $(document).trigger(EVENT.LAYOUT_CHANGE)
}
})
})
diffModeObserver.observe(document.body, {
attributes: true,
attributeFilter: ['class'],
attributeOldValue: true
})
}
// @override
_getCssClass() {
return 'octotree_oschina_sidebar'
}
// @override
canLoadEntireTree() {
return true
}
// @override
getCreateTokenUrl() {
return `https://gitee.com/api/v5/swagger`
}
// @override
updateLayout(togglerVisible, sidebarVisible, sidebarWidth) {
const SPACING = 232
const $containers = $(OSC_CONTAINERS)
const autoMarginLeft = ($(document).width() - $containers.width()) / 2
const WIDTH = $(document).width() - SPACING
const shouldPushLeft = sidebarVisible && (autoMarginLeft <= sidebarWidth + SPACING)
$('html').css('margin-left', shouldPushLeft ? sidebarWidth : '')
$containers.css('margin-left', shouldPushLeft ? SPACING : '')
$containers.css('width', shouldPushLeft ? WIDTH : '')
// $(".ui.right.floated.horizontal.list").css('margin-right', shouldPushLeft ? 210 : '')
$(".git-project-download-panel").css('margin-right', shouldPushLeft ? 240 : '')
}
// @override
getRepoFromPath(currentRepo, token, cb) {
const showInNonCodePage = this.store.get(STORE.NONCODE)
// 404 page, skip
if ($(OSC_404_SEL).length) {
return cb()
}
// @override
init($sidebar) {
const pjaxContainer = $(OSC_PJAX_CONTAINER_SEL)[0]
super.init($sidebar, { 'pjaxContainer': pjaxContainer })
// Skip raw page
if ($(OSC_RAW_CONTENT).length) {
return cb()
}
// Fix #151 by detecting when page layout is updated.
// In this case, split-diff page has a wider layout, so need to recompute margin.
// Note that couldn't do this in response to URL change, since new DOM via pjax might not be ready.
const diffModeObserver = new window.MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (~mutation.oldValue.indexOf('split-diff') ||
~mutation.target.className.indexOf('split-diff')) {
return $(document).trigger(EVENT.LAYOUT_CHANGE)
}
})
// (username)/(reponame)[/(type)]
const match = window.location.pathname.match(/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?/)
if (!match) {
return cb()
}
const username = match[1]
const reponame = match[2]
// Not a repository, skip
if (~OSC_RESERVED_USER_NAMES.indexOf(username) ||
~OSC_RESERVED_REPO_NAMES.indexOf(reponame)) {
return cb()
}
// Skip non-code page unless showInNonCodePage is true
if (!showInNonCodePage && match[3] && !~['tree', 'blob'].indexOf(match[3])) {
return cb()
}
// Get branch by inspecting page, quite fragile so provide multiple fallbacks
const branch =
// Code page
$('#git-project-branch .text').text().trim() ||
// Pull requests page
($('.commit-ref.base-ref').attr('title') || ':').match(/:(.*)/)[1] ||
// Reuse last selected branch if exist
(currentRepo.username === username && currentRepo.reponame === reponame && currentRepo.branch) ||
// Get default branch from cache
this._defaultBranch[username + '/' + reponame]
// Still no luck, get default branch for real
const repo = { username: username, reponame: reponame, branch: branch }
if (repo.branch) {
cb(null, repo)
} else {
this._get(null, { repo, token }, (err, data) => {
if (err) return cb(err)
repo.branch = this._defaultBranch[username + '/' + reponame] = data.default_branch || 'master'
cb(null, repo)
})
}
}
// @override
selectFile(path) {
console.log('select file: ' + path)
const $pjaxContainer = $(OSC_PJAX_CONTAINER_SEL)
super.selectFile(path, { '$pjaxContainer': $pjaxContainer, fragment: OSC_PJAX_CONTAINER_SEL })
}
// @override
loadCodeTree(opts, cb) {
opts.encodedBranch = encodeURIComponent(decodeURIComponent(opts.repo.branch))
opts.path = (opts.node && (opts.node.sha || opts.encodedBranch)) ||
(opts.encodedBranch + '?recursive=1')
this._loadCodeTreeInternal(opts, null, cb)
}
// @override
_getTree(path, opts, cb) {
this._get(`/git/trees/${path}`, opts, (err, res) => {
if (err) cb(err)
else cb(null, res.tree)
})
}
// @override
_getSubmodules(tree, opts, cb) {
cb()
// const item = tree.filter((item) => /^\.gitmodules$/i.test(item.path))[0]
// if (!item) return cb()
// this._get(`/git/blobs/${item.sha}`, opts, (err, res) => {
// if (err) return cb(err)
// const data = atob(res.content.replace(/\n/g, ''))
// cb(null, parseGitmodules(data))
// })
}
_get(path, opts, cb) {
const host = location.protocol + '//' + location.host
var url = `${host}/api/v5/repos/${opts.repo.username}/${opts.repo.reponame}${path || ''}`
var request = (retry) => {
if (!retry && opts.token) {
url += (url.indexOf("?") >= 0 ? "&" : "?") + `access_token=${opts.token}`
}
const cfg = {
url,
method: 'GET',
cache: false,
xhrFields: {
withCredentials: true
},
}
$.ajax(cfg)
.done((data) => {
if (path && path.indexOf('/git/trees') === 0 && data.truncated) {
this._handleError({ status: 206 }, cb)
}
else cb(null, data)
})
diffModeObserver.observe(document.body, {
attributes: true,
attributeFilter: ['class'],
attributeOldValue: true
.fail((jqXHR) => {
if (retry) {
request(false)
} else {
this._handleError(jqXHR, cb)
}
})
}
// @override
getCssClass() {
return 'octotree_oschina_sidebar'
}
// @override
canLoadEntireTree() {
return true
}
// @override
getCreateTokenUrl() {
return `https://gitee.com/api/v5/swagger`
}
// @override
updateLayout(togglerVisible, sidebarVisible, sidebarWidth) {
const SPACING = 232
const $containers = $(OSC_CONTAINERS)
const autoMarginLeft = ($(document).width() - $containers.width()) / 2
const WIDTH = $(document).width() - SPACING
const shouldPushLeft = sidebarVisible && (autoMarginLeft <= sidebarWidth + SPACING)
$('html').css('margin-left', shouldPushLeft ? sidebarWidth : '')
$containers.css('margin-left', shouldPushLeft ? SPACING : '')
$containers.css('width', shouldPushLeft ? WIDTH : '')
// $(".ui.right.floated.horizontal.list").css('margin-right', shouldPushLeft ? 210 : '')
$(".git-project-download-panel").css('margin-right', shouldPushLeft ? 240 : '')
}
// @override
getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
// 404 page, skip
if ($(OSC_404_SEL).length) {
return cb()
}
// Skip raw page
if ($(OSC_RAW_CONTENT).length) {
return cb()
}
// (username)/(reponame)[/(type)]
const match = window.location.pathname.match(/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?/)
if (!match) {
return cb()
}
const username = match[1]
const reponame = match[2]
// Not a repository, skip
if (~OSC_RESERVED_USER_NAMES.indexOf(username) ||
~OSC_RESERVED_REPO_NAMES.indexOf(reponame)) {
return cb()
}
// Skip non-code page unless showInNonCodePage is true
if (!showInNonCodePage && match[3] && !~['tree', 'blob'].indexOf(match[3])) {
return cb()
}
// Get branch by inspecting page, quite fragile so provide multiple fallbacks
const branch =
// Code page
$('#git-project-branch .text').text().trim() ||
// Pull requests page
($('.commit-ref.base-ref').attr('title') || ':').match(/:(.*)/)[1] ||
// Reuse last selected branch if exist
(currentRepo.username === username && currentRepo.reponame === reponame && currentRepo.branch) ||
// Get default branch from cache
this._defaultBranch[username + '/' + reponame]
// Still no luck, get default branch for real
const repo = { username: username, reponame: reponame, branch: branch }
if (repo.branch) {
cb(null, repo)
} else {
this._get(null, { repo, token }, (err, data) => {
if (err) return cb(err)
repo.branch = this._defaultBranch[username + '/' + reponame] = data.default_branch || 'master'
cb(null, repo)
})
}
}
// @override
selectFile(path) {
const $pjaxContainer = $(OSC_PJAX_CONTAINER_SEL)
super.selectFile(path, { '$pjaxContainer': $pjaxContainer, fragment: OSC_PJAX_CONTAINER_SEL })
}
// @override
loadCodeTree(opts, cb) {
opts.encodedBranch = encodeURIComponent(decodeURIComponent(opts.repo.branch))
opts.path = (opts.node && (opts.node.sha || opts.encodedBranch)) ||
(opts.encodedBranch + '?recursive=1')
this._loadCodeTree(opts, null, cb)
}
// @override
_getTree(path, opts, cb) {
this._get(`/git/trees/${path}`, opts, (err, res) => {
if (err) cb(err)
else cb(null, res.tree)
})
}
// @override
_getSubmodules(tree, opts, cb) {
cb()
// const item = tree.filter((item) => /^\.gitmodules$/i.test(item.path))[0]
// if (!item) return cb()
// this._get(`/git/blobs/${item.sha}`, opts, (err, res) => {
// if (err) return cb(err)
// const data = atob(res.content.replace(/\n/g, ''))
// cb(null, parseGitmodules(data))
// })
}
_get(path, opts, cb) {
const host = location.protocol + '//' + location.host
var url = `${host}/api/v5/repos/${opts.repo.username}/${opts.repo.reponame}${path || ''}`
var request = (retry) => {
if (!retry && opts.token) {
url += (url.indexOf("?") >= 0 ? "&" : "?") + `access_token=${opts.token}`
}
const cfg = {
url,
method: 'GET',
cache: false,
xhrFields: {
withCredentials: true
},
}
$.ajax(cfg)
.done((data) => {
if (path && path.indexOf('/git/trees') === 0 && data.truncated) {
this._handleError({ status: 206 }, cb)
}
else cb(null, data)
})
.fail((jqXHR) => {
if (retry) {
request(false)
} else {
this._handleError(jqXHR, cb)
}
})
}
request(true)
}
request(true)
}
}

87
src/adapters/pjax.js Normal file
View File

@ -0,0 +1,87 @@
class PjaxAdapter extends Adapter {
constructor(store) {
super(['jquery.pjax.js'], store)
$.pjax.defaults.timeout = 0 // no timeout
$(document)
.on('pjax:send', () => $(document).trigger(EVENT.REQ_START))
.on('pjax:end', () => $(document).trigger(EVENT.REQ_END))
}
// @override
// @param {Object} opts - {pjaxContainer: the specified pjax container}
// @api public
init($sidebar, opts) {
super.init($sidebar)
opts = opts || {}
const pjaxContainer = opts.pjaxContainer
if (!window.MutationObserver) return
// Some host switch pages using pjax. This observer detects if the pjax container
// has been updated with new contents and trigger layout.
const pageChangeObserver = new window.MutationObserver(() => {
// Trigger location change, can't just relayout as Octotree might need to
// hide/show depending on whether the current page is a code page or not.
return $(document).trigger(EVENT.LOC_CHANGE)
})
if (pjaxContainer) {
pageChangeObserver.observe(pjaxContainer, {
childList: true,
})
}
else { // Fall back if DOM has been changed
let firstLoad = true, href, hash
function detectLocChange() {
if (location.href !== href || location.hash !== hash) {
href = location.href
hash = location.hash
// If this is the first time this is called, no need to notify change as
// Octotree does its own initialization after loading options.
if (firstLoad) {
firstLoad = false
}
else {
setTimeout(() => {
$(document).trigger(EVENT.LOC_CHANGE)
}, 300) // Wait a bit for pjax DOM change
}
}
setTimeout(detectLocChange, 200)
}
detectLocChange()
}
}
// @override
// @param {Object} opts - {$pjax_container: jQuery object}
// @api public
selectFile(path, opts) {
opts = opts || {}
const $pjaxContainer = opts.$pjaxContainer
const fragment = opts.fragment
// if we're on the same page and just navigating to a different anchor
// don't bother fetching the page with pjax
const pathWithoutAnchor = path.replace(/#.*$/, '')
const isSamePage = location.pathname === pathWithoutAnchor
const loadWithPjax = $pjaxContainer.length && !isSamePage
if (loadWithPjax) {
$.pjax({
// needs full path for pjax to work with Firefox as per cross-domain-content setting
url: location.protocol + '//' + location.host + path,
container: $pjaxContainer,
fragment: fragment
})
}
else {
super.selectFile(path)
}
}
}

View File

@ -1,93 +1,97 @@
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status !== 'loading') return
if (changeInfo.status !== 'loading') return
chrome.tabs.executeScript(tabId, {
code: 'var injected = window.octotreeInjected; window.octotreeInjected = true; injected;',
runAt: 'document_start'
}, (res) => {
if (chrome.runtime.lastError || // don't continue if error (i.e. page isn't in permission list)
res[0]) // value of `injected` above: don't inject twice
return
chrome.tabs.executeScript(tabId, {
code: 'var injected = window.octotreeInjected; window.octotreeInjected = true; injected;',
runAt: 'document_start'
}, (res) => {
if (chrome.runtime.lastError || // don't continue if error (i.e. page isn't in permission list)
res[0]) // value of `injected` above: don't inject twice
return
const cssFiles = [
'jstree.css',
'octotree.css'
]
const cssFiles = [
'jstree.css',
'file-icons.css',
'octotree.css'
]
const jsFiles = [
'jquery.js',
'jquery-ui.js',
'jstree.js',
'keymaster.js',
'ondemand.js',
'octotree.js'
]
const jsFiles = [
'file-icons.js',
'jquery.js',
'jquery-ui.js',
'jstree.js',
'keymaster.js',
'ondemand.js',
'octotree.js'
]
eachTask([
(cb) => eachItem(cssFiles, inject('insertCSS'), cb),
(cb) => eachItem(jsFiles, inject('executeScript'), cb)
])
eachTask([
(cb) => eachItem(cssFiles, inject('insertCSS'), cb),
(cb) => eachItem(jsFiles, inject('executeScript'), cb)
])
function inject(fn) {
return (file, cb) => {
chrome.tabs[fn](tabId, { file: file, runAt: 'document_start' }, cb)
}
}
})
function inject(fn) {
return (file, cb) => {
chrome.tabs[fn](tabId, { file: file, runAt: 'document_start' }, cb)
}
}
})
})
chrome.runtime.onMessage.addListener((req, sender, sendRes) => {
const handler = {
requestPermissions: () => {
const urls = (req.urls || [])
.filter((url) => url.trim() !== '')
.map((url) => {
if (url.slice(-2) === '/*') return url
if (url.slice(-1) === '/') return url + '*'
return url + '/*'
})
const handler = {
requestPermissions: () => {
const urls = (req.urls || [])
.filter((url) => url.trim() !== '')
.map((url) => {
if (url.slice(-2) === '/*') return url
if (url.slice(-1) === '/') return url + '*'
return url + '/*'
})
if (urls.length === 0) {
sendRes(true)
removeUnnecessaryPermissions()
} else {
chrome.permissions.request({ origins: urls }, (granted) => {
sendRes(granted)
removeUnnecessaryPermissions()
})
}
return true
if (urls.length === 0) {
sendRes(true)
removeUnnecessaryPermissions()
}
else {
chrome.permissions.request({ origins: urls }, (granted) => {
sendRes(granted)
removeUnnecessaryPermissions()
})
}
return true
function removeUnnecessaryPermissions() {
const whitelist = urls.concat([
'https://github.com/*'
])
chrome.permissions.getAll((permissions) => {
const toBeRemovedUrls = permissions.origins.filter((url) => {
return !~whitelist.indexOf(url)
})
function removeUnnecessaryPermissions() {
const whitelist = urls.concat([
'https://github.com/*',
'https://bitbucket.org/*'
])
chrome.permissions.getAll((permissions) => {
const toBeRemovedUrls = permissions.origins.filter((url) => {
return !~whitelist.indexOf(url)
})
if (toBeRemovedUrls.length) {
chrome.permissions.remove({ origins: toBeRemovedUrls })
}
})
}
}
if (toBeRemovedUrls.length) {
chrome.permissions.remove({ origins: toBeRemovedUrls })
}
})
}
}
}
return handler[req.type]()
return handler[req.type]()
})
function eachTask(tasks, done) {
(function next(index = 0) {
if (index === tasks.length) done && done()
else tasks[index](() => next(++index))
})()
(function next(index = 0) {
if (index === tasks.length) done && done()
else tasks[index](() => next(++index))
})()
}
function eachItem(arr, iter, done) {
const tasks = arr.map((item) => {
return (cb) => iter(item, cb)
})
return eachTask(tasks, done)
const tasks = arr.map((item) => {
return (cb) => iter(item, cb)
})
return eachTask(tasks, done)
}

View File

@ -3,27 +3,26 @@
"version": "$VERSION",
"manifest_version": 2,
"author": "inu1255",
"description": "基于octotree的[码云](http://gitee.com)文件树插件",
"homepage_url": "http://git.oschina.net/inu1255/GitCodeTree",
"description": "基于 Octotree 的[码云](http://gitee.com)文件树插件",
"homepage_url": "https://gitee.com/inu1255/GitCodeTree",
"icons": {
"128": "icons/icon128.png"
},
"permissions": [
"https://bitbucket.org/*",
"https://github.com/*",
"http://git.oschina.net/*",
"https://git.oschina.net/*",
"http://gitee.com/*",
"https://gitee.com/*",
"https://bitbucket.org/*",
"storage"
],
"optional_permissions": [
"<all_urls>"
],
"web_accessible_resources": ["*.woff2"],
"background": {
"scripts": [
"background.js"
],
"scripts": ["background.js"],
"persistent": false
}
}

View File

@ -1,17 +0,0 @@
const data = require('sdk/self').data
const pageMod = require('sdk/page-mod')
pageMod.PageMod({
include: ['https://bitbucket.org/*', 'https://github.com/*', 'https://git.oschina.com/*', 'https://gitee.com/*'],
contentScriptFile: [data.url('jquery.js'),
data.url('jquery-ui.js'),
data.url('jstree.js'),
data.url('keymaster.js'),
data.url('ondemand.js'),
data.url('octotree.js')
],
contentStyleFile: [data.url('jstree.css'),
data.url('octotree.css')
],
contentScriptWhen: 'start'
})

View File

@ -0,0 +1,40 @@
{
"name": "Octotree",
"version": "$VERSION",
"manifest_version": 2,
"author": "inu1255",
"description": "基于 Octotree 的[码云](http://gitee.com)文件树插件",
"homepage_url": "https://gitee.com/inu1255/GitCodeTree",
"icons": {
"48": "icons/icon48.png",
"64": "icons/icon64.png"
},
"permissions": [
"https://*.github.com/*",
"https://*.bitbucket.org/*",
"storage"
],
"web_accessible_resources": ["*.woff2"],
"content_scripts": [
{
"matches": [
"https://github.com/*",
"https://bitbucket.org/*"
],
"js": [
"file-icons.js",
"jquery.js",
"jquery-ui.js",
"jstree.js",
"keymaster.js",
"ondemand.js",
"octotree.js"
],
"css": [
"file-icons.css",
"jstree.css",
"octotree.css"
]
}
]
}

View File

@ -1,25 +0,0 @@
{
"name": "GitCodeTree",
"title": "GitCodeTree",
"id": "929909260@qq.com",
"description": "基于octotree的[码云](http://gitee.com)文件树插件",
"author": "inu1255",
"homepage": "http://git.oschina.net/inu1255/GitCodeTree",
"main": "lib/firefox",
"icon": "resource://jid1-Om7eJGwA1U8Akg-at-jetpack/data/icons/icon48.png",
"icon64": "resource://jid1-Om7eJGwA1U8Akg-at-jetpack/data/icons/icon64.png",
"license": "MIT",
"version": "$VERSION",
"permissions": {
"cross-domain-content": [
"https://api.bitbucket.org",
"https://api.github.com",
"https://bitbucket.org",
"https://git.oschina.net",
"https://gitee.com",
"https://github.com"
],
"private-browsing": true,
"multiprocess": true
}
}

View File

@ -48,7 +48,7 @@
</array>
</dict>
<key>Description</key>
<string>基于octotree的[码云](http://git.oschina.net)文件树插件</string>
<string>基于 Octotree 的[码云](http://git.oschina.net)文件树插件</string>
<key>ExtensionInfoDictionaryVersion</key>
<string>1.0</string>
<key>Permissions</key>

View File

@ -6,7 +6,9 @@ const STORE = {
TOKEN : 'octotree.access_token',
REMEMBER : 'octotree.remember',
NONCODE : 'octotree.noncode_shown',
PR : 'octotree.pr_shown',
HOTKEYS : 'octotree.hotkeys',
ICONS : 'octotree.icons',
LOADALL : 'octotree.loadall',
POPUP : 'octotree.popup_shown',
WIDTH : 'octotree.sidebar_width',
@ -19,8 +21,10 @@ const DEFAULTS = {
TOKEN : '',
REMEMBER : true,
NONCODE : true,
PR : true,
LOADALL : true,
HOTKEYS : '⌘+⇧+s, ⌃+⇧+s',
ICONS : true,
POPUP : false,
WIDTH : 232,
SHOWN : false,

View File

@ -1,201 +1,199 @@
$(document).ready(() => {
const store = new Storage()
const store = new Storage()
parallel(Object.keys(STORE), setDefault, loadExtension)
parallel(Object.keys(STORE), setDefault, loadExtension)
function setDefault(key, cb) {
const storeKey = STORE[key]
store.get(storeKey, (val) => {
store.set(storeKey, val == null ? DEFAULTS[key] : val, cb)
function setDefault(key, cb) {
const storeKey = STORE[key]
store.get(storeKey, (val) => {
store.set(storeKey, val == null ? DEFAULTS[key] : val, cb)
})
}
function createAdapter() {
const normalizeUrl = (url) => url.replace(/(.*?:\/\/[^/]+)(.*)/, '$1')
const githubUrls = store.get(STORE.GHEURLS).split(/\n/)
.map(normalizeUrl)
.concat('https://github.com')
const bitbucketUrls = ['https://bitbucket.org']
const oschinaUrls = [
'http://git.oschina.net', 'https://git.oschina.net',
'http://gitee.com', 'https://gitee.com'
]
const currentUrl = `${location.protocol}//${location.host}`
if (~oschinaUrls.indexOf(currentUrl)) {
return new Oschina(store)
} else if (~githubUrls.indexOf(currentUrl)) {
return new GitHub(store)
} else if (~bitbucketUrls.indexOf(currentUrl)) {
return new Bitbucket(store)
}
}
function loadExtension() {
const $html = $('html')
const $document = $(document)
const $dom = $(TEMPLATE)
const $sidebar = $dom.find('.octotree_sidebar')
const $toggler = $sidebar.find('.octotree_toggle')
const $views = $sidebar.find('.octotree_view')
const adapter = createAdapter()
const treeView = new TreeView($dom, store, adapter)
const optsView = new OptionsView($dom, store)
const helpPopup = new HelpPopup($dom, store)
const errorView = new ErrorView($dom, store)
let currRepo = false
let hasError = false
$html.addClass(ADDON_CLASS)
$(window).resize((event) => {
if (event.target === window) layoutChanged()
})
$toggler.click(toggleSidebarAndSave)
key.filter = () => $toggler.is(':visible')
key(store.get(STORE.HOTKEYS), toggleSidebarAndSave)
const views = [treeView, errorView, optsView]
views.forEach((view) => {
$(view)
.on(EVENT.VIEW_READY, function (event) {
if (this !== optsView) {
$document.trigger(EVENT.REQ_END)
}
showView(this.$view)
})
.on(EVENT.VIEW_CLOSE, () => showView(hasError ? errorView.$view : treeView.$view))
.on(EVENT.OPTS_CHANGE, optionsChanged)
.on(EVENT.FETCH_ERROR, (event, err) => showError(err))
})
$document
.on(EVENT.REQ_START, () => $toggler.addClass('octotree_loading'))
.on(EVENT.REQ_END, () => $toggler.removeClass('octotree_loading'))
.on(EVENT.LAYOUT_CHANGE, layoutChanged)
.on(EVENT.TOGGLE, layoutChanged)
.on(EVENT.LOC_CHANGE, () => tryLoadRepo())
$sidebar
.width(parseInt(store.get(STORE.WIDTH)))
.resize(() => layoutChanged(true))
.appendTo($('body'))
adapter.init($sidebar)
return tryLoadRepo()
function optionsChanged(event, changes) {
let reload = false
Object.keys(changes).forEach((storeKey) => {
const value = changes[storeKey]
switch (storeKey) {
case STORE.TOKEN:
case STORE.LOADALL:
case STORE.ICONS:
reload = true
break
case STORE.HOTKEYS:
key.unbind(value[0])
key(value[1], toggleSidebar)
break
}
})
if (reload) {
tryLoadRepo(true)
}
}
function createAdapter() {
const normalizeUrl = (url) => url.replace(/(.*?:\/\/[^/]+)(.*)/, '$1')
function tryLoadRepo(reload) {
hasError = false
const remember = store.get(STORE.REMEMBER)
const shown = store.get(STORE.SHOWN)
const token = store.get(STORE.TOKEN)
const githubUrls = store.get(STORE.GHEURLS).split(/\n/)
.map(normalizeUrl)
.concat('https://github.com')
const bitbucketUrls = ['https://bitbucket.org']
const oschinaUrls = ['http://git.oschina.net', 'https://git.oschina.net', 'http://gitee.com', 'https://gitee.com']
const currentUrl = `${location.protocol}//${location.host}`
if (oschinaUrls.indexOf(currentUrl) >= 0) {
return new Oschina(store)
} else if (~githubUrls.indexOf(currentUrl)) {
return new GitHub(store)
} else if (~bitbucketUrls.indexOf(currentUrl)) {
return new Bitbucket(store)
adapter.getRepoFromPath(currRepo, token, (err, repo) => {
if (err) {
showError(err)
}
}
else if (repo) {
$toggler.show()
function loadExtension() {
const $html = $('html')
const $document = $(document)
const $dom = $(TEMPLATE)
const $sidebar = $dom.find('.octotree_sidebar')
const $toggler = $sidebar.find('.octotree_toggle')
const $views = $sidebar.find('.octotree_view')
const adapter = createAdapter()
const treeView = new TreeView($dom, store, adapter)
const optsView = new OptionsView($dom, store)
const helpPopup = new HelpPopup($dom, store)
const errorView = new ErrorView($dom, store)
let currRepo = false
let hasError = false
if (remember && shown) {
toggleSidebar(true)
}
$html.addClass(ADDON_CLASS)
$(window).resize((event) => {
if (event.target === window) layoutChanged()
})
$toggler.click(toggleSidebarAndSave)
key.filter = () => $toggler.is(':visible')
key(store.get(STORE.HOTKEYS), toggleSidebarAndSave)
const views = [treeView, errorView, optsView]
views.forEach((view) => {
$(view)
.on(EVENT.VIEW_READY, function(event) {
if (this !== optsView) {
$document.trigger(EVENT.REQ_END)
}
showView(this.$view)
})
.on(EVENT.VIEW_CLOSE, () => showView(hasError ? errorView.$view : treeView.$view))
.on(EVENT.OPTS_CHANGE, optionsChanged)
.on(EVENT.FETCH_ERROR, (event, err) => showError(err))
})
$document
.on(EVENT.REQ_START, () => $toggler.addClass('octotree_loading'))
.on(EVENT.REQ_END, () => $toggler.removeClass('octotree_loading'))
.on(EVENT.LAYOUT_CHANGE, layoutChanged)
.on(EVENT.TOGGLE, layoutChanged)
.on(EVENT.LOC_CHANGE, () => tryLoadRepo())
$sidebar
.width(parseInt(store.get(STORE.WIDTH)))
.resize(layoutChanged)
.appendTo($('body'))
var prev = location.href
function aa() {
setTimeout(function() {
if (prev != location.href) {
tryLoadRepo()
prev = location.href
}
aa()
}, 500)
}
aa()
adapter.init($sidebar)
return tryLoadRepo()
function optionsChanged(event, changes) {
let reload = false
Object.keys(changes).forEach((storeKey) => {
const value = changes[storeKey]
switch (storeKey) {
case STORE.TOKEN:
case STORE.LOADALL:
reload = true
break
case STORE.HOTKEYS:
key.unbind(value[0])
key(value[1], toggleSidebar)
break
}
})
if (reload) {
tryLoadRepo(true)
if (isSidebarVisible()) {
const replacer = ['username', 'reponame', 'branch', 'pullNumber']
const repoChanged = JSON.stringify(repo, replacer) !== JSON.stringify(currRepo, replacer)
if (repoChanged || reload === true) {
$document.trigger(EVENT.REQ_START)
currRepo = repo
treeView.show(repo, token)
}
}
function tryLoadRepo(reload) {
hasError = false
const remember = store.get(STORE.REMEMBER)
const showInNonCodePage = store.get(STORE.NONCODE)
const shown = store.get(STORE.SHOWN)
const token = store.get(STORE.TOKEN)
adapter.getRepoFromPath(showInNonCodePage, currRepo, token, (err, repo) => {
if (err) {
showError(err)
} else if (repo) {
$toggler.show()
if (remember && shown) {
toggleSidebar(true)
}
if (isSidebarVisible()) {
const replacer = ['username', 'reponame', 'branch']
const repoChanged = JSON.stringify(repo, replacer) !== JSON.stringify(currRepo, replacer)
if (repoChanged || reload === true) {
$document.trigger(EVENT.REQ_START)
currRepo = repo
treeView.show(repo, token)
} else {
treeView.syncSelection()
}
}
} else {
$toggler.hide()
toggleSidebar(false)
}
helpPopup.init()
layoutChanged()
})
}
function showView(view) {
$views.removeClass('current')
view.addClass('current')
}
function showError(err) {
hasError = true
errorView.show(err)
}
function toggleSidebarAndSave() {
store.set(STORE.SHOWN, !isSidebarVisible(), () => {
toggleSidebar()
if (isSidebarVisible()) {
tryLoadRepo()
}
})
}
function toggleSidebar(visibility) {
if (visibility !== undefined) {
if (isSidebarVisible() === visibility) return
toggleSidebar()
} else {
$html.toggleClass(SHOW_CLASS)
$document.trigger(EVENT.TOGGLE, isSidebarVisible())
else {
treeView.syncSelection()
}
}
}
function layoutChanged() {
const width = $sidebar.outerWidth()
adapter.updateLayout(isTogglerVisible(), isSidebarVisible(), width)
store.set(STORE.WIDTH, width)
}
function isSidebarVisible() {
return $html.hasClass(SHOW_CLASS)
}
function isTogglerVisible() {
return $toggler.is(':visible')
else {
$toggler.hide()
toggleSidebar(false)
}
helpPopup.init()
layoutChanged()
})
}
function showView(view) {
$views.removeClass('current')
view.addClass('current')
}
function showError(err) {
hasError = true
errorView.show(err)
}
function toggleSidebarAndSave() {
store.set(STORE.SHOWN, !isSidebarVisible(), () => {
toggleSidebar()
if (isSidebarVisible()) {
tryLoadRepo()
}
})
}
function toggleSidebar(visibility) {
if (visibility !== undefined) {
if (isSidebarVisible() === visibility) return
toggleSidebar()
}
else {
$html.toggleClass(SHOW_CLASS)
$document.trigger(EVENT.TOGGLE, isSidebarVisible())
}
}
function layoutChanged(save = false) {
const width = $sidebar.outerWidth()
adapter.updateLayout(isTogglerVisible(), isSidebarVisible(), width)
if (save === true) {
store.set(STORE.WIDTH, width)
}
}
function isSidebarVisible() {
return $html.hasClass(SHOW_CLASS)
}
function isTogglerVisible() {
return $toggler.is(':visible')
}
}
})

View File

@ -116,6 +116,7 @@
}
.jstree-default .jstree-icon, .jstree-default .jstree-icon:empty, .jstree-default .jstree-anchor {
line-height: 24px !important;
font-style: normal;
}
}
@ -169,6 +170,13 @@
height: 60px;
}
}
.octotree_opts_disclaimer {
color: gray;
display: block;
font-size: 12px;
margin-left: 17px;
}
}
}

View File

@ -69,6 +69,14 @@
<label><input type="checkbox" data-store="LOADALL"> 自动加载</label>
</div>
<div class="octotree_github_only">
<label>
<input type="checkbox" data-store="PR">
Show only pull request changes
<span class="octotree_opts_disclaimer">Note: maximum of 300 files</span>
</label>
</div>
<div>
<button type="submit" class="btn">保存</button>
</div>

View File

@ -1,6 +1,13 @@
class Storage {
set(key, val, cb) {
localStorage.setItem(key, JSON.stringify(val))
try {
localStorage.setItem(key, JSON.stringify(val))
}
catch (e) {
const msg = 'Octotree cannot save its settings. ' +
'If the local storage for this domain is full, please clean it up and try again.'
console.error(msg, e)
}
if (cb) cb()
}

View File

@ -47,7 +47,7 @@ class OptionsView {
* Certainly not a good place to put this logic but Chrome requires
* permissions to be requested only in response of user input. So...
*/
// @ifdef CHROME
// @ifdef SUPPORT_GHE
const $ta = this.$view.find('[data-store$=EURLS]').filter(':visible')
if ($ta.length > 0) {
const storeKey = $ta.data('store')

View File

@ -21,8 +21,9 @@ class TreeView {
const $jstree = this.$jstree
$jstree.settings.core.data = (node, cb) => {
const loadAll = this.adapter.canLoadEntireTree() &&
this.store.get(STORE.LOADALL)
const prMode = this.store.get(STORE.PR) && repo.pullNumber
const loadAll = this.adapter.canLoadEntireTree() && (prMode || this.store.get(STORE.LOADALL))
node = !loadAll && (node.id === '#' ? {path: ''} : node.original)
this.adapter.loadCodeTree({repo, token, node}, (err, treeData) => {
@ -138,7 +139,9 @@ class TreeView {
}
else if ($icon.hasClass('blob')) {
if (download) {
adapter.downloadFile(href, $target.text())
const downloadUrl = $target.attr('data-download-url')
const downloadFileName = $target.attr('data-download-filename')
adapter.downloadFile(downloadUrl, downloadFileName)
}
else {
refocusAfterCompletion()