From fff01ce0910c78fa8f2fb27c8f3a85ab8ddb6d88 Mon Sep 17 00:00:00 2001 From: Antelle Date: Fri, 18 Mar 2016 00:10:49 +0300 Subject: [PATCH] fix #57: support custom Dropbox apps and folders --- app/scripts/comp/dropbox-link.js | 19 +++-- app/scripts/storage/storage-dropbox.js | 73 +++++++++++++++++-- app/scripts/util/locale.js | 5 ++ .../views/settings/settings-general-view.js | 27 ++++++- .../views/settings/settings-prv-view.js | 36 +++++++++ app/styles/areas/_settings.scss | 6 ++ app/templates/settings/settings-general.hbs | 8 +- app/templates/settings/settings-prv.hbs | 33 +++++++++ release-notes.md | 1 + 9 files changed, 192 insertions(+), 16 deletions(-) create mode 100644 app/scripts/views/settings/settings-prv-view.js create mode 100644 app/templates/settings/settings-prv.hbs diff --git a/app/scripts/comp/dropbox-link.js b/app/scripts/comp/dropbox-link.js index 9632c1f5..164ce638 100644 --- a/app/scripts/comp/dropbox-link.js +++ b/app/scripts/comp/dropbox-link.js @@ -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) { diff --git a/app/scripts/storage/storage-dropbox.js b/app/scripts/storage/storage-dropbox.js index ea593dea..92dbb906 100644 --- a/app/scripts/storage/storage-dropbox.js +++ b/app/scripts/storage/storage-dropbox.js @@ -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'; }, diff --git a/app/scripts/util/locale.js b/app/scripts/util/locale.js index f73bac44..78f8b275 100644 --- a/app/scripts/util/locale.js +++ b/app/scripts/util/locale.js @@ -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' diff --git a/app/scripts/views/settings/settings-general-view.js b/app/scripts/views/settings/settings-general-view.js index ae086ff2..02d2a5da 100644 --- a/app/scripts/views/settings/settings-general-view.js +++ b/app/scripts/views/settings/settings-general-view.js @@ -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); } }, diff --git a/app/scripts/views/settings/settings-prv-view.js b/app/scripts/views/settings/settings-prv-view.js new file mode 100644 index 00000000..1aba3882 --- /dev/null +++ b/app/scripts/views/settings/settings-prv-view.js @@ -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; diff --git a/app/styles/areas/_settings.scss b/app/styles/areas/_settings.scss index facb109c..51ce7278 100644 --- a/app/styles/areas/_settings.scss +++ b/app/styles/areas/_settings.scss @@ -88,4 +88,10 @@ width: 15em; margin-right: $small-spacing; } + &__general-storage-header { + margin-bottom: 0; + } + &__general-prv { + margin-bottom: $base-padding-v; + } } diff --git a/app/templates/settings/settings-general.hbs b/app/templates/settings/settings-general.hbs index 26433098..8ea1c697 100644 --- a/app/templates/settings/settings-general.hbs +++ b/app/templates/settings/settings-general.hbs @@ -113,9 +113,11 @@

{{res 'setGenStorage'}}

{{#each storageProviders as |prv|}} -

+

+
{{/each}} {{#if devTools}} diff --git a/app/templates/settings/settings-prv.hbs b/app/templates/settings/settings-prv.hbs new file mode 100644 index 00000000..d9ed8718 --- /dev/null +++ b/app/templates/settings/settings-prv.hbs @@ -0,0 +1,33 @@ +
+ {{#if desc}}
{{res desc}}
{{/if}} +
+ {{#each fields as |field ix|}} + {{#ifeq type 'select'}} +
+ + +
+ {{else}} + + {{#if desc}}
{{res desc}}
{{/if}} + + {{/ifeq}} + {{/each}} +
+
diff --git a/release-notes.md b/release-notes.md index 868e6e7a..ac9450a2 100644 --- a/release-notes.md +++ b/release-notes.md @@ -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