Support BitBucket
This commit is contained in:
parent
c5cfdb4e52
commit
770e1e145f
|
@ -48,6 +48,11 @@ Octotree uses [GitHub API](https://developer.github.com/v3/) to retrieve reposit
|
||||||
|
|
||||||
When that happens, Octotree will ask for your [GitHub personal access token](https://help.github.com/articles/creating-an-access-token-for-command-line-use). If you don't already have one, [create one](https://github.com/settings/tokens/new), then copy and paste it into the textbox. Note that the minimal scopes that should be granted are `public_repo` and `repo` (if you need access to private repositories).
|
When that happens, Octotree will ask for your [GitHub personal access token](https://help.github.com/articles/creating-an-access-token-for-command-line-use). If you don't already have one, [create one](https://github.com/settings/tokens/new), then copy and paste it into the textbox. Note that the minimal scopes that should be granted are `public_repo` and `repo` (if you need access to private repositories).
|
||||||
|
|
||||||
|
#### Bitbucket
|
||||||
|
Octotree uses [Bitbucket API](https://confluence.atlassian.com/bitbucket/repositories-endpoint-1-0-296092719.html) to retrieve repository metadata. By defualt, Octotree will ask for your [Bitbucket App password](https://confluence.atlassian.com/bitbucket/app-passwords-828781300.html). If you don't already have one, [create one](https://bitbucket.org/account/admin/app-passwords) (the minimal requirement is `Repositories`'s `Read` permission), then copy and paste it into the textbox.
|
||||||
|
|
||||||
|
Note that Octotree extract your username from your current page by default for calling Bitbucket API. If fail to extract, Octotree will ask you for a token update, then you just need to prepend your username to the token, separated by a colon, i.e. `USERNAME:TOKEN`.
|
||||||
|
|
||||||
### Enterprise URLs
|
### Enterprise URLs
|
||||||
By default, Octotree only works on `github.com`. To support enterprise version (Chrome and Opera only), you must grant Octotree sufficient permissions. Follow these steps to do so:
|
By default, Octotree only works on `github.com`. To support enterprise version (Chrome and Opera only), you must grant Octotree sufficient permissions. Follow these steps to do so:
|
||||||
|
|
||||||
|
|
|
@ -173,6 +173,7 @@ function buildJs(overrides, ctx) {
|
||||||
'./tmp/template.js',
|
'./tmp/template.js',
|
||||||
'./src/constants.js',
|
'./src/constants.js',
|
||||||
'./src/adapters/adapter.js',
|
'./src/adapters/adapter.js',
|
||||||
|
'./src/adapters/bitbucket.js',
|
||||||
'./src/adapters/github.js',
|
'./src/adapters/github.js',
|
||||||
'./src/view.help.js',
|
'./src/view.help.js',
|
||||||
'./src/view.error.js',
|
'./src/view.error.js',
|
||||||
|
|
|
@ -76,7 +76,7 @@ class Adapter {
|
||||||
// encodes but retains the slashes, see #274
|
// encodes but retains the slashes, see #274
|
||||||
const encodedPath = path.split('/').map(encodeURIComponent).join('/')
|
const encodedPath = path.split('/').map(encodeURIComponent).join('/')
|
||||||
item.a_attr = {
|
item.a_attr = {
|
||||||
href: `/${repo.username}/${repo.reponame}/${type}/${repo.branch}/${encodedPath}`
|
href: this._getItemHref(repo, type, path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (type === 'commit') {
|
else if (type === 'commit') {
|
||||||
|
@ -273,7 +273,7 @@ class Adapter {
|
||||||
*/
|
*/
|
||||||
downloadFile(path, fileName) {
|
downloadFile(path, fileName) {
|
||||||
const link = document.createElement('a')
|
const link = document.createElement('a')
|
||||||
link.setAttribute('href', path.replace(/\/blob\//, '/raw/'))
|
link.setAttribute('href', path.replace(/\/blob\/|\/src\//, '/raw/'))
|
||||||
link.setAttribute('download', fileName)
|
link.setAttribute('download', fileName)
|
||||||
link.click()
|
link.click()
|
||||||
}
|
}
|
||||||
|
@ -295,4 +295,95 @@ class Adapter {
|
||||||
_getSubmodules(tree, opts, cb) {
|
_getSubmodules(tree, opts, cb) {
|
||||||
throw new Error('Not implemented')
|
throw new Error('Not implemented')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setTimeout(detectLocChange, 200)
|
||||||
|
}
|
||||||
|
|
||||||
|
detectLocChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// @override
|
||||||
|
// @param {Object} opts - {$pjax_container: jQuery object}
|
||||||
|
// @api public
|
||||||
|
selectFile(path, opts) {
|
||||||
|
opts = opts || {}
|
||||||
|
const $pjaxContainer = opts.$pjaxContainer
|
||||||
|
|
||||||
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else { // falls back
|
||||||
|
super.selectFile(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,212 @@
|
||||||
|
const BB_RESERVED_USER_NAMES = [
|
||||||
|
'account', 'dashboard', 'integrations', 'product',
|
||||||
|
'repo', 'snippets', 'support', 'whats-new'
|
||||||
|
]
|
||||||
|
const BB_RESERVED_REPO_NAMES = []
|
||||||
|
const BB_RESERVED_TYPES = ['raw']
|
||||||
|
const BB_404_SEL = '#error.404'
|
||||||
|
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]
|
||||||
|
super.init($sidebar, {'pjaxContainer': pjaxContainer})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
getCssClass() {
|
||||||
|
return 'octotree_bitbucket_sidebar'
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
getCreateTokenUrl() {
|
||||||
|
return `${location.protocol}//${location.host}/account/admin/app-passwords/new`
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
updateLayout(togglerVisible, sidebarVisible, sidebarWidth) {
|
||||||
|
$('.octotree_toggle').css('right', sidebarVisible ? '' : -44)
|
||||||
|
$('.aui-header').css('padding-left', sidebarVisible ? '' : 56)
|
||||||
|
$('html').css('margin-left', sidebarVisible ? sidebarWidth : '')
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
|
||||||
|
|
||||||
|
// 404 page, skip
|
||||||
|
if ($(BB_404_SEL).length) {
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
// (username)/(reponame)[/(type)]
|
||||||
|
const match = window.location.pathname.match(/([^\/]+)\/([^\/]+)(?:\/([^\/]+))?/)
|
||||||
|
if (!match) {
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
const username = match[1]
|
||||||
|
const reponame = match[2]
|
||||||
|
const type = match[3]
|
||||||
|
|
||||||
|
// Not a repository, skip
|
||||||
|
if (~BB_RESERVED_USER_NAMES.indexOf(username) ||
|
||||||
|
~BB_RESERVED_REPO_NAMES.indexOf(reponame) ||
|
||||||
|
~BB_RESERVED_TYPES.indexOf(type)) {
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip non-code page unless showInNonCodePage is true
|
||||||
|
// with Bitbucket /username/repo is non-code page
|
||||||
|
if (!showInNonCodePage &&
|
||||||
|
(!type || (type && type !== 'src'))) {
|
||||||
|
return cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get branch by inspecting page, quite fragile so provide multiple fallbacks
|
||||||
|
const BB_BRANCH_SEL_1 = '.branch-dialog-trigger'
|
||||||
|
|
||||||
|
const branch =
|
||||||
|
// Code page
|
||||||
|
$(BB_BRANCH_SEL_1).attr('title') ||
|
||||||
|
// Assume same with previously
|
||||||
|
(currentRepo.username === username && currentRepo.reponame === reponame && currentRepo.branch) ||
|
||||||
|
// Default from cache
|
||||||
|
this._defaultBranch[username + '/' + reponame]
|
||||||
|
|
||||||
|
const repo = {username: username, reponame: reponame, branch: branch}
|
||||||
|
|
||||||
|
if (repo.branch) {
|
||||||
|
cb(null, repo)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._get('/main-branch', {repo, token}, (err, data) => {
|
||||||
|
if (err) return cb(err)
|
||||||
|
repo.branch = this._defaultBranch[username + '/' + reponame] = data.name || 'master'
|
||||||
|
cb(null, repo)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
selectFile(path) {
|
||||||
|
const $pjaxContainer = $(BB_PJAX_CONTAINER_SEL)
|
||||||
|
super.selectFile(path, {'$pjaxContainer': $pjaxContainer})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
loadCodeTree(opts, cb) {
|
||||||
|
opts.path = opts.node.path
|
||||||
|
this._loadCodeTree(opts, (item) => {
|
||||||
|
if (!item.type) {
|
||||||
|
item.type = 'blob'
|
||||||
|
}
|
||||||
|
}, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
_getTree(path, opts, cb) {
|
||||||
|
this._get(`/src/${opts.repo.branch}/${path}`, opts, (err, res) => {
|
||||||
|
if (err) return cb(err)
|
||||||
|
const directories = res.directories.map((dir) => ({path: dir, type: 'tree'}))
|
||||||
|
res.files.forEach((file) => {
|
||||||
|
if (file.path.startsWith(res.path)) {
|
||||||
|
file.path = file.path.substring(res.path.length)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const tree = res.files.concat(directories)
|
||||||
|
cb(null, tree)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
_getSubmodules(tree, opts, cb) {
|
||||||
|
if (opts.repo.submodules) {
|
||||||
|
return this._getSubmodulesInCurrentPath(tree, opts, cb)
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = tree.filter((item) => /^\.gitmodules$/i.test(item.path))[0]
|
||||||
|
if (!item) return cb()
|
||||||
|
|
||||||
|
this._get(`/src/${opts.encodedBranch}/${item.path}`, opts, (err, res) => {
|
||||||
|
if (err) return cb(err)
|
||||||
|
// Memoize submodules so that they will be inserted into the tree later.
|
||||||
|
opts.repo.submodules = parseGitmodules(res.data)
|
||||||
|
this._getSubmodulesInCurrentPath(tree, opts, cb)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
_getSubmodulesInCurrentPath(tree, opts, cb) {
|
||||||
|
const currentPath = opts.path
|
||||||
|
const isInCurrentPath = currentPath
|
||||||
|
? (path) => path.startsWith(`${currentPath}/`)
|
||||||
|
: (path) => path.indexOf('/') === -1
|
||||||
|
|
||||||
|
const submodules = opts.repo.submodules
|
||||||
|
const submodulesInCurrentPath = {}
|
||||||
|
Object.keys(submodules).filter(isInCurrentPath).forEach((key) => {
|
||||||
|
submodulesInCurrentPath[key] = submodules[key]
|
||||||
|
})
|
||||||
|
|
||||||
|
// Insert submodules in current path into the tree because submodules can not
|
||||||
|
// be retrieved with Bitbucket API but can only by reading .gitmodules.
|
||||||
|
Object.keys(submodulesInCurrentPath).forEach((path) => {
|
||||||
|
if (currentPath) {
|
||||||
|
// `currentPath` is prefixed to `path`, so delete it.
|
||||||
|
path = path.substring(currentPath.length + 1)
|
||||||
|
}
|
||||||
|
tree.push({path: path, type: 'commit'})
|
||||||
|
})
|
||||||
|
cb(null, submodulesInCurrentPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
_get(path, opts, cb) {
|
||||||
|
const host = location.protocol + '//' + 'api.bitbucket.org/1.0'
|
||||||
|
const url = `${host}/repositories/${opts.repo.username}/${opts.repo.reponame}${path || ''}`
|
||||||
|
const cfg = { url, method: 'GET', cache: false }
|
||||||
|
|
||||||
|
if (opts.token) {
|
||||||
|
// Bitbucket App passwords can be used only for Basic Authentication.
|
||||||
|
// Get username of logged-in user.
|
||||||
|
let username = null, token = null
|
||||||
|
|
||||||
|
// Or get username by spliting token.
|
||||||
|
if (opts.token.includes(':')) {
|
||||||
|
const result = opts.token.split(':')
|
||||||
|
username = result[0], token = result[1]
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const currentUser = JSON.parse($('body').attr('data-current-user'))
|
||||||
|
if (!currentUser || !currentUser.username) {
|
||||||
|
return cb({
|
||||||
|
error: 'Error: Invalid token',
|
||||||
|
message: `Cannot retrieve your user name from the current page.
|
||||||
|
Please update the token setting to prepend your user
|
||||||
|
name to the token, separated by a colon, i.e. USERNAME:TOKEN`,
|
||||||
|
needAuth: true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
username = currentUser.username, token = opts.token
|
||||||
|
}
|
||||||
|
cfg.headers = { Authorization: 'Basic ' + btoa(username + ':' + token) }
|
||||||
|
}
|
||||||
|
|
||||||
|
$.ajax(cfg)
|
||||||
|
.done((data) => cb(null, data))
|
||||||
|
.fail((jqXHR) => {
|
||||||
|
this._handleError(jqXHR, cb)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// @override
|
||||||
|
_getItemHref(repo, type, encodedPath) {
|
||||||
|
return `/${repo.username}/${repo.reponame}/src/${repo.branch}/${encodedPath}`
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,164 @@
|
||||||
|
.octotree-show {
|
||||||
|
.octotree_bitbucket_only {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.octotree_bitbucket_sidebar {
|
||||||
|
a.octotree_toggle {
|
||||||
|
top: 9px;
|
||||||
|
right: 5px;
|
||||||
|
&: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_bitbucket_sidebar {
|
||||||
|
.btn {
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 3.01px;
|
||||||
|
color: #333;
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 14px;
|
||||||
|
font-variant: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
height: 2.14285714em;
|
||||||
|
line-height: 1.42857143;
|
||||||
|
margin: 0;
|
||||||
|
padding: 4px 10px;
|
||||||
|
text-decoration: none;
|
||||||
|
vertical-align: baseline;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
background: #e9e9e9;
|
||||||
|
border-color: #999;
|
||||||
|
color: #000;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
padding-top: 49px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
|
||||||
|
.octotree_bitbucket_only {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.octotree_views {
|
||||||
|
.octotree_view {
|
||||||
|
.octotree_view_header {
|
||||||
|
height: 49px;
|
||||||
|
background-color: #f3f3f3;
|
||||||
|
background-image: linear-gradient(#f9f9f9, #f3f3f3);
|
||||||
|
background-repeat: repeat-x;
|
||||||
|
border-bottom: 1px solid #e5e5e5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.octotree_help {
|
||||||
|
& > span:before {
|
||||||
|
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/question.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.octotree_treeview {
|
||||||
|
.octotree_header_repo {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.octotree_header_repo:before {
|
||||||
|
font-family: 'Atlassian Icons';
|
||||||
|
content: '\f135';
|
||||||
|
color: #707070;
|
||||||
|
}
|
||||||
|
.octotree_header_branch {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
|
.octotree_header_branch:before {
|
||||||
|
font-family: 'Atlassian Icons';
|
||||||
|
content: '\f127';
|
||||||
|
color: #707070;
|
||||||
|
}
|
||||||
|
.jstree-icon.tree:before {
|
||||||
|
content: '\f131';
|
||||||
|
color: #707070;
|
||||||
|
}
|
||||||
|
.jstree-icon.blob:before {
|
||||||
|
content: '\f12e';
|
||||||
|
color: #707070;
|
||||||
|
}
|
||||||
|
.jstree-node.jstree-leaf:hover {
|
||||||
|
.jstree-icon.blob {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
.jstree-icon.blob:before {
|
||||||
|
content: data-uri('image/svg+xml;charset=UTF-8', './octicons/cloud-download.svg');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.jstree-icon.commit:before {
|
||||||
|
content: '\f139';
|
||||||
|
color: #707070;
|
||||||
|
}
|
||||||
|
.jstree-anchor {
|
||||||
|
color: #3572b0 !important;
|
||||||
|
& > span {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.jstree-default {
|
||||||
|
.jstree-wholerow-hovered {
|
||||||
|
background: #e6e6e6;
|
||||||
|
}
|
||||||
|
.jstree-wholerow-clicked {
|
||||||
|
background: #e6e6e6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.jstree-icon.tree, .jstree-icon.blob, .jstree-icon.commit {
|
||||||
|
font: normal normal 16px 'Atlassian Icons';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.octotree_toggle, a.octotree_opts {
|
||||||
|
color: black !important;
|
||||||
|
|
||||||
|
&:hover, &.selected {
|
||||||
|
color: #4183C4 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.octotree_opts {
|
||||||
|
top: 16px;
|
||||||
|
right: 38px;
|
||||||
|
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: 5px;
|
||||||
|
right: -44px;
|
||||||
|
|
||||||
|
&: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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,22 +16,16 @@ const GH_PJAX_CONTAINER_SEL = '#js-repo-pjax-container, .context-loader-containe
|
||||||
const GH_CONTAINERS = '.container, .container-responsive'
|
const GH_CONTAINERS = '.container, .container-responsive'
|
||||||
const GH_RAW_CONTENT = 'body > pre'
|
const GH_RAW_CONTENT = 'body > pre'
|
||||||
|
|
||||||
class GitHub extends Adapter {
|
class GitHub extends PjaxAdapter {
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super(['jquery.pjax.js'])
|
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
|
// @override
|
||||||
init($sidebar) {
|
init($sidebar) {
|
||||||
super.init($sidebar)
|
const pjaxContainer = $(GH_PJAX_CONTAINER_SEL)[0]
|
||||||
|
super.init($sidebar, {'pjaxContainer': pjaxContainer})
|
||||||
if (!window.MutationObserver) return
|
|
||||||
|
|
||||||
// Fix #151 by detecting when page layout is updated.
|
// Fix #151 by detecting when page layout is updated.
|
||||||
// In this case, split-diff page has a wider layout, so need to recompute margin.
|
// In this case, split-diff page has a wider layout, so need to recompute margin.
|
||||||
|
@ -50,46 +44,6 @@ class GitHub extends Adapter {
|
||||||
attributeFilter: ['class'],
|
attributeFilter: ['class'],
|
||||||
attributeOldValue: true
|
attributeOldValue: true
|
||||||
})
|
})
|
||||||
|
|
||||||
// GitHub 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)
|
|
||||||
})
|
|
||||||
|
|
||||||
const pjaxContainer = $(GH_PJAX_CONTAINER_SEL)[0]
|
|
||||||
|
|
||||||
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
|
// @override
|
||||||
|
@ -180,17 +134,7 @@ class GitHub extends Adapter {
|
||||||
// @override
|
// @override
|
||||||
selectFile(path) {
|
selectFile(path) {
|
||||||
const $pjaxContainer = $(GH_PJAX_CONTAINER_SEL)
|
const $pjaxContainer = $(GH_PJAX_CONTAINER_SEL)
|
||||||
|
super.selectFile(path, {'$pjaxContainer': $pjaxContainer})
|
||||||
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
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else { // falls back
|
|
||||||
super.selectFile(path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @override
|
// @override
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
"128": "icons/icon128.png"
|
"128": "icons/icon128.png"
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
|
"https://bitbucket.org/*",
|
||||||
"https://github.com/*",
|
"https://github.com/*",
|
||||||
"storage"
|
"storage"
|
||||||
],
|
],
|
||||||
|
|
|
@ -2,7 +2,7 @@ const data = require('sdk/self').data
|
||||||
const pageMod = require('sdk/page-mod')
|
const pageMod = require('sdk/page-mod')
|
||||||
|
|
||||||
pageMod.PageMod({
|
pageMod.PageMod({
|
||||||
include: ['https://github.com/*'],
|
include: ['https://bitbucket.org/*', 'https://github.com/*'],
|
||||||
contentScriptFile : [data.url('jquery.js'),
|
contentScriptFile : [data.url('jquery.js'),
|
||||||
data.url('jquery-ui.js'),
|
data.url('jquery-ui.js'),
|
||||||
data.url('jstree.js'),
|
data.url('jstree.js'),
|
||||||
|
|
|
@ -12,7 +12,9 @@
|
||||||
"version": "$VERSION",
|
"version": "$VERSION",
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"cross-domain-content": [
|
"cross-domain-content": [
|
||||||
|
"https://api.bitbucket.org",
|
||||||
"https://api.github.com",
|
"https://api.github.com",
|
||||||
|
"https://bitbucket.org",
|
||||||
"https://github.com"
|
"https://github.com"
|
||||||
],
|
],
|
||||||
"private-browsing": true,
|
"private-browsing": true,
|
||||||
|
|
|
@ -39,6 +39,7 @@
|
||||||
</array>
|
</array>
|
||||||
<key>Whitelist</key>
|
<key>Whitelist</key>
|
||||||
<array>
|
<array>
|
||||||
|
<string>https://bitbucket.org/*</string>
|
||||||
<string>https://github.com/*</string>
|
<string>https://github.com/*</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
|
@ -52,6 +53,7 @@
|
||||||
<dict>
|
<dict>
|
||||||
<key>Allowed Domains</key>
|
<key>Allowed Domains</key>
|
||||||
<array>
|
<array>
|
||||||
|
<string>bitbucket.org</string>
|
||||||
<string>github.com</string>
|
<string>github.com</string>
|
||||||
</array>
|
</array>
|
||||||
<key>Include Secure Pages</key>
|
<key>Include Secure Pages</key>
|
||||||
|
|
|
@ -11,7 +11,20 @@ $(document).ready(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function createAdapter() {
|
function createAdapter() {
|
||||||
return new GitHub(store)
|
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() {
|
function loadExtension() {
|
||||||
|
@ -110,7 +123,8 @@ $(document).ready(() => {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSidebarVisible()) {
|
if (isSidebarVisible()) {
|
||||||
const repoChanged = JSON.stringify(repo) !== JSON.stringify(currRepo)
|
const replacer = ['username', 'reponame', 'branch']
|
||||||
|
const repoChanged = JSON.stringify(repo, replacer) !== JSON.stringify(currRepo, replacer)
|
||||||
|
|
||||||
if (repoChanged || reload === true) {
|
if (repoChanged || reload === true) {
|
||||||
$document.trigger(EVENT.REQ_START)
|
$document.trigger(EVENT.REQ_START)
|
||||||
|
|
|
@ -1,2 +1,3 @@
|
||||||
@import "base";
|
@import "base";
|
||||||
|
@import "../adapters/bitbucket";
|
||||||
@import "../adapters/github";
|
@import "../adapters/github";
|
||||||
|
|
|
@ -49,18 +49,20 @@ class OptionsView {
|
||||||
*/
|
*/
|
||||||
// @ifdef CHROME
|
// @ifdef CHROME
|
||||||
const $ta = this.$view.find('[data-store$=EURLS]').filter(':visible')
|
const $ta = this.$view.find('[data-store$=EURLS]').filter(':visible')
|
||||||
const storeKey = $ta.data('store')
|
if ($ta.length > 0) {
|
||||||
const urls = $ta.val().split(/\n/).filter((url) => url !== '')
|
const storeKey = $ta.data('store')
|
||||||
|
const urls = $ta.val().split(/\n/).filter((url) => url !== '')
|
||||||
|
|
||||||
if (urls.length > 0) {
|
if (urls.length > 0) {
|
||||||
chrome.runtime.sendMessage({type: 'requestPermissions', urls: urls}, (granted) => {
|
chrome.runtime.sendMessage({type: 'requestPermissions', urls: urls}, (granted) => {
|
||||||
if (!granted) {
|
if (!granted) {
|
||||||
// permissions not granted (by user or error), reset value
|
// permissions not granted (by user or error), reset value
|
||||||
$ta.val(this.store.get(STORE[storeKey]))
|
$ta.val(this.store.get(STORE[storeKey]))
|
||||||
}
|
}
|
||||||
this._saveOptions()
|
this._saveOptions()
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// @endif
|
// @endif
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue