码云git

This commit is contained in:
inu1255 2017-06-15 16:05:27 +08:00
parent 770e1e145f
commit c855e8fdb1
13 changed files with 646 additions and 282 deletions

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/opera.nex vendored

Binary file not shown.

BIN
dist/safari.safariextz vendored

Binary file not shown.

View File

@ -1,208 +1,209 @@
import gulp from 'gulp'
import fs from 'fs'
import path from 'path'
import {merge} from 'event-stream'
import { merge } from 'event-stream'
import map from 'map-stream'
import {spawn} from 'child_process'
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/**/*'], ['default'])
})
gulp.task('dist', ['build'], (cb) => {
$.runSequence('firefox:xpi', 'chrome:zip', 'chrome:crx', 'opera:nex', cb)
$.runSequence('firefox:xpi', '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 () {
const dir = './libs/ondemand'
const code = fs.readdirSync(dir).map(file => {
return `window['${file}'] = function () {
${fs.readFileSync(path.join(dir, file))}
};\n`
}).join('')
}).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({ CHROME: 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'], { CHROME: 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('./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/')
)
})
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)
$.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', ['chrome:_crx'], () => {
return pipe('./tmp/chrome.crx', './dist')
return pipe('./tmp/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({ FIREFOX: true })
})
gulp.task('firefox:js', ['firefox:template', 'lib:ondemand'], () => {
return buildJs([], {FIREFOX: true})
return buildJs([], { FIREFOX: 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/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')
)
})
gulp.task('firefox:xpi', (cb) => {
$.run('cd ./tmp/firefox && ../../node_modules/.bin/jpm xpi && mkdir -p ../../dist && mv jid1-Om7eJGwA1U8Akg*.xpi ../../dist/firefox.xpi').exec(cb)
$.run('cd ./tmp/firefox && ../../node_modules/.bin/jpm xpi && mkdir -p ../../dist && mv jid1-Om7eJGwA1U8Akg*.xpi ../../dist/firefox.xpi').exec(cb)
})
// Safari
gulp.task('safari:template', () => {
return buildTemplate({SAFARI: true})
return buildTemplate({ SAFARI: true })
})
gulp.task('safari:js', ['safari:template', 'lib:ondemand'], () => {
return buildJs([], {SAFARI: true})
return buildJs([], { SAFARI: true })
})
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', version), './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/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/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'
)
}

187
src/adapters/oschina.js Normal file
View File

@ -0,0 +1,187 @@
const OSC_RESERVED_USER_NAMES = [
'settings', 'orgs', 'organizations',
'site', 'blog', 'about', 'explore',
'styleguide', 'showcases', 'trending',
'stars', 'dashboard', 'notifications',
'search', 'developer', 'account',
'pulls', 'issues', 'features', 'contact',
'security', 'join', 'login', 'watching',
'new', 'integrations', 'gist', 'business',
'mirrors', 'open-source', 'personal',
'pricing'
]
const OSC_RESERVED_REPO_NAMES = ['followers', 'following', 'repositories']
const OSC_404_SEL = '#parallax_wrapper'
const OSC_PJAX_CONTAINER_SEL = '.git-project-content-wrapper, .context-loader-container, [data-pjax-container]'
const OSC_CONTAINERS = '#git-header-nav'
const OSC_RAW_CONTENT = 'body > pre'
class Oschina extends PjaxAdapter {
constructor() {
super(['jquery.pjax.js'])
}
// @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 `${location.protocol}//${location.host}/settings/tokens/new`
}
// @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 : '')
}
// @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
$('.branch-select-menu .select-menu-item.selected').data('name') ||
// 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 })
}
// @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) {
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 = 'http://git.oschina.net/api/v5'
var url = `${host}/repos/${opts.repo.username}/${opts.repo.reponame}${path || ''}`
const cfg = { url, method: 'GET', cache: false }
if (opts.token) {
url += (url.endsWith("?") ? "&" : "?") + `access_token=${opts.token}`
}
$.ajax(cfg)
.done((data) => {
if (path && path.indexOf('/git/trees') === 0 && data.truncated) {
this._handleError({ status: 206 }, cb)
}
else cb(null, data)
})
.fail((jqXHR) => this._handleError(jqXHR, cb))
}
}

173
src/adapters/oschina.less Normal file
View File

