Improve option handling and GitLab styling

* Refactor storage support
* Options except urls are per-site
* Fix GitLab styling bug
This commit is contained in:
Buu Nguyen 2015-11-13 15:54:40 -08:00
parent f8161c02c7
commit 63e111e438
20 changed files with 205 additions and 245 deletions

View File

@ -50,8 +50,9 @@ By default, Octotree only works on `github.com` and `gitlab.com`. To support ent
### v2.0.0 ### v2.0.0
* Support GitLab * Support GitLab
* Modify Octotree options * Support lazy-load individual folder (GitHub only)
* Support lazy-load individual folder (GitHub) * Simplify Octotree options
* Support selecting different options for each host
### v1.7.2 ### v1.7.2
* Fix bug long branches are not loaded correctly due to GitHub DOM change * Fix bug long branches are not loaded correctly due to GitHub DOM change

View File

@ -1,29 +1,31 @@
var gulp = require('gulp') 'use strict'
, path = require('path')
, merge = require('event-stream').merge const gulp = require('gulp')
, map = require('map-stream') const path = require('path')
, spawn = require('child_process').spawn const merge = require('event-stream').merge
, $ = require('gulp-load-plugins')() const map = require('map-stream')
const spawn = require('child_process').spawn
const $ = require('gulp-load-plugins')()
// Tasks // Tasks
gulp.task('clean', function () { gulp.task('clean', () => {
return pipe('./tmp', [$.clean()]) return pipe('./tmp', [$.clean()])
}) })
gulp.task('build', function (cb) { gulp.task('build', (cb) => {
$.runSequence('clean', 'styles', 'chrome', 'opera', 'safari', 'firefox', cb) $.runSequence('clean', 'styles', 'chrome', 'opera', 'safari', 'firefox', cb)
}) })
gulp.task('default', ['build'], function () { gulp.task('default', ['build'], () => {
gulp.watch(['./libs/**/*', './src/**/*'], ['default']) gulp.watch(['./libs/**/*', './src/**/*'], ['default'])
}) })
gulp.task('dist', ['build'], function (cb) { gulp.task('dist', ['build'], (cb) => {
$.runSequence('firefox:xpi', 'chrome:zip', 'chrome:crx', 'opera:nex', cb) $.runSequence('firefox:xpi', 'chrome:zip', 'chrome:crx', 'opera:nex', cb)
}) })
gulp.task('test', ['build'], function (cb) { gulp.task('test', ['build'], (cb) => {
var ps = spawn( const ps = spawn(
'./node_modules/.bin/mocha', './node_modules/.bin/mocha',
['--harmony', '--reporter', 'spec', '--bail', '--recursive', '--timeout', '-1'] ['--harmony', '--reporter', 'spec', '--bail', '--recursive', '--timeout', '-1']
) )
@ -32,62 +34,62 @@ gulp.task('test', ['build'], function (cb) {
ps.on('close', cb) ps.on('close', cb)
}) })
gulp.task('styles', function () { gulp.task('styles', () => {
return pipe('./src/styles/octotree.less', return pipe('./src/styles/octotree.less',
[$.less(), $.autoprefixer({cascade: true})], [$.less(), $.autoprefixer({cascade: true})],
'./tmp') './tmp')
}) })
// Chrome // Chrome
gulp.task('chrome:template', function () { gulp.task('chrome:template', () => {
return buildTemplate({CHROME: true}) return buildTemplate({CHROME: true})
}) })
gulp.task('chrome:js', ['chrome:template'], function () { gulp.task('chrome:js', ['chrome:template'], () => {
return buildJs(['./src/config/chrome/storage.js'], {CHROME: true}) return buildJs(['./src/config/chrome/overrides.js'], {CHROME: true})
}) })
gulp.task('chrome', ['chrome:js'], function () { gulp.task('chrome', ['chrome:js'], () => {
return merge( return merge(
pipe('./icons/**/*', './tmp/chrome/icons'), pipe('./icons/**/*', './tmp/chrome/icons'),
pipe(['./libs/**/*', './tmp/octotree.*', './src/config/chrome/**/*', '!./src/config/chrome/storage.js'], './tmp/chrome/') pipe(['./libs/**/*', './tmp/octotree.*', './src/config/chrome/**/*', '!./src/config/chrome/storage.js'], './tmp/chrome/')
) )
}) })
gulp.task('chrome:zip', function () { gulp.task('chrome:zip', () => {
return pipe('./tmp/chrome/**/*', [$.zip('chrome.zip')], './dist') return pipe('./tmp/chrome/**/*', [$.zip('chrome.zip')], './dist')
}) })
gulp.task('chrome:_crx', function (cb) { gulp.task('chrome:_crx', (cb) => {
$.run('"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"' + $.run('"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"' +
' --pack-extension=' + path.join(__dirname, './tmp/chrome') + ' --pack-extension=' + path.join(__dirname, './tmp/chrome') +
' --pack-extension-key=' + path.join(process.env.HOME, '.ssh/chrome.pem') ' --pack-extension-key=' + path.join(process.env.HOME, '.ssh/chrome.pem')
).exec(cb) ).exec(cb)
}) })
gulp.task('chrome:crx', ['chrome:_crx'], function () { gulp.task('chrome:crx', ['chrome:_crx'], () => {
return pipe('./tmp/chrome.crx', './dist') return pipe('./tmp/chrome.crx', './dist')
}) })
// Opera // Opera
gulp.task('opera', ['chrome'], function () { gulp.task('opera', ['chrome'], () => {
return pipe('./tmp/chrome/**/*', './tmp/opera') return pipe('./tmp/chrome/**/*', './tmp/opera')
}) })
gulp.task('opera:nex', function () { gulp.task('opera:nex', () => {
return pipe('./dist/chrome.crx', [$.rename('opera.nex')], './dist') return pipe('./dist/chrome.crx', [$.rename('opera.nex')], './dist')
}) })
// Safari // Safari
gulp.task('safari:template', function () { gulp.task('safari:template', () => {
return buildTemplate({SAFARI: true}) return buildTemplate({SAFARI: true})
}) })
gulp.task('safari:js', ['safari:template'], function () { gulp.task('safari:js', ['safari:template'], () => {
return buildJs(['./src/config/safari/storage.js'], {SAFARI: true}) return buildJs([], {SAFARI: true})
}) })
gulp.task('safari', ['safari:js'], function () { gulp.task('safari', ['safari:js'], () => {
return merge( return merge(
pipe('./icons/**/*', './tmp/safari/octotree.safariextension/icons'), pipe('./icons/**/*', './tmp/safari/octotree.safariextension/icons'),
pipe(['./libs/**/*', './tmp/octotree.js', './tmp/octotree.css', pipe(['./libs/**/*', './tmp/octotree.js', './tmp/octotree.css',
@ -96,15 +98,15 @@ gulp.task('safari', ['safari:js'], function () {
}) })
// Firefox // Firefox
gulp.task('firefox:template', function () { gulp.task('firefox:template', () => {
return buildTemplate({FIREFOX: true}) return buildTemplate({FIREFOX: true})
}) })
gulp.task('firefox:js', ['firefox:template'], function () { gulp.task('firefox:js', ['firefox:template'], () => {
return buildJs(['./src/config/firefox/storage.js'], {FIREFOX: true}) return buildJs([], {FIREFOX: true})
}) })
gulp.task('firefox', ['firefox:js'], function () { gulp.task('firefox', ['firefox:js'], () => {
return merge( return merge(
pipe('./icons/**/*', './tmp/firefox/data/icons'), pipe('./icons/**/*', './tmp/firefox/data/icons'),
pipe(['./libs/**/*', './tmp/octotree.js', './tmp/octotree.css'], './tmp/firefox/data'), pipe(['./libs/**/*', './tmp/octotree.js', './tmp/octotree.css'], './tmp/firefox/data'),
@ -113,7 +115,7 @@ gulp.task('firefox', ['firefox:js'], function () {
) )
}) })
gulp.task('firefox:xpi', function (cb) { gulp.task('firefox:xpi', (cb) => {
$.run('cd ./tmp/firefox && cfx xpi --output-file=../../dist/firefox.xpi').exec(cb) $.run('cd ./tmp/firefox && cfx xpi --output-file=../../dist/firefox.xpi').exec(cb)
}) })
@ -123,10 +125,12 @@ function pipe(src, transforms, dest) {
dest = transforms dest = transforms
transforms = null transforms = null
} }
var stream = gulp.src(src)
let stream = gulp.src(src)
transforms && transforms.forEach(function (transform) { transforms && transforms.forEach(function (transform) {
stream = stream.pipe(transform) stream = stream.pipe(transform)
}) })
if (dest) stream = stream.pipe(gulp.dest(dest)) if (dest) stream = stream.pipe(gulp.dest(dest))
return stream return stream
} }
@ -135,20 +139,22 @@ function html2js(template) {
return map(escape) return map(escape)
function escape(file, cb) { function escape(file, cb) {
var path = $.util.replaceExtension(file.path, '.js') const path = $.util.replaceExtension(file.path, '.js')
, content = file.contents.toString() const content = file.contents.toString()
, escaped = content.replace(/\\/g, "\\\\") const escaped = content
.replace(/'/g, "\\'") .replace(/\\/g, "\\\\")
.replace(/\r?\n/g, "\\n' +\n '") .replace(/'/g, "\\'")
, body = template.replace('$$', escaped) .replace(/\r?\n/g, "\\n' +\n '")
const body = template.replace('$$', escaped)
file.path = path file.path = path
file.contents = new Buffer(body) file.contents = new Buffer(body)
cb(null, file) cb(null, file)
} }
} }
function buildJs(additions, ctx) { function buildJs(overrides, ctx) {
var src = additions.concat([ const src = [
'./tmp/template.js', './tmp/template.js',
'./src/constants.js', './src/constants.js',
'./src/adapters/adapter.js', './src/adapters/adapter.js',
@ -161,8 +167,10 @@ function buildJs(additions, ctx) {
'./src/util.location.js', './src/util.location.js',
'./src/util.module.js', './src/util.module.js',
'./src/util.async.js', './src/util.async.js',
'./src/octotree.js', './src/util.storage.js'
]) ].concat(overrides)
.concat('./src/octotree.js')
return pipe(src, [ return pipe(src, [
$.babel({presets: ['es2015']}), $.babel({presets: ['es2015']}),
$.concat('octotree.js'), $.concat('octotree.js'),

View File

@ -23,7 +23,7 @@
"crx": "^0.4.4", "crx": "^0.4.4",
"event-stream": "*", "event-stream": "*",
"firefox-profile": "~0.3.6", "firefox-profile": "~0.3.6",
"gulp": "^3.8.6", "gulp": "^3.9.0",
"gulp-autoprefixer": "0.0.8", "gulp-autoprefixer": "0.0.8",
"gulp-babel": "^6.1.0", "gulp-babel": "^6.1.0",
"gulp-clean": "^0.3.1", "gulp-clean": "^0.3.1",

View File

@ -191,13 +191,31 @@ class Adapter {
} }
/** /**
* Returns the CSS class to be added to the Octotree sidebar. * Inits behaviors after the sidebar is added to the DOM.
* @api public * @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') throw new Error('Not implemented')
} }
/**
* Returns the minimum width acceptable for the sidebar.
* @api protected
*/
getMinWidth() {
return 200
}
/** /**
* Returns whether the adapter is capable of loading the entire tree in * Returns whether the adapter is capable of loading the entire tree in
* a single request. This is usually determined by the underlying the API. * a single request. This is usually determined by the underlying the API.
@ -247,7 +265,6 @@ class Adapter {
window.location.href = path window.location.href = path
} }
/** /**
* Selects a submodule. * Selects a submodule.
* @api public * @api public

View File

@ -14,6 +14,7 @@ const GH_PJAX_SEL = '#js-repo-pjax-container'
const GH_CONTAINERS = '.container' const GH_CONTAINERS = '.container'
class GitHub extends Adapter { class GitHub extends Adapter {
// @override // @override
getCssClass() { getCssClass() {
return 'octotree_github_sidebar' return 'octotree_github_sidebar'

View File

@ -9,6 +9,7 @@ const GL_SHIFTED = 'h1.title'
const GL_PROJECT_ID = '#project_id' const GL_PROJECT_ID = '#project_id'
class GitLab extends Adapter { class GitLab extends Adapter {
constructor(store) { constructor(store) {
super() super()
@ -21,6 +22,11 @@ class GitLab extends Adapter {
store.set(STORE.TOKEN, token, true) store.set(STORE.TOKEN, token, true)
} }
} }
}
// @override
init($sidebar) {
super.init($sidebar)
// Triggers layout when the GL sidebar is toggled // Triggers layout when the GL sidebar is toggled
$('.toggle-nav-collapse').click(() => { $('.toggle-nav-collapse').click(() => {
@ -28,6 +34,22 @@ class GitLab extends Adapter {
$(document).trigger(EVENT.LAYOUT_CHANGE) $(document).trigger(EVENT.LAYOUT_CHANGE)
}, 10) }, 10)
}) })
// GitLab disables our submit buttons, re-enable them
$('.octotree_view_body button[type="submit"]').click((event) => {
setTimeout(() => {
$(event.target).prop('disabled', false).removeClass('disabled')
}, 100)
})
// Reuses GitLab styles for inputs
$('.octotree_view_body input[type="text"], .octotree_view_body textarea')
.addClass('form-control')
// GitLab removes DOM, add back
$(document).on(EVENT.LOC_CHANGE, () => {
$sidebar.appendTo('body')
})
} }
// @override // @override
@ -35,6 +57,11 @@ class GitLab extends Adapter {
return 'octotree_gitlab_sidebar' return 'octotree_gitlab_sidebar'
} }
// @override
getMinWidth() {
return 230 // just enough to hide the GitLab sidebar
}
// @override // @override
getCreateTokenUrl() { getCreateTokenUrl() {
return `${location.protocol}//${location.host}/profile/account` return `${location.protocol}//${location.host}/profile/account`
@ -42,16 +69,16 @@ class GitLab extends Adapter {
// @override // @override
updateLayout(sidebarVisible, sidebarWidth) { updateLayout(sidebarVisible, sidebarWidth) {
const useNewDesign = $('.navbar-gitlab.header-collapsed, .navbar-gitlab.header-expanded').length > 0 const isNewDesign = $('.navbar-gitlab.header-collapsed, .navbar-gitlab.header-expanded').length > 0
const glSidebarExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded') const glSidebarExpanded = $('.page-with-sidebar').hasClass('page-sidebar-expanded')
let glSidebarWidth = glSidebarExpanded ? 230 : 62
if (useNewDesign) { if (isNewDesign) {
const glSidebarWidth = glSidebarExpanded ? 230 : 62
$(GL_SHIFTED).css('margin-left', sidebarVisible ? '' : 36) $(GL_SHIFTED).css('margin-left', sidebarVisible ? '' : 36)
$('.octotree_toggle').css('right', sidebarVisible ? '' : -(glSidebarWidth + 50)) $('.octotree_toggle').css('right', sidebarVisible ? '' : -(glSidebarWidth + 50))
} }
else { else {
glSidebarWidth = glSidebarExpanded ? 230 : 52 const glSidebarWidth = glSidebarExpanded ? 230 : 52
$(GL_HEADER).css('z-index', 3) $(GL_HEADER).css('z-index', 3)
$(GL_SIDEBAR).css('z-index', 1) $(GL_SIDEBAR).css('z-index', 1)
$(GL_SHIFTED).css('margin-left', sidebarVisible ? '' : 56) $(GL_SHIFTED).css('margin-left', sidebarVisible ? '' : 56)
@ -61,7 +88,7 @@ class GitLab extends Adapter {
}) })
} }
$(GL_HEADER).css({'z-index': 3, 'padding-left': sidebarVisible ? sidebarWidth : ''}) $(GL_HEADER).css({'z-index': 3, 'margin-left': sidebarVisible ? sidebarWidth : ''})
$('.page-with-sidebar').css('padding-left', sidebarVisible ? sidebarWidth : '') $('.page-with-sidebar').css('padding-left', sidebarVisible ? sidebarWidth : '')
} }
@ -138,25 +165,6 @@ class GitLab extends Adapter {
}, cb) }, cb)
} }
// @override
setSideBar(sidebar) {
this.sidebar = sidebar
// GL disables our submit buttons, re-enable them
const btns = $('.octotree_view_body button[type="submit"]')
btns.click((event) => {
setTimeout(() => {
$(event.target).prop('disabled', false).removeClass('disabled')
}, 30)
})
// Make inputs consistent with GL style
$('.octotree_view_body input[type="text"], .octotree_view_body textarea').addClass('form-control')
$(document).on(EVENT.LOC_CHANGE, () => {
this.sidebar.appendTo('body')
})
}
// @override // @override
_getTree(path, opts, cb) { _getTree(path, opts, cb) {
this._get(`/tree?path=${path}&ref_name=${opts.encodedBranch}`, opts, cb) this._get(`/tree?path=${path}&ref_name=${opts.encodedBranch}`, opts, cb)

View File

@ -3,18 +3,6 @@
display: none; display: none;
} }
.page-sidebar-expanded {
.octotree_gitlab_sidebar {
left: 249px;
}
}
.page-sidebar-collapsed {
.octotree_gitlab_sidebar {
left: 79px;
}
}
.octotree_gitlab_sidebar { .octotree_gitlab_sidebar {
a.octotree_toggle { a.octotree_toggle {
right: 5px; right: 5px;
@ -29,24 +17,6 @@
} }
} }
html:not(.octotree) {
.page-sidebar-expanded {
.octotree_gitlab_sidebar {
a.octotree_toggle {
right: -294px;
}
}
}
.page-sidebar-collapsed {
.octotree_gitlab_sidebar {
a.octotree_toggle {
right: -110px;
}
}
}
}
.octotree_gitlab_sidebar { .octotree_gitlab_sidebar {
transition-duration: .3s; transition-duration: .3s;
padding-top: 58px; padding-top: 58px;

View File

@ -0,0 +1,21 @@
(() => {
const oldSet = Storage.prototype.set
Storage.prototype.set = function (key, val, cb) {
this._cache = this._cache || {}
this._cache[key] = val
const shared = ~key.indexOf('.shared')
if (shared) chrome.storage.local.set({[key]: val}, cb || Function())
else oldSet.call(this, key, val, cb)
}
const oldGet = Storage.prototype.get
Storage.prototype.get = function (key, cb) {
this._cache = this._cache || {}
if (!cb) return this._cache[key]
const shared = ~key.indexOf('.shared')
if (shared) chrome.storage.local.get(key, (item) => cb(item[key]))
else oldGet.call(this, key, cb)
}
})()

View File

@ -1,48 +0,0 @@
function Storage() {
var cache = this.cache = {}
chrome.storage.onChanged.addListener(function(changes) {
for (var key in changes) cache[key] = changes[key].newValue
})
}
Storage.prototype.set = function(key, val, local, cb) {
if (typeof local === 'function') {
cb = local
local = false
}
cb = cb || Function()
this.cache[key] = val
if (local) {
localStorage.setItem(key, JSON.stringify(val))
cb()
}
else {
var item = {}
item[key] = val
chrome.storage.local.set(item, cb)
}
}
Storage.prototype.get = function(key, local, cb) {
if (typeof local === 'function') {
cb = local
local = false
}
if (!cb) return this.cache[key]
if (local) cb(parse(localStorage.getItem(key)))
else chrome.storage.local.get(key, function(item) {
cb(item[key])
})
function parse(val) {
try {
return JSON.parse(val)
} catch (e) {
return val
}
}
}

View File

@ -1,24 +0,0 @@
function Storage() {}
Storage.prototype.set = function(key, val, local, cb) {
if (typeof local === 'function') cb = local
localStorage.setItem(key, JSON.stringify(val))
if (cb) cb()
}
Storage.prototype.get = function(key, local, cb) {
if (typeof local === 'function') cb = local
var val = parse(localStorage.getItem(key))
if (cb) cb(val)
else return val
function parse(val) {
try {
return JSON.parse(val)
} catch (e) {
return val
}
}
}

View File

@ -1,24 +0,0 @@
function Storage() {}
Storage.prototype.set = function(key, val, local, cb) {
if (typeof local === 'function') cb = local
localStorage.setItem(key, JSON.stringify(val))
if (cb) cb()
}
Storage.prototype.get = function(key, local, cb) {
if (typeof local === 'function') cb = local
var val = parse(localStorage.getItem(key))
if (cb) cb(val)
else return val
function parse(val) {
try {
return JSON.parse(val)
} catch (e) {
return val
}
}
}

View File

@ -6,11 +6,11 @@ const STORE = {
NONCODE : 'octotree.noncode_shown', NONCODE : 'octotree.noncode_shown',
HOTKEYS : 'octotree.hotkeys', HOTKEYS : 'octotree.hotkeys',
LOADALL : 'octotree.loadall', LOADALL : 'octotree.loadall',
GHEURLS : 'octotree.gheurls',
GLEURLS : 'octotree.gleurls',
WIDTH : 'octotree.sidebar_width',
POPUP : 'octotree.popup_shown', POPUP : 'octotree.popup_shown',
SHOWN : 'octotree.sidebar_shown' WIDTH : 'octotree.sidebar_width',
SHOWN : 'octotree.sidebar_shown',
GHEURLS : 'octotree.gheurls.shared',
GLEURLS : 'octotree.gleurls.shared'
} }
const DEFAULTS = { const DEFAULTS = {
@ -26,7 +26,7 @@ const DEFAULTS = {
// @endif // @endif
GHEURLS : '', GHEURLS : '',
GLEURLS : '', GLEURLS : '',
WIDTH : 250, WIDTH : 232,
POPUP : false, POPUP : false,
SHOWN : false SHOWN : false
} }

View File

@ -5,10 +5,8 @@ $(document).ready(() => {
function setDefault(key, cb) { function setDefault(key, cb) {
const storeKey = STORE[key] const storeKey = STORE[key]
const local = storeKey === STORE.TOKEN store.get(storeKey, (val) => {
store.set(storeKey, val == null ? DEFAULTS[key] : val, cb)
store.get(storeKey, local, (val) => {
store.set(storeKey, val == null ? DEFAULTS[key] : val, local, cb)
}) })
} }
@ -36,13 +34,11 @@ $(document).ready(() => {
let hasError = false let hasError = false
$sidebar $sidebar
.width(parseFloat(store.get(STORE.WIDTH))) .width(parseInt(store.get(STORE.WIDTH)))
.resizable({ handles: 'e', minWidth: 230 }) // to hide GL sidebar
.resize(layoutChanged) .resize(layoutChanged)
.addClass(adapter.getCssClass())
.appendTo($('body')) .appendTo($('body'))
adapter.setSideBar($sidebar) adapter.init($sidebar)
layoutChanged() layoutChanged()
$(window).resize((event) => { // handle zoom $(window).resize((event) => { // handle zoom
@ -170,7 +166,7 @@ $(document).ready(() => {
} }
function layoutChanged() { function layoutChanged() {
const width = $sidebar.width() const width = $sidebar.outerWidth()
adapter.updateLayout(isSidebarVisible(), width) adapter.updateLayout(isSidebarVisible(), width)
store.set(STORE.WIDTH, width) store.set(STORE.WIDTH, width)
} }

View File

@ -197,7 +197,7 @@ a.octotree_toggle {
&.octotree_loading { &.octotree_loading {
& > span:after { & > span:after {
content: ''; content: '';
} }
.loader { .loader {
@ -221,9 +221,7 @@ a.octotree_toggle {
cursor: pointer; cursor: pointer;
display: none; display: none;
opacity: 0; opacity: 0;
top: 40px; z-index: 999999999;
left: 5px;
z-index: 93;
width: 260px; width: 260px;
text-align: left; text-align: left;
background-color: #fff; background-color: #fff;

View File

@ -1,3 +1,3 @@
@import "common"; @import "base";
@import "github"; @import "../adapters/github";
@import "gitlab"; @import "../adapters/gitlab";

View File

@ -32,6 +32,26 @@
<input type="text" data-store="TOKEN" data-perhost="true"> <input type="text" data-store="TOKEN" data-perhost="true">
</div> </div>
<div>
<div>
<label>Hotkeys</label>
<a href="https://github.com/madrobby/keymaster#defining-shortcuts" target="_blank" tabIndex="-1">supported keys</a>
</div>
<input type="text" data-store="HOTKEYS">
</div>
<div>
<label><input type="checkbox" data-store="REMEMBER"> Remember sidebar visibility</label>
</div>
<div>
<label><input type="checkbox" data-store="NONCODE"> Show in non-code pages</label>
</div>
<div class="octotree_github_only">
<label><input type="checkbox" data-store="LOADALL"> Load entire tree at once</label>
</div>
<!-- @ifdef CHROME --> <!-- @ifdef CHROME -->
<div class="octotree_github_only"> <div class="octotree_github_only">
<div> <div>
@ -40,6 +60,7 @@
<textarea data-store="GHEURLS" placeholder="https://github.mysite1.com/ https://github.mysite2.com/"> <textarea data-store="GHEURLS" placeholder="https://github.mysite1.com/ https://github.mysite2.com/">
</textarea> </textarea>
</div> </div>
<div class="octotree_gitlab_only"> <div class="octotree_gitlab_only">
<div> <div>
<label>GitLab Enterprise URLs</label> <label>GitLab Enterprise URLs</label>
@ -49,24 +70,6 @@
</div> </div>
<!-- @endif --> <!-- @endif -->
<hr />
<div>
<label><input type="checkbox" data-store="REMEMBER"> Remember sidebar visibility</label>
</div>
<div>
<label><input type="checkbox" data-store="NONCODE"> Show in non-code pages</label>
</div>
<div class="octotree_github_only">
<label><input type="checkbox" data-store="LOADALL"> Load entire tree at once</label>
</div>
<div>
<div>
<label>Hotkeys</label>
<a href="https://github.com/madrobby/keymaster#defining-shortcuts" target="_blank" tabIndex="-1">supported keys</a>
</div>
<input type="text" data-store="HOTKEYS">
</div>
<div> <div>
<button type="submit" class="btn">Save</button> <button type="submit" class="btn">Save</button>
</div> </div>

20
src/util.storage.js Normal file
View File

@ -0,0 +1,20 @@
class Storage {
set(key, val, cb) {
localStorage.setItem(key, JSON.stringify(val))
if (cb) cb()
}
get(key, cb) {
var val = parse(localStorage.getItem(key))
if (cb) cb(val)
else return val
function parse(val) {
try {
return JSON.parse(val)
} catch (e) {
return val
}
}
}
}

View File

@ -5,13 +5,27 @@ class HelpPopup {
} }
show() { show() {
const $view = this.$view
const store = this.store const store = this.store
const popupShown = store.get(STORE.POPUP) const popupShown = store.get(STORE.POPUP)
const sidebarVisible = $('html').hasClass(PREFIX)
if (popupShown) return if (popupShown || sidebarVisible) {
store.set(STORE.POPUP, true)
return
}
$view.css('display', 'block').appendTo($('body')) const $view = this.$view
const $toggler = $('.octotree_toggle')
const offset = $toggler.offset()
const height = $toggler.outerHeight()
$view
.css({
display: 'block',
top: offset.top + height + 2,
left: offset.left
})
$view.appendTo($('body'))
$(document).one(EVENT.TOGGLE, hide) $(document).one(EVENT.TOGGLE, hide)

View File

@ -28,7 +28,7 @@ class OptionsView {
_load() { _load() {
this._eachOption( this._eachOption(
($elm, key, local, value, cb) => { ($elm, key, value, cb) => {
if ($elm.is(':checkbox')) $elm.prop('checked', value) if ($elm.is(':checkbox')) $elm.prop('checked', value)
else $elm.val(value) else $elm.val(value)
cb() cb()
@ -70,11 +70,11 @@ class OptionsView {
_saveOptions() { _saveOptions() {
const changes = {} const changes = {}
this._eachOption( this._eachOption(
($elm, key, local, value, cb) => { ($elm, key, value, cb) => {
const newValue = $elm.is(':checkbox') ? $elm.is(':checked') : $elm.val() const newValue = $elm.is(':checkbox') ? $elm.is(':checked') : $elm.val()
if (value === newValue) return cb() if (value === newValue) return cb()
changes[key] = [value, newValue] changes[key] = [value, newValue]
this.store.set(key, newValue, local, cb) this.store.set(key, newValue, cb)
}, },
() => { () => {
this._toggle(false) this._toggle(false)
@ -90,10 +90,9 @@ class OptionsView {
(elm, cb) => { (elm, cb) => {
const $elm = $(elm) const $elm = $(elm)
const key = STORE[$elm.data('store')] const key = STORE[$elm.data('store')]
const local = !!$elm.data('perhost')
this.store.get(key, local, (value) => { this.store.get(key, (value) => {
processFn($elm, key, local, value, () => cb()) processFn($elm, key, value, () => cb())
}) })
}, },
completeFn completeFn