Refactor code

This commit is contained in:
Buu Nguyen 2017-08-29 14:55:27 -07:00
parent f9bb5b72ee
commit faafae3480
9 changed files with 169 additions and 166 deletions

View File

@ -173,6 +173,7 @@ function buildJs(overrides, ctx) {
'./tmp/template.js',
'./src/constants.js',
'./src/adapters/adapter.js',
'./src/adapters/pjax.js',
'./src/adapters/bitbucket.js',
'./src/adapters/github.js',
'./src/view.help.js',

View File

@ -1,7 +1,8 @@
class Adapter {
constructor(deps) {
constructor(deps, store) {
deps.forEach(dep => window[dep]())
this._defaultBranch = {}
this.store = store
}
/**
@ -14,8 +15,9 @@ class Adapter {
* }
* @param {Function} transform(item)
* @param {Function} cb(err: error, tree: Array[Array|item])
* @api protected
*/
_loadCodeTree(opts, transform, cb) {
_loadCodeTreeInternal(opts, transform, cb) {
const folders = { '': [] }
const $dummyDiv = $('<div/>')
const {path, repo, node} = opts
@ -107,14 +109,13 @@ class Adapter {
else folders[item.path] = item.children = []
}
// If item is part of a PR, jump to that file's diff
// if item is part of a PR, jump to that file's diff
if (item.patch && typeof item.patch.diffId === 'number') {
let url = `/${repo.username}/${repo.reponame}/pull/${repo.pullNumber}/files`.split('/').map(encodeURIComponent).join('/')
url = `${url}#diff-${item.patch.diffId}`
const url = this._getPatchHref(repo, item.patch)
item.a_attr = {
href: url,
'data-downloadHref': item.url,
'data-downloadFileName': name,
'data-download-url': item.url,
'data-download-filename': name,
}
} else {
// encodes but retains the slashes, see #274
@ -122,8 +123,8 @@ class Adapter {
const url = this._getItemHref(repo, type, encodedPath)
item.a_attr = {
href: url,
'data-downloadHref': url,
'data-downloadFileName': name,
'data-download-url': url,
'data-download-filename': name,
}
}
}
@ -153,6 +154,10 @@ class Adapter {
})
}
/**
* Generic error handler.
* @api protected
*/
_handleError(jqXHR, cb) {
let error, message, needAuth
@ -224,21 +229,11 @@ class Adapter {
})
}
/**
* Inits behaviors after the sidebar is added to the DOM.
* @api public
*/
init($sidebar) {
$sidebar
.resizable({ handles: 'e', minWidth: this.getMinWidth() })
.addClass(this.getCssClass())
}
/**
* Returns the CSS class to be added to the Octotree sidebar.
* @api protected
*/
getCssClass() {
_getCssClass() {
throw new Error('Not implemented')
}
@ -246,10 +241,20 @@ class Adapter {
* Returns the minimum width acceptable for the sidebar.
* @api protected
*/
getMinWidth() {
_getMinWidth() {
return 200
}
/**
* Inits behaviors after the sidebar is added to the DOM.
* @api public
*/
init($sidebar) {
$sidebar
.resizable({ handles: 'e', minWidth: this._getMinWidth() })
.addClass(this._getCssClass())
}
/**
* Returns whether the adapter is capable of loading the entire tree in
* a single request. This is usually determined by the underlying the API.
@ -287,7 +292,7 @@ class Adapter {
* Returns repo info at the current path.
* @api public
*/
getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
getRepoFromPath(token, cb) {
throw new Error('Not implemented')
}
@ -351,87 +356,11 @@ class Adapter {
_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)
}
}
}
/**
* Returns patch's href value.
* @api protected
*/
_getPatchHref(repo, patch) {
return `/${repo.username}/${repo.reponame}/pull/${repo.pullNumber}/files#diff-${patch.diffId}`
}
}

View File

@ -9,10 +9,6 @@ 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]
@ -20,7 +16,7 @@ class Bitbucket extends PjaxAdapter {
}
// @override
getCssClass() {
_getCssClass() {
return 'octotree_bitbucket_sidebar'
}
@ -37,7 +33,7 @@ class Bitbucket extends PjaxAdapter {
}
// @override
getRepoFromPath(showInNonCodePage, showOnlyChangedInPR, currentRepo, token, cb) {
getRepoFromPath(currentRepo, token, cb) {
// 404 page, skip
if ($(BB_404_SEL).length) {
@ -63,8 +59,8 @@ class Bitbucket extends PjaxAdapter {
// Skip non-code page unless showInNonCodePage is true
// with Bitbucket /username/repo is non-code page
if (!showInNonCodePage &&
(!type || (type && type !== 'src'))) {
const showInNonCodePage = this.store.get(STORE.NONCODE)
if (!showInNonCodePage && (!type || (type && type !== 'src'))) {
return cb()
}
@ -102,7 +98,7 @@ class Bitbucket extends PjaxAdapter {
// @override
loadCodeTree(opts, cb) {
opts.path = opts.node.path
this._loadCodeTree(opts, (item) => {
this._loadCodeTreeInternal(opts, (item) => {
if (!item.type) {
item.type = 'blob'
}

View File

@ -18,8 +18,8 @@ const GH_RAW_CONTENT = 'body > pre'
class GitHub extends PjaxAdapter {
constructor() {
super(['jquery.pjax.js'])
constructor(store) {
super(store)
}
// @override
@ -47,7 +47,7 @@ class GitHub extends PjaxAdapter {
}
// @override
getCssClass() {
_getCssClass() {
return 'octotree_github_sidebar'
}
@ -73,7 +73,9 @@ class GitHub extends PjaxAdapter {
}
// @override
getRepoFromPath(showInNonCodePage, showOnlyChangedInPR, currentRepo, token, cb) {
getRepoFromPath(currentRepo, token, cb) {
const showInNonCodePage = this.store.get(STORE.NONCODE)
const showOnlyChangedInPR = this.store.get(STORE.PR)
// 404 page, skip
if ($(GH_404_SEL).length) {
@ -147,19 +149,17 @@ class GitHub extends PjaxAdapter {
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)
this._loadCodeTreeInternal(opts, null, cb)
}
// @override
_getTree(path, opts, cb) {
if (opts.repo.pullNumber) {
this._getPatch(opts, (err, res) => {
if (err) cb(err)
else cb(null, res)
})
this._getPatch(opts, cb)
}
else {
this._get(`/git/trees/${path}`, opts, (err, res) => {
// console.log('****', res.tree);
if (err) cb(err)
else cb(null, res.tree)
})
@ -180,70 +180,71 @@ class GitHub extends PjaxAdapter {
*/
_getPatch(opts, cb) {
const {pullNumber} = opts.repo
this._get(`/pulls/${pullNumber}/files`, opts, (err, res) => {
if (err) cb(err)
else {
const diffMap = {}
// Iterate files/folders to determine diff details
res.forEach((file, index) => {
// Grab parent folder path
const folderPath = file.filename.split('/').slice(0, -1).join('/')
// Record file patch info
// record file patch info
diffMap[file.filename] = {
type: 'blob',
diffId: index,
action: file.status,
additions: file.additions,
blob_url: file.blob_url,
deletions: file.deletions,
diffId: index,
filename: file.filename,
path: file.path,
sha: file.sha,
type: 'blob',
sha: file.sha
}
// Record ancestor folder patch info
// record ancestor folders
const folderPath = file.filename.split('/').slice(0, -1).join('/')
const split = folderPath.split('/')
// Start at root folder and construct path
// aggregate metadata for ancestor folders
split.reduce((path, curr) => {
if (path.length) {
path = `${path}/${curr}`
if (path.length) path = `${path}/${curr}`
else path = `${curr}`
if (diffMap[path] == null) {
diffMap[path] = {
type: 'tree',
filename: path,
filesChanged: 1,
additions: file.additions,
deletions: file.deletions
}
}
else {
path = `${curr}`
}
// Path already has been recorded, accumulate changes
if (diffMap[path]) {
diffMap[path].additions += file.additions
diffMap[path].deletions += file.deletions
diffMap[path].filesChanged++
}
// Path is new
else {
diffMap[path] = {
additions: file.additions,
deletions: file.deletions,
filesChanged: 1,
type: 'tree',
filename: path,
}
}
return path
}, '')
})
// Convert diffMap to emulate response from get `tree`
const diffTree = []
Object.keys(diffMap).forEach(fileName => {
// transform to emulate response from get `tree`
const tree = Object.keys(diffMap).map(fileName => {
const patch = diffMap[fileName]
diffTree.push({
return {
patch,
path: fileName,
sha: patch.sha,
type: patch.type,
url: patch.blob_url,
})
}
})
// Sort by path, needs to be alphabetical order (so parent folders come before children)
diffTree.sort((a, b) => a.path.localeCompare(b.path))
cb(null, diffTree)
// sort by path, needs to be alphabetical order (so parent folders come before children)
// note: this is still part of the above transform to mimic the behavior of get tree
tree.sort((a, b) => a.path.localeCompare(b.path))
cb(null, tree)
}
})
}

79
src/adapters/pjax.js Normal file
View File

@ -0,0 +1,79 @@
class PjaxAdapter extends Adapter {
constructor(store) {
super(['jquery.pjax.js'], store)
$.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)
}
}
}

View File

@ -20,7 +20,7 @@ const DEFAULTS = {
TOKEN : '',
REMEMBER : true,
NONCODE : true,
PR : false,
PR : true,
LOADALL : true,
HOTKEYS : '⌘+⇧+s, ⌃+⇧+s',
POPUP : false,

View File

@ -107,12 +107,10 @@ $(document).ready(() => {
function tryLoadRepo(reload) {
hasError = false
const remember = store.get(STORE.REMEMBER)
const showInNonCodePage = store.get(STORE.NONCODE)
const showOnlyChangedInPR = store.get(STORE.PR)
const shown = store.get(STORE.SHOWN)
const token = store.get(STORE.TOKEN)
adapter.getRepoFromPath(showInNonCodePage, showOnlyChangedInPR, currRepo, token, (err, repo) => {
adapter.getRepoFromPath(currRepo, token, (err, repo) => {
if (err) {
showError(err)
}

View File

@ -80,7 +80,7 @@
</div>
<div class="octotree_github_only">
<label><input type="checkbox" data-store="PR"> Show only changes in pull requests</label>
<label><input type="checkbox" data-store="PR"> Show pull request changes</label>
</div>
<div>

View File

@ -128,9 +128,6 @@ class TreeView {
const adapter = this.adapter
const newTab = event.shiftKey || event.ctrlKey || event.metaKey
const href = $target.attr('href')
// Link to download might be different from link to online view (PR View)
const downloadHref = $target.attr('data-downloadHref')
const downloadFileName = $target.attr('data-downloadFileName')
const $icon = $target.children().length
? $target.children(':first')
: $target.siblings(':first') // handles child links in submodule
@ -141,7 +138,9 @@ class TreeView {
}
else if ($icon.hasClass('blob')) {
if (download) {
adapter.downloadFile(downloadHref, downloadFileName)
const downloadUrl = $target.attr('data-download-url')
const downloadFileName = $target.attr('data-download-filename')
adapter.downloadFile(downloadUrl, downloadFileName)
}
else {
refocusAfterCompletion()