@ -0,0 +1,173 @@
.octotree-show {
.octotree_oschina_only {
display: none;
}
.octotree_oschina_sidebar {
a.octotree_toggle {
top: 11px;
right: 12px;
&:not(.octotree_loading) > span:after {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/chevron-left.svg');
}
&:not(.octotree_loading):hover > span:after {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/chevron-left-hover.svg');
}
}
}
body.split-diff .container {
padding-left: 0;
}
}
.octotree_oschina_sidebar {
padding-top: 54px;
background-color: #f7f7f7;
border-right: none;
.octotree_oschina_only {
display: block;
}
.octotree_views {
border-right: 1px solid #ddd;
background-color: #fff;
.octotree_view {
.octotree_view_header {
font-weight: normal;
text-shadow: none;
height: 46px;
line-height: 2.4;
background: #303643;
background: #fafbfc none;
border-right: 1px solid #e1e4e8;
border-bottom: 1px solid #e1e4e8;
}
.octotree_help {
& > span:before {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/question.svg');
}
}
}
.octotree_treeview {
.octotree_header_repo {
font-size: 13px;
font-weight: normal;
}
.octotree_header_repo:before {
position: relative;
top: 2px;
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/repo.svg');
}
.octotree_header_branch {
font-size: 11px;
}
.octotree_header_branch:before {
position: relative;
top: 2px;
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/git-branch.svg');
}
.jstree-icon {
margin-top: 3px;
}
.jstree-icon.tree:before {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/file-directory.svg');
}
.jstree-icon.blob: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.commit:before {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/file-submodule.svg');
}
.jstree-anchor {
color: #0366d6 !important;
& > span {
color: black;
}
}
.jstree-default {
.jstree-wholerow-hovered {
background: #eee;
}
.jstree-wholerow-clicked {
background: #dbeeff;
}
}
}
}
a.octotree_toggle, a.octotree_opts {
color: black !important;
&:hover, &.selected {
color: #0366d6 !important;
}
}
a.octotree_opts {
top: 15px;
right: 48px;
width: 14px;
height: 16px;
background: data-uri('image/svg+xml;charset=UTF-8', './octicons/gear.svg');
&:hover {
background: data-uri('image/svg+xml;charset=UTF-8', './octicons/gear-hover.svg');
}
}
a.octotree_toggle {
top: 11px;
right: -35px;
&:not(.octotree_loading) > span:after {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/chevron-right.svg');
}
&:not(.octotree_loading):hover > span:after {
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/chevron-right-hover.svg');
}
&.btn{
position: relative;
display: inline-block;
padding: 6px 12px;
font-size: 14px;
font-weight: 600;
line-height: 20px;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
background-repeat: repeat-x;
background-position: -1px -1px;
background-size: 110% 110%;
border: 1px solid rgba(27,31,35,0.2);
border-radius: 0.25em;
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
height: 23px;
padding-top: 3px !important;
background: #FAFBFC;
&:hover {
background-color: #e6ebf1;
background-image: -webkit-linear-gradient(270deg, #f0f3f6 0%, #e6ebf1 90%);
background-image: linear-gradient(-180deg, #f0f3f6 0%, #e6ebf1 90%);
background-position: 0 -0.5em;
border-color: rgba(27,31,35,0.35);
text-decoration: none;
background-repeat: repeat-x;
}
}
}
}

View File

@ -11,6 +11,7 @@
"permissions": [
"https://bitbucket.org/*",
"https://github.com/*",
"http://git.oschina.net/*",
"storage"
],
"optional_permissions": [

View File

@ -15,6 +15,7 @@
"https://api.bitbucket.org",
"https://api.github.com",
"https://bitbucket.org",
"http://git.oschina.net",
"https://github.com"
],
"private-browsing": true,

View File

@ -41,6 +41,7 @@
<array>
<string>https://bitbucket.org/*</string>
<string>https://github.com/*</string>
<string>http://git.oschina.net/*</string>
</array>
</dict>
<key>Description</key>

View File

@ -1,192 +1,190 @@
$(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 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 currentUrl = `${location.protocol}//${location.host}`
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)
function setDefault(key, cb) {
const storeKey = STORE[key]
store.get(storeKey, (val) => {
store.set(storeKey, val == null ? DEFAULTS[key] : val, cb)
})
.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'))
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)
}
}
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)
function createAdapter() {
const normalizeUrl = (url) => url.replace(/(.*?:\/\/[^/]+)(.*)/, '$1')
adapter.getRepoFromPath(showInNonCodePage, currRepo, token, (err, repo) => {
if (err) {
showError(err)
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']
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)
}
else if (repo) {
$toggler.show()
}
if (remember && shown) {
toggleSidebar(true)
}
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 (isSidebarVisible()) {
const replacer = ['username', 'reponame', 'branch']
const repoChanged = JSON.stringify(repo, replacer) !== JSON.stringify(currRepo, replacer)
$html.addClass(ADDON_CLASS)
if (repoChanged || reload === true) {
$document.trigger(EVENT.REQ_START)
currRepo = repo
treeView.show(repo, token)
$(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'))
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)
}
else {
treeView.syncSelection()
}
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 {
$toggler.hide()
toggleSidebar(false)
function layoutChanged() {
const width = $sidebar.outerWidth()
adapter.updateLayout(isTogglerVisible(), isSidebarVisible(), width)
store.set(STORE.WIDTH, width)
}
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 isSidebarVisible() {
return $html.hasClass(SHOW_CLASS)
}
})
}
function toggleSidebar(visibility) {
if (visibility !== undefined) {
if (isSidebarVisible() === visibility) return
toggleSidebar()
}
else {
$html.toggleClass(SHOW_CLASS)
$document.trigger(EVENT.TOGGLE, isSidebarVisible())
}
function isTogglerVisible() {
return $toggler.is(':visible')
}
}
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')
}
}
})

View File

@ -1,3 +1,5 @@
@import "base";
@import "../adapters/bitbucket";
@import "../adapters/github";
@import "../adapters/oschina";