244 lines
7.7 KiB
JavaScript
244 lines
7.7 KiB
JavaScript
const GH_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'
|
|
]
|
|
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'
|
|
|
|
class GitHub 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
|
|
init($sidebar) {
|
|
super.init($sidebar)
|
|
|
|
if (!window.MutationObserver) return
|
|
|
|
// 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) => {
|
|
for (const mutation of mutations) {
|
|
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
|
|
})
|
|
|
|
// 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
|
|
getCssClass() {
|
|
return 'octotree_github_sidebar'
|
|
}
|
|
|
|
// @override
|
|
canLoadEntireTree() {
|
|
return true
|
|
}
|
|
|
|
// @override
|
|
getCreateTokenUrl() {
|
|
return `${location.protocol}//${location.host}/settings/tokens/new`
|
|
}
|
|
|
|
// @override
|
|
updateLayout(togglerVisible, sidebarVisible, sidebarWidth) {
|
|
const SPACING = 10
|
|
const $containers = $(GH_CONTAINERS)
|
|
const autoMarginLeft = ($(document).width() - $containers.width()) / 2
|
|
const shouldPushLeft = sidebarVisible && (autoMarginLeft <= sidebarWidth + SPACING)
|
|
|
|
$('html').css('margin-left', shouldPushLeft ? sidebarWidth : '')
|
|
$containers.css('margin-left', shouldPushLeft ? SPACING : '')
|
|
}
|
|
|
|
// @override
|
|
getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
|
|
|
|
// 404 page, skip
|
|
if ($(GH_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]
|
|
|
|
// Not a repository, skip
|
|
if (~GH_RESERVED_USER_NAMES.indexOf(username) ||
|
|
~GH_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 GH_BRANCH_SEL_1 = '[aria-label="Switch branches or tags"]'
|
|
const GH_BRANCH_SEL_2 = '.repo-root a[data-branch]'
|
|
const GH_BRANCH_SEL_3 = '.repository-sidebar a[aria-label="Code"]'
|
|
const GH_BRANCH_SEL_4 = '.current-branch'
|
|
const GH_BRANCH_SEL_5 = 'link[title*="Recent Commits to"]'
|
|
|
|
const branch =
|
|
// Detect branch in code page (don't care about non-code pages, let them use the next fallback)
|
|
$(GH_BRANCH_SEL_1).attr('title') || $(GH_BRANCH_SEL_2).data('branch') ||
|
|
// Non-code page
|
|
($(GH_BRANCH_SEL_3).attr('href') || ' ').match(/([^\/]+)/g)[3] ||
|
|
// Non-code page (new design)
|
|
// Specific handle /commit page
|
|
($(GH_BRANCH_SEL_4).attr('title') || ' ').match(/([^\:]+)/g)[1] ||
|
|
// Ignore if Github expands one more <link> - use last selected one instead
|
|
($(GH_BRANCH_SEL_5).length === 1
|
|
&& ($(GH_BRANCH_SEL_5).attr('title') || ' ').match(/([^\:]+)/g)[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 = $(GH_PJAX_CONTAINER_SEL)
|
|
|
|
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
|
|
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) return cb(err)
|
|
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 = location.protocol + '//' +
|
|
(location.host === 'github.com' ? 'api.github.com' : (location.host + '/api/v3'))
|
|
const url = `${host}/repos/${opts.repo.username}/${opts.repo.reponame}${path || ''}`
|
|
const cfg = { url, method: 'GET', cache: false }
|
|
|
|
if (opts.token) {
|
|
cfg.headers = { Authorization: 'token ' + opts.token }
|
|
}
|
|
|
|
$.ajax(cfg)
|
|
.done((data) => cb(null, data))
|
|
.fail((jqXHR) => this._handleError(jqXHR, cb))
|
|
}
|
|
}
|