mirror of https://github.com/keeweb/keeweb.git
fix #57: support custom Dropbox apps and folders
This commit is contained in:
parent
060d4cd3ee
commit
fff01ce091
|
@ -12,8 +12,7 @@ var logger = new Logger('dropbox');
|
||||||
|
|
||||||
var DropboxKeys = {
|
var DropboxKeys = {
|
||||||
AppFolder: 'qp7ctun6qt5n9d6',
|
AppFolder: 'qp7ctun6qt5n9d6',
|
||||||
FullDropbox: 'eor7hvv6u6oslq9',
|
FullDropbox: 'eor7hvv6u6oslq9'
|
||||||
AppFolderKeyParts: ['qp7ctun6', 'qt5n9d6'] // to allow replace key by sed, compare in this way
|
|
||||||
};
|
};
|
||||||
|
|
||||||
var DropboxCustomErrors = {
|
var DropboxCustomErrors = {
|
||||||
|
@ -128,6 +127,8 @@ var DropboxLink = {
|
||||||
ERROR_CONFLICT: Dropbox.ApiError.CONFLICT,
|
ERROR_CONFLICT: Dropbox.ApiError.CONFLICT,
|
||||||
ERROR_NOT_FOUND: Dropbox.ApiError.NOT_FOUND,
|
ERROR_NOT_FOUND: Dropbox.ApiError.NOT_FOUND,
|
||||||
|
|
||||||
|
Keys: DropboxKeys,
|
||||||
|
|
||||||
_getClient: function(complete, overrideAppKey) {
|
_getClient: function(complete, overrideAppKey) {
|
||||||
if (this._dropboxClient && this._dropboxClient.isAuthenticated()) {
|
if (this._dropboxClient && this._dropboxClient.isAuthenticated()) {
|
||||||
complete(null, this._dropboxClient);
|
complete(null, this._dropboxClient);
|
||||||
|
@ -244,11 +245,19 @@ var DropboxLink = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
isValidKey: function() {
|
canUseBuiltInKeys: function() {
|
||||||
var isSelfHostedApp = !/^http(s?):\/\/localhost:8085/.test(location.href) &&
|
var isSelfHosted = !/^http(s?):\/\/localhost:8085/.test(location.href) &&
|
||||||
!/http(s?):\/\/antelle\.github\.io\/keeweb/.test(location.href) &&
|
!/http(s?):\/\/antelle\.github\.io\/keeweb/.test(location.href) &&
|
||||||
!/http(s?):\/\/app\.keeweb\.info/.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) {
|
authenticate: function(complete, overrideAppKey) {
|
||||||
|
|
|
@ -49,6 +49,14 @@ var StorageDropbox = {
|
||||||
return path;
|
return path;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
_fixConfigFolder: function(folder) {
|
||||||
|
folder = folder.replace(/\\/g, '/').trim();
|
||||||
|
if (folder[0] === '/') {
|
||||||
|
folder = folder.substr(1);
|
||||||
|
}
|
||||||
|
return folder;
|
||||||
|
},
|
||||||
|
|
||||||
needShowOpenConfig: function() {
|
needShowOpenConfig: function() {
|
||||||
return !DropboxLink.isValidKey();
|
return !DropboxLink.isValidKey();
|
||||||
},
|
},
|
||||||
|
@ -57,20 +65,46 @@ var StorageDropbox = {
|
||||||
return {
|
return {
|
||||||
desc: 'dropboxSetupDesc',
|
desc: 'dropboxSetupDesc',
|
||||||
fields: [
|
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'}
|
{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) {
|
applyConfig: function(config, callback) {
|
||||||
|
var that = this;
|
||||||
DropboxLink.authenticate(function(err) {
|
DropboxLink.authenticate(function(err) {
|
||||||
if (!err) {
|
if (!err) {
|
||||||
if (config.folder) {
|
if (config.folder) {
|
||||||
config.folder = config.folder.replace(/\\/g, '/').trim();
|
config.folder = that._fixConfigFolder(config.folder);
|
||||||
if (config.folder[0] === '/') {
|
|
||||||
config.folder = config.folder.substr(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
AppSettingsModel.instance.set({
|
AppSettingsModel.instance.set({
|
||||||
dropboxAppKey: config.key,
|
dropboxAppKey: config.key,
|
||||||
|
@ -81,6 +115,35 @@ var StorageDropbox = {
|
||||||
}, config.key);
|
}, 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) {
|
getPathForName: function(fileName) {
|
||||||
return '/' + fileName + '.kdbx';
|
return '/' + fileName + '.kdbx';
|
||||||
},
|
},
|
||||||
|
|
|
@ -375,7 +375,12 @@ var Locale = {
|
||||||
dropboxAppKeyDesc: 'Copy the key from your Dropbox app (Developer settings)',
|
dropboxAppKeyDesc: 'Copy the key from your Dropbox app (Developer settings)',
|
||||||
dropboxFolder: 'App folder',
|
dropboxFolder: 'App folder',
|
||||||
dropboxFolderDesc: 'If your app is linked to entire Dropbox (not app folder), set the folder with your Kdbx files here',
|
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',
|
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',
|
launcherSave: 'Save Passwords Database',
|
||||||
launcherFileFilter: 'KeePass files'
|
launcherFileFilter: 'KeePass files'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var Backbone = require('backbone'),
|
var Backbone = require('backbone'),
|
||||||
|
SettingsPrvView = require('./settings-prv-view'),
|
||||||
Launcher = require('../../comp/launcher'),
|
Launcher = require('../../comp/launcher'),
|
||||||
Updater = require('../../comp/updater'),
|
Updater = require('../../comp/updater'),
|
||||||
Format = require('../../util/format'),
|
Format = require('../../util/format'),
|
||||||
|
@ -36,6 +37,8 @@ var SettingsGeneralView = Backbone.View.extend({
|
||||||
'click .settings__general-dev-tools-link': 'openDevTools'
|
'click .settings__general-dev-tools-link': 'openDevTools'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
views: {},
|
||||||
|
|
||||||
allThemes: {
|
allThemes: {
|
||||||
fb: 'Flat blue',
|
fb: 'Flat blue',
|
||||||
db: 'Dark brown',
|
db: 'Dark brown',
|
||||||
|
@ -50,7 +53,8 @@ var SettingsGeneralView = Backbone.View.extend({
|
||||||
render: function() {
|
render: function() {
|
||||||
var updateReady = UpdateModel.instance.get('updateStatus') === 'ready',
|
var updateReady = UpdateModel.instance.get('updateStatus') === 'ready',
|
||||||
updateFound = UpdateModel.instance.get('updateStatus') === 'found',
|
updateFound = UpdateModel.instance.get('updateStatus') === 'found',
|
||||||
updateManual = UpdateModel.instance.get('updateManual');
|
updateManual = UpdateModel.instance.get('updateManual'),
|
||||||
|
storageProviders = this.getStorageProviders();
|
||||||
this.renderTemplate({
|
this.renderTemplate({
|
||||||
themes: this.allThemes,
|
themes: this.allThemes,
|
||||||
activeTheme: AppSettingsModel.instance.get('theme'),
|
activeTheme: AppSettingsModel.instance.get('theme'),
|
||||||
|
@ -78,8 +82,23 @@ var SettingsGeneralView = Backbone.View.extend({
|
||||||
updateManual: updateManual,
|
updateManual: updateManual,
|
||||||
releaseNotesLink: Links.ReleaseNotes,
|
releaseNotesLink: Links.ReleaseNotes,
|
||||||
colorfulIcons: AppSettingsModel.instance.get('colorfulIcons'),
|
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() {
|
getUpdateInfo: function() {
|
||||||
|
@ -131,7 +150,8 @@ var SettingsGeneralView = Backbone.View.extend({
|
||||||
return storageProviders.map(function(sp) {
|
return storageProviders.map(function(sp) {
|
||||||
return {
|
return {
|
||||||
name: sp.name,
|
name: sp.name,
|
||||||
enabled: sp.enabled
|
enabled: sp.enabled,
|
||||||
|
hasConfig: sp.getSettingsConfig
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -232,6 +252,7 @@ var SettingsGeneralView = Backbone.View.extend({
|
||||||
if (storage) {
|
if (storage) {
|
||||||
storage.enabled = e.target.checked;
|
storage.enabled = e.target.checked;
|
||||||
AppSettingsModel.instance.set(storage.name, storage.enabled);
|
AppSettingsModel.instance.set(storage.name, storage.enabled);
|
||||||
|
this.$el.find('.settings__general-' + storage.name).toggleClass('hide', !e.target.checked);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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;
|
|
@ -88,4 +88,10 @@
|
||||||
width: 15em;
|
width: 15em;
|
||||||
margin-right: $small-spacing;
|
margin-right: $small-spacing;
|
||||||
}
|
}
|
||||||
|
&__general-storage-header {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
&__general-prv {
|
||||||
|
margin-bottom: $base-padding-v;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -113,9 +113,11 @@
|
||||||
|
|
||||||
<h2>{{res 'setGenStorage'}}</h2>
|
<h2>{{res 'setGenStorage'}}</h2>
|
||||||
{{#each storageProviders as |prv|}}
|
{{#each storageProviders as |prv|}}
|
||||||
<h3><input type="checkbox" id="settings__general-prv-check-{{prv.name}}" class="settings__general-prv-check"
|
<h4 class="settings__general-storage-header"><input
|
||||||
data-storage="{{prv.name}}" {{#if prv.enabled}}checked{{/if}}
|
type="checkbox" id="settings__general-prv-check-{{prv.name}}" class="settings__general-prv-check"
|
||||||
/><label for="settings__general-prv-check-{{prv.name}}">{{res prv.name}}</label></h3>
|
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}}
|
{{/each}}
|
||||||
|
|
||||||
{{#if devTools}}
|
{{#if devTools}}
|
||||||
|
|
|
@ -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>
|
|
@ -22,6 +22,7 @@ Storage providers, usability improvements
|
||||||
`+` overall spacing increased
|
`+` overall spacing increased
|
||||||
`+` hide demo button once opened
|
`+` hide demo button once opened
|
||||||
`+` show error details on open
|
`+` show error details on open
|
||||||
|
`+` select dropbox folder
|
||||||
`-` fix capslock indicator
|
`-` fix capslock indicator
|
||||||
`-` fix file settings input behavior
|
`-` fix file settings input behavior
|
||||||
`-` fix favicon download
|
`-` fix favicon download
|
||||||
|
|
Loading…
Reference in New Issue