From f137ec8e659b075c1c23c6d9f298b539e64c9225 Mon Sep 17 00:00:00 2001 From: Buu Nguyen Date: Thu, 12 Nov 2015 11:40:35 -0800 Subject: [PATCH] Refactor adapters & update styles --- src/adapters/adapter.js | 154 ++++++++++++++++++++++++++++++---------- src/adapters/github.js | 64 +++++++++-------- src/adapters/gitlab.js | 65 ++++++++--------- src/octotree.js | 2 +- src/styles/github.less | 10 +-- src/styles/gitlab.less | 21 +++--- src/util.module.js | 6 +- src/view.tree.js | 2 - 8 files changed, 201 insertions(+), 123 deletions(-) diff --git a/src/adapters/adapter.js b/src/adapters/adapter.js index 75d5fb9..2f73285 100644 --- a/src/adapters/adapter.js +++ b/src/adapters/adapter.js @@ -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 = $('
') + 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') + } } diff --git a/src/adapters/github.js b/src/adapters/github.js index 7fe55c9..31a12ed 100644 --- a/src/adapters/github.js +++ b/src/adapters/github.js @@ -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)) } } diff --git a/src/adapters/gitlab.js b/src/adapters/gitlab.js index f762005..06b5e2a 100644 --- a/src/adapters/gitlab.js +++ b/src/adapters/gitlab.js @@ -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)) } } diff --git a/src/octotree.js b/src/octotree.js index 2928798..c9266b0 100755 --- a/src/octotree.js +++ b/src/octotree.js @@ -1,4 +1,4 @@ -$(document).ready(function() { +$(document).ready(() => { const store = new Storage() parallel(Object.keys(STORE), setDefault, loadExtension) diff --git a/src/styles/github.less b/src/styles/github.less index 58cb2f0..83c6748 100644 --- a/src/styles/github.less +++ b/src/styles/github.less @@ -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; diff --git a/src/styles/gitlab.less b/src/styles/gitlab.less index 47926da..0d86800 100644 --- a/src/styles/gitlab.less +++ b/src/styles/gitlab.less @@ -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; diff --git a/src/util.module.js b/src/util.module.js index f943a2b..aa0c8f4 100644 --- a/src/util.module.js +++ b/src/util.module.js @@ -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 } diff --git a/src/view.tree.js b/src/view.tree.js index d4f4977..ffd84ad 100644 --- a/src/view.tree.js +++ b/src/view.tree.js @@ -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]