fix #57: support custom Dropbox apps and folders

This commit is contained in:
Antelle 2016-03-18 00:10:49 +03:00
parent 060d4cd3ee
commit fff01ce091
9 changed files with 192 additions and 16 deletions

View File

@ -12,8 +12,7 @@ var logger = new Logger('dropbox');
var DropboxKeys = {
AppFolder: 'qp7ctun6qt5n9d6',
FullDropbox: 'eor7hvv6u6oslq9',
AppFolderKeyParts: ['qp7ctun6', 'qt5n9d6'] // to allow replace key by sed, compare in this way
FullDropbox: 'eor7hvv6u6oslq9'
};
var DropboxCustomErrors = {
@ -128,6 +127,8 @@ var DropboxLink = {
ERROR_CONFLICT: Dropbox.ApiError.CONFLICT,
ERROR_NOT_FOUND: Dropbox.ApiError.NOT_FOUND,
Keys: DropboxKeys,
_getClient: function(complete, overrideAppKey) {
if (this._dropboxClient && this._dropboxClient.isAuthenticated()) {
complete(null, this._dropboxClient);
@ -244,11 +245,19 @@ var DropboxLink = {
});
},
isValidKey: function() {
var isSelfHostedApp = !/^http(s?):\/\/localhost:8085/.test(location.href) &&
canUseBuiltInKeys: function() {
var isSelfHosted = !/^http(s?):\/\/localhost:8085/.test(location.href) &&
!/http(s?):\/\/antelle\.github\.io\/keeweb/.test(location.href) &&
!/http(s?):\/\/app\.keeweb\.info/.test(location.href);
return Launcher || !isSelfHostedApp || getKey() !== DropboxKeys.AppFolderKeyParts.join('');
return !!Launcher || !isSelfHosted;
},
getKey: getKey,
isValidKey: function() {
var key = getKey();
var isBuiltIn = key === DropboxKeys.AppFolder || key === DropboxKeys.FullDropbox;
return key && key.indexOf(' ') < 0 && (!isBuiltIn || this.canUseBuiltInKeys());
},
authenticate: function(complete, overrideAppKey) {

View File

@ -49,6 +49,14 @@ var StorageDropbox = {
return path;
},
_fixConfigFolder: function(folder) {
folder = folder.replace(/\\/g, '/').trim();
if (folder[0] === '/') {
folder = folder.substr(1);
}
return folder;
},
needShowOpenConfig: function() {
return !DropboxLink.isValidKey();
},
@ -57,20 +65,46 @@ var StorageDropbox = {
return {
desc: 'dropboxSetupDesc',
fields: [
{id: 'key', title: 'dropboxAppKey', desc: 'dropboxAppKeyDesc', type: 'text', required: true, pattern: '\\w{10,}'},
{id: 'key', title: 'dropboxAppKey', desc: 'dropboxAppKeyDesc', type: 'text', required: true, pattern: '\\w+'},
{id: 'folder', title: 'dropboxFolder', desc: 'dropboxFolderDesc', type: 'text', placeholder: 'dropboxFolderPlaceholder'}
]
};
},
getSettingsConfig: function() {
var fields = [];
var appKey = DropboxLink.getKey();
var linkField = {id: 'link', title: 'dropboxLink', type: 'select', value: 'custom',
options: { app: 'dropboxLinkApp', full: 'dropboxLinkFull', custom: 'dropboxLinkCustom' } };
var keyField = {id: 'key', title: 'dropboxAppKey', desc: 'dropboxAppKeyDesc', type: 'text', required: true, pattern: '\\w+',
value: appKey};
var folderField = {id: 'folder', title: 'dropboxFolder', desc: 'dropboxFolderSettingsDesc', type: 'text',
value: AppSettingsModel.instance.get('dropboxFolder') || ''};
var canUseBuiltInKeys = DropboxLink.canUseBuiltInKeys();
if (canUseBuiltInKeys) {
fields.push(linkField);
if (appKey === DropboxLink.Keys.AppFolder) {
linkField.value = 'app';
} else if (appKey === DropboxLink.Keys.FullDropbox) {
linkField.value = 'full';
fields.push(folderField);
} else {
fields.push(keyField);
fields.push(folderField);
}
} else {
fields.push(keyField);
fields.push(folderField);
}
return { fields: fields };
},
applyConfig: function(config, callback) {
var that = this;
DropboxLink.authenticate(function(err) {
if (!err) {
if (config.folder) {
config.folder = config.folder.replace(/\\/g, '/').trim();
if (config.folder[0] === '/') {
config.folder = config.folder.substr(1);
}
config.folder = that._fixConfigFolder(config.folder);
}
AppSettingsModel.instance.set({
dropboxAppKey: config.key,
@ -81,6 +115,35 @@ var StorageDropbox = {
}, config.key);
},
applySetting: function(key, value) {
switch (key) {
case 'link':
key = 'dropboxAppKey';
switch (value) {
case 'app':
value = DropboxLink.Keys.AppFolder;
break;
case 'full':
value = DropboxLink.Keys.FullDropbox;
break;
case 'custom':
value = '(your app key)';
break;
}
break;
case 'key':
key = 'dropboxAppKey';
break;
case 'folder':
key = 'dropboxFolder';
value = this._fixConfigFolder(value);
break;
default:
return;
}
AppSettingsModel.instance.set(key, value);
},
getPathForName: function(fileName) {
return '/' + fileName + '.kdbx';
},

View File

@ -375,7 +375,12 @@ var Locale = {
dropboxAppKeyDesc: 'Copy the key from your Dropbox app (Developer settings)',
dropboxFolder: 'App folder',
dropboxFolderDesc: 'If your app is linked to entire Dropbox (not app folder), set the folder with your Kdbx files here',
dropboxFolderSettingsDesc: 'Select any folder in your Dropbox where files will be stored (root folder by default)',
dropboxFolderPlaceholder: 'default folder',
dropboxLink: 'Link the app to',
dropboxLinkApp: 'App folder (Apps/KeeWeb)',
dropboxLinkFull: 'Full Dropbox / Select a folder',
dropboxLinkCustom: 'Own Dropbox app',
launcherSave: 'Save Passwords Database',
launcherFileFilter: 'KeePass files'

View File

@ -1,6 +1,7 @@
'use strict';
var Backbone = require('backbone'),
SettingsPrvView = require('./settings-prv-view'),
Launcher = require('../../comp/launcher'),
Updater = require('../../comp/updater'),
Format = require('../../util/format'),
@ -36,6 +37,8 @@ var SettingsGeneralView = Backbone.View.extend({
'click .settings__general-dev-tools-link': 'openDevTools'
},
views: {},
allThemes: {
fb: 'Flat blue',
db: 'Dark brown',
@ -50,7 +53,8 @@ var SettingsGeneralView = Backbone.View.extend({
render: function() {
var updateReady = UpdateModel.instance.get('updateStatus') === 'ready',
updateFound = UpdateModel.instance.get('updateStatus') === 'found',
updateManual = UpdateModel.instance.get('updateManual');
updateManual = UpdateModel.instance.get('updateManual'),
storageProviders = this.getStorageProviders();
this.renderTemplate({
themes: this.allThemes,
activeTheme: AppSettingsModel.instance.get('theme'),
@ -78,8 +82,23 @@ var SettingsGeneralView = Backbone.View.extend({
updateManual: updateManual,
releaseNotesLink: Links.ReleaseNotes,
colorfulIcons: AppSettingsModel.instance.get('colorfulIcons'),
storageProviders: this.getStorageProviders()
storageProviders: storageProviders
});
this.renderProviderViews(storageProviders);
},
renderProviderViews: function(storageProviders) {
storageProviders.forEach(function(prv) {
if (this.views[prv.name]) {
this.views[prv.name].remove();
}
if (prv.hasConfig) {
this.views[prv.name] = new SettingsPrvView({
el: this.$el.find('.settings__general-' + prv.name),
model: prv
}).render();
}
}, this);
},
getUpdateInfo: function() {
@ -131,7 +150,8 @@ var SettingsGeneralView = Backbone.View.extend({
return storageProviders.map(function(sp) {
return {
name: sp.name,
enabled: sp.enabled
enabled: sp.enabled,
hasConfig: sp.getSettingsConfig
};
});
},
@ -232,6 +252,7 @@ var SettingsGeneralView = Backbone.View.extend({
if (storage) {
storage.enabled = e.target.checked;
AppSettingsModel.instance.set(storage.name, storage.enabled);
this.$el.find('.settings__general-' + storage.name).toggleClass('hide', !e.target.checked);
}
},

View File

@ -0,0 +1,36 @@
'use strict';
var Backbone = require('backbone'),
Storage = require('../../storage');
var SettingsPrvView = Backbone.View.extend({
template: require('templates/settings/settings-prv.hbs'),
events: {
'change .settings__general-prv-field-sel': 'changeField',
'input .settings__general-prv-field-txt': 'changeField'
},
render: function () {
var storage = Storage[this.model.name];
if (storage && storage.getSettingsConfig) {
this.renderTemplate(storage.getSettingsConfig());
}
return this;
},
changeField: function(e) {
var id = e.target.dataset.id,
value = e.target.value;
if (!e.target.checkValidity()) {
return;
}
var storage = Storage[this.model.name];
storage.applySetting(id, value);
if ($(e.target).is('select')) {
this.render();
}
}
});
module.exports = SettingsPrvView;

View File

@ -88,4 +88,10 @@
width: 15em;
margin-right: $small-spacing;
}
&__general-storage-header {
margin-bottom: 0;
}
&__general-prv {
margin-bottom: $base-padding-v;
}
}

View File

@ -113,9 +113,11 @@
<h2>{{res 'setGenStorage'}}</h2>
{{#each storageProviders as |prv|}}
<h3><input type="checkbox" id="settings__general-prv-check-{{prv.name}}" class="settings__general-prv-check"
data-storage="{{prv.name}}" {{#if prv.enabled}}checked{{/if}}
/><label for="settings__general-prv-check-{{prv.name}}">{{res prv.name}}</label></h3>
<h4 class="settings__general-storage-header"><input
type="checkbox" id="settings__general-prv-check-{{prv.name}}" class="settings__general-prv-check"
data-storage="{{prv.name}}" {{#if prv.enabled}}checked{{/if}}
/><label for="settings__general-prv-check-{{prv.name}}">{{res prv.name}}</label></h4>
<div class="settings__general-prv-wrap settings__general-{{prv.name}} {{#ifeq prv.enabled false}}hide{{/ifeq}}"></div>
{{/each}}
{{#if devTools}}

View File

@ -0,0 +1,33 @@
<div class="settings__general-prv settings__general-prv-{{name}}">
{{#if desc}}<div class="settings__general-prv-desc">{{res desc}}</div>{{/if}}
<div class="settings__general-prv-fields">
{{#each fields as |field ix|}}
{{#ifeq type 'select'}}
<div>
<label for="settings__general-prv-field-sel-{{id}}">{{res title}}:</label>
<select
class="settings__select input-base settings__general-prv-field settings__general-prv-field-sel"
id="settings__general-prv-field-sel-{{id}}"
data-id="{{id}}">
{{#each options as |title val|}}
<option value="{{val}}" {{#ifeq ../value val}}selected{{/ifeq}}>{{res title}}</option>
{{/each}}
</select>
</div>
{{else}}
<label for="settings__general-prv-field-txt-{{id}}">{{res title}}:</label>
{{#if desc}}<div class="settings__general-prv-field-desc muted-color">{{res desc}}</div>{{/if}}
<input type="{{type}}"
class="input-base settings__general-prv-field settings__input settings__general-prv-field-txt"
id="settings__general-prv-field-txt-{{id}}"
autocomplete="off"
value="{{value}}"
data-id="{{id}}"
{{#if placeholder}}placeholder="{{res placeholder}}"{{/if}}
{{#if required}}required{{/if}}
{{#if pattern}}pattern="{{{pattern}}}"{{/if}}
/>
{{/ifeq}}
{{/each}}
</div>
</div>

View File

@ -22,6 +22,7 @@ Storage providers, usability improvements
`+` overall spacing increased
`+` hide demo button once opened
`+` show error details on open
`+` select dropbox folder
`-` fix capslock indicator
`-` fix file settings input behavior
`-` fix favicon download