Refactor adapters & update styles

This commit is contained in:
Buu Nguyen 2015-11-12 11:40:35 -08:00
parent 612f8c84db
commit f137ec8e65
8 changed files with 201 additions and 123 deletions

View File

@ -1,11 +1,10 @@
class Adapter {
constructor(store) {
this.store = store
constructor() {
this._defaultBranch = {}
this.observe()
this._observe()
}
observe() {
_observe() {
if (!window.MutationObserver) return
// Fix #151 by detecting when page layout is updated.
@ -27,42 +26,28 @@ class Adapter {
})
}
selectSubmodule(path) {
window.location.href = path
}
downloadFile(path, fileName) {
const link = document.createElement('a')
link.setAttribute('href', path.replace(/\/blob\//, '/raw/'))
link.setAttribute('download', fileName)
link.click()
}
/**
* Loads the code tree of a repository.
* @param {Object} opts: {
* repo: repository,
* node(optional): selected node (null to load entire tree),
* token (optional): user access token,
* apiUrl (optional): base API URL
* path: the starting path to load the tree,
* repo: the current repository,
* node (optional): the selected node (null to load entire tree),
* token (optional): the personal access token
* }
* @param {Function} transform(item)
* @param {Function} cb(err: error, tree: array (of arrays) of items)
* @api protected
* @param {Function} cb(err: error, tree: Array[Array|item])
*/
loadCodeTree(opts, transform, cb) {
_loadCodeTree(opts, transform, cb) {
const folders = { '': [] }
const $dummyDiv = $('<div/>')
const {path, repo, node} = opts
this.repo = opts.repo
this.token = opts.token
this.treePath = opts.treePath
this.encodedBranch = encodeURIComponent(decodeURIComponent(this.repo.branch))
opts.encodedBranch = opts.encodedBranch || encodeURIComponent(decodeURIComponent(repo.branch))
this.getTree(this.treePath, (err, tree) => {
this._getTree(path, opts, (err, tree) => {
if (err) return cb(err)
this.getSubmodules(tree, (err, submodules) => {
this._getSubmodules(tree, opts, (err, submodules) => {
if (err) return cb(err)
submodules = submodules || {}
@ -84,8 +69,8 @@ class Adapter {
}
// if lazy load and has parent, prefix with parent path
if (opts.node && opts.node.path) {
item.path = opts.node.path + '/' + item.path
if (node && node.path) {
item.path = node.path + '/' + item.path
}
const path = item.path
@ -97,7 +82,7 @@ class Adapter {
item.text = name
item.icon = type // use `type` as class name for tree node
if (opts.node) {
if (node) {
folders[''].push(item)
}
else {
@ -105,13 +90,13 @@ class Adapter {
}
if (type === 'tree') {
if (opts.node) item.children = true
if (node) item.children = true
else folders[item.path] = item.children = []
item.a_attr = { href: '#' }
}
else if (type === 'blob') {
item.a_attr = {
href: `/${this.repo.username}/${this.repo.reponame}/${type}/${this.repo.branch}/${encodeURIComponent(path)}`
href: `/${repo.username}/${repo.reponame}/${type}/${repo.branch}/${encodeURIComponent(path)}`
}
}
else if (type === 'commit') {
@ -140,7 +125,7 @@ class Adapter {
})
}
handleError(jqXHR, cb) {
_handleError(jqXHR, cb) {
let error, message, needAuth
switch (jqXHR.status) {
@ -199,9 +184,104 @@ class Adapter {
break
}
cb({
error : `Error: ${error}`,
message : message,
needAuth : needAuth,
error: `Error: ${error}`,
message: message,
needAuth: needAuth
})
}
/**
* Returns the CSS class to be added to the Octotree sidebar.
* @api public
*/
getCssClass() {
throw new Error('Not implemented')
}
/**
* Returns whether the adapter is capable of loading the entire tree in
* a single request. This is usually determined by the underlying the API.
* @api public
*/
canLoadEntireTree() {
return false
}
/**
* Loads the code tree.
* @api public
*/
loadCodeTree(opts, cb) {
throw new Error('Not implemented')
}
/**
* Returns the URL to create a personal access token.
* @api public
*/
getCreateTokenUrl() {
throw new Error('Not implemented')
}
/**
* Updates the layout based on sidebar visibility and width.
* @api public
*/
updateLayout(sidebarVisible, sidebarWidth) {
throw new Error('Not implemented')
}
/**
* Returns repo info at the current path.
* @api public
*/
getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
throw new Error('Not implemented')
}
/**
* Selects the file at a specific path.
* @api public
*/
selectFile(path) {
window.location.href = path
}
/**
* Selects a submodule.
* @api public
*/
selectSubmodule(path) {
window.location.href = path
}
/**
* Downloads a file.
* @api public
*/
downloadFile(path, fileName) {
const link = document.createElement('a')
link.setAttribute('href', path.replace(/\/blob\//, '/raw/'))
link.setAttribute('download', fileName)
link.click()
}
/**
* Gets tree at path.
* @param {Object} opts - {token, repo}
* @api protected
*/
_getTree(path, opts, cb) {
throw new Error('Not implemented')
}
/**
* Gets submodules in the tree.
* @param {Object} opts - {token, repo, encodedBranch}
* @api protected
*/
_getSubmodules(tree, opts, cb) {
throw new Error('Not implemented')
}
}

View File

@ -14,18 +14,22 @@ const GH_PJAX_SEL = '#js-repo-pjax-container'
const GH_CONTAINERS = '.container'
class GitHub extends Adapter {
// @override
getCssClass() {
return 'octotree_github_sidebar'
}
// @override
canLoadEntireTree() {
return true
}
// @override
getCreateTokenUrl() {
return `${location.protocol}//${location.host}/settings/tokens/new`
}
// @override
updateLayout(sidebarVisible, sidebarWidth) {
const SPACING = 10
const $containers = $(GH_CONTAINERS)
@ -40,6 +44,7 @@ class GitHub extends Adapter {
else $('html').css('margin-left', sidebarVisible ? sidebarWidth + SPACING : '')
}
// @override
getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
// 404 page, skip
@ -88,7 +93,7 @@ class GitHub extends Adapter {
cb(null, repo)
}
else {
this.get(repo, null, token, (err, data) => {
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)
@ -96,6 +101,7 @@ class GitHub extends Adapter {
}
}
// @override
selectFile(path) {
const container = $(GH_PJAX_SEL)
@ -106,53 +112,51 @@ class GitHub extends Adapter {
container : container
})
}
else window.location.href = path // falls back if no container (i.e. GitHub DOM has changed or is not yet available)
else { // falls back
super.selectFile(path)
}
}
// @override
loadCodeTree(opts, cb) {
const encodedBranch = encodeURIComponent(decodeURIComponent(opts.repo.branch))
const treePath = (opts.node && (opts.node.sha || encodedBranch)) ||
(encodedBranch + '?recursive=1')
opts.treePath = treePath
super.loadCodeTree(opts, null, 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)
}
getSubmodules(tree, cb) {
const item = tree.filter((item) => /^\.gitmodules$/i.test(item.path))[0]
if (!item) return cb()
this.getBlob(item.sha, (err, data) => {
if (err || !data) return cb(err)
parseGitmodules(data, cb)
})
}
getTree(tree, cb) {
this.get(this.repo, `/git/trees/${tree}`, this.token, (err, res) => {
// @override
_getTree(path, opts, cb) {
this._get(`/git/trees/${path}`, opts, (err, res) => {
if (err) return cb(err)
cb(null, res.tree)
})
}
getBlob(sha, cb) {
this.get(this.repo, `/git/blobs/${sha}`, this.token, (err, res) => {
// @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)
cb(null, atob(res.content.replace(/\n/g,'')))
const data = atob(res.content.replace(/\n/g,''))
cb(null, parseGitmodules(data))
})
}
get(repo, path, token, cb) {
const host = (location.host === 'github.com' ? 'api.github.com' : (location.host + '/api/v3'))
const base = `${location.protocol}//${host}/repos/${repo.username}/${repo.reponame}`
const cfg = { method: 'GET', url: base + (path || ''), cache: false }
_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 (token) {
cfg.headers = { Authorization: 'token ' + token }
if (opts.token) {
cfg.headers = { Authorization: 'token ' + opts.token }
}
$.ajax(cfg)
.done((data) => cb(null, data))
.fail((jqXHR) => this.handleError(jqXHR, cb))
.fail((jqXHR) => this._handleError(jqXHR, cb))
}
}

View File

@ -11,7 +11,7 @@ const GL_PROJECT_ID = '#project_id'
class GitLab extends Adapter {
constructor(store) {
super(store)
super()
// GitLab (for now) embeds access token in the page.
// Use it to set the token if one isn't available.
@ -23,33 +23,32 @@ class GitLab extends Adapter {
}
}
// Triggers layout when the GL sidebar is toggled
$('.toggle-nav-collapse').click(() => {
setTimeout(() => {
$(document).trigger(EVENT.LAYOUT_CHANGE)
}, 10)
})
// GL disables our submit buttons, reenable them
const btns = $('.octotree_view_body button[type="submit"]')
btns.click((event) => {
// GL disables our submit buttons, re-enable them
$('.octotree_view_body button[type="submit"]').click((event) => {
setTimeout(() => {
$(event.target).prop('disabled', false).removeClass('disabled')
}, 30)
})
}
// @override
getCssClass() {
return 'octotree_gitlab_sidebar'
}
canLoadEntireTree() {
return false
}
// @override
getCreateTokenUrl() {
return `${location.protocol}//${location.host}/profile/account`
}
// @override
updateLayout(sidebarVisible, sidebarWidth) {
var $containers = $(GL_CONTAINERS)
@ -80,14 +79,15 @@ class GitLab extends Adapter {
else $('html').css('margin-left', sidebarVisible ? sidebarWidth : '')
}
// @override
getRepoFromPath(showInNonCodePage, currentRepo, token, cb) {
// 404 page, skip - GitLab doesn't have specific element for Not Found page
if ($(document).find("title").text() === 'The page you\'re looking for could not be found (404)') {
if ($(document).find('title').text() === 'The page you\'re looking for could not be found (404)') {
return cb()
}
// No project id no way to fetch project files
// We need project ID
if (!$(GL_PROJECT_ID).length) {
return cb()
}
@ -101,7 +101,6 @@ class GitLab extends Adapter {
const username = match[1]
const reponame = match[2]
// not a repository, skip
if (~GL_RESERVED_USER_NAMES.indexOf(username) ||
~GL_RESERVED_REPO_NAMES.indexOf(reponame)) {
@ -136,7 +135,7 @@ class GitLab extends Adapter {
cb(null, repo)
}
else {
this.get(repo, null, token, (err, data) => {
this._get(null, {token}, (err, data) => {
if (err) return cb(err)
repo.branch = this._defaultBranch[username + '/' + reponame] = data.default_branch || 'master'
cb(null, repo)
@ -144,45 +143,39 @@ class GitLab extends Adapter {
}
}
selectFile(path) {
// TODO: pjax possible?
window.location.href = path
}
// @override
loadCodeTree(opts, cb) {
opts.treePath = opts.node.path
super.loadCodeTree(opts, (item) => {
opts.path = opts.node.path
this._loadCodeTree(opts, (item) => {
item.sha = item.id
item.path = item.name
}, cb)
}
getSubmodules(tree, cb) {
// @override
_getTree(path, opts, cb) {
this._get(`/tree?path=${path}&ref_name=${opts.encodedBranch}`, opts, cb)
}
// @override
_getSubmodules(tree, opts, cb) {
const item = tree.filter((item) => /^\.gitmodules$/i.test(item.name))[0]
if (!item) return cb()
this.getBlob(this.encodedBranch, item.name, (err, data) => {
if (err || !data) return cb(err)
parseGitmodules(data, cb)
this._get(`/blobs/${opts.encodedBranch}?filepath=${item.name}`, opts, (err, data) => {
if (err) return cb(err)
cb(null, parseGitmodules(data))
})
}
getTree(tree, cb) {
this.get(this.repo, `tree?path=${tree}&ref_name=${this.encodedBranch}`, this.token, cb)
}
getBlob(sha, path, cb) {
this.get(this.repo, `blobs/${sha}?filepath=${path}`, this.token, cb)
}
get(repo, path, token, cb) {
const host = `${location.host}/api/v3`
_get(path, opts, cb) {
const projectId = $(GL_PROJECT_ID).val()
const url = `${location.protocol}//${host}/projects/${projectId}/repository/${path}&private_token=${token}`
const cfg = { method: 'GET', url, cache: false }
const host = `${location.protocol}//${location.host}/api/v3`
const url = `${host}/projects/${projectId}/repository${path}&private_token=${opts.token}`
const cfg = { url, method: 'GET', cache: false }
$.ajax(cfg)
.done((data) => cb(null, data))
.fail((jqXHR) => this.handleError(jqXHR, cb))
.fail((jqXHR) => this._handleError(jqXHR, cb))
}
}

View File

@ -1,4 +1,4 @@
$(document).ready(function() {
$(document).ready(() => {
const store = new Storage()
parallel(Object.keys(STORE), setDefault, loadExtension)

View File

@ -1,4 +1,8 @@
.octotree {
.octotree_github_only {
display: none;
}
.octotree_github_sidebar {
a.octotree_toggle {
right: 5px;
@ -12,10 +16,6 @@
}
}
.octotree_github_only {
display: none;
}
.octotree_github_sidebar {
padding-top: 49px;
@ -80,7 +80,6 @@
background: #dbeeff;
}
}
.jstree-icon.tree, .jstree-icon.blob, .jstree-icon.commit {
font: normal normal 16px octicons;
}
@ -106,6 +105,7 @@
content: '\f031';
}
}
a.octotree_toggle {
top: 9px;
right: -35px;

View File

@ -1,14 +1,20 @@
.octotree {
.octotree_gitlab_only {
display: none;
}
.page-sidebar-expanded {
.octotree_gitlab_sidebar {
left: 249px;
}
}
.page-sidebar-collapsed {
.octotree_gitlab_sidebar {
left: 79px;
}
}
.octotree_gitlab_sidebar {
a.octotree_toggle {
right: 5px;
@ -41,10 +47,6 @@ html:not(.octotree) {
}
}
.octotree_gitlab_only {
display: none;
}
.octotree_gitlab_sidebar {
transition-duration: .3s;
padding-top: 58px;
@ -71,6 +73,7 @@ html:not(.octotree) {
.octotree_header_repo:before {
font-family: FontAwesome;
content: '\f015';
color: #333c48;
}
.octotree_header_branch {
font-size: 13px;
@ -85,22 +88,22 @@ html:not(.octotree) {
}
.jstree-icon.blob:before {
content: '\f0f6';
color: #777;
color: #333c48;
}
.jstree-node.jstree-leaf:hover {
.jstree-icon.blob:before {
content: '\f019';
color: #7f8fa4;
color: #333c48;
}
}
.jstree-icon.commit:before {
content: '\f187';
color: #777;
color: #333c48;
}
.jstree-anchor {
color: #333c48 !important;
& > span {
color: black;
color: black;
}
}
.jstree-default {
@ -111,7 +114,6 @@ html:not(.octotree) {
background: #e7e9ed;
}
}
.jstree-icon.tree, .jstree-icon.blob, .jstree-icon.commit {
font: normal normal 16px FontAwesome;
}
@ -136,6 +138,7 @@ html:not(.octotree) {
color: #4183C4 !important;
}
}
a.octotree_toggle {
top: 14px;

View File

@ -4,8 +4,8 @@ const INI_COMMENT = /^\s*;.*$/
const INI_PARAM = /^\s*([\w\.\-\_]+)\s*=\s*(.*?)\s*$/
const SEPARATOR = /\r\n|\r|\n/
function parseGitmodules(data, cb) {
if (!data) return cb()
function parseGitmodules(data) {
if (!data) return
const submodules = {}
const lines = data.split(SEPARATOR)
@ -23,5 +23,5 @@ function parseGitmodules(data, cb) {
else if (match[1] === 'url') submodules[lastPath] = match[2]
})
cb(null, submodules)
return submodules
}

View File

@ -140,13 +140,11 @@ class TreeView {
syncSelection() {
const tree = this.$view.find('.octotree_view_body').jstree(true)
if (!tree) return
// converts /username/reponame/object_type/branch/path to path
const path = decodeURIComponent(location.pathname)
const match = path.match(/(?:[^\/]+\/){4}(.*)/)
if (!match) return
const currentPath = match[1]