forked from ovity/octotree
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathgithub.js
More file actions
254 lines (215 loc) · 7.88 KB
/
Copy pathgithub.js
File metadata and controls
254 lines (215 loc) · 7.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
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', 'gist', 'business',
'mirrors', 'open-source', 'personal',
'pricing'
]
const GH_RESERVED_REPO_NAMES = ['followers', 'following', 'repositories']
const GH_404_SEL = '#parallax_wrapper'
const GH_PJAX_CONTAINER_SEL = '#js-repo-pjax-container, .context-loader-container, [data-pjax-container]'
const GH_CONTAINERS = '.container, .container-responsive'
const GH_RAW_CONTENT = 'body > pre'
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) => {
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
})
// 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()
}
// Skip raw page
if ($(GH_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 (~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
$(GH_BRANCH_SEL_1).attr('title') || $(GH_BRANCH_SEL_2).data('branch') ||
// Non-code page (old GH design)
($(GH_BRANCH_SEL_3).attr('href') || ' ').match(/([^\/]+)/g)[3] ||
// Non-code page: commit page
($(GH_BRANCH_SEL_4).attr('title') || ' ').match(/([^\:]+)/g)[1] ||
// Non-code page: others
($(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) 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 = 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) => {
if (path && path.indexOf('/git/trees') === 0 && data.truncated) {
this._handleError({status: 206}, cb)
}
else cb(null, data)
})
.fail((jqXHR) => this._handleError(jqXHR, cb))
}
}