码云git
This commit is contained in:
parent
770e1e145f
commit
c855e8fdb1
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -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'
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
"permissions": [
|
||||
"https://bitbucket.org/*",
|
||||
"https://github.com/*",
|
||||
"http://git.oschina.net/*",
|
||||
"storage"
|
||||
],
|
||||
"optional_permissions": [
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
|
|
342
src/octotree.js
342
src/octotree.js
|
@ -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')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
@import "base";
|
||||
@import "../adapters/bitbucket";
|
||||
@import "../adapters/github";
|
||||
@import "../adapters/oschina";
|
||||
|
||||
|
|
Loading…
Reference in New Issue