mirror of
http://git.whoc.org.uk/git/password-manager.git
synced 2025-10-29 18:37:35 +01:00
Merged Import and Export branches, implemented Giulio's remarks on Import feature
This commit is contained in:
@@ -233,6 +233,8 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.DirectLogin, Object, {
|
||||
|
||||
'serializedData': function () {
|
||||
return Clipperz.Async.collectResults("DirectLogin.serializedData", {
|
||||
'favicon': MochiKit.Base.method(this,'favicon'),
|
||||
'label': MochiKit.Base.method(this,'label'),
|
||||
'bookmarkletVersion': MochiKit.Base.method(this, 'getValue', 'bookmarkletVersion'),
|
||||
'formData': MochiKit.Base.method(this, 'getValue', 'formData'),
|
||||
'formValues': MochiKit.Base.method(this, 'getValue', 'formValues'),
|
||||
|
||||
@@ -316,6 +316,36 @@ console.log("Record.Version.hasPendingChanges");
|
||||
* /
|
||||
},
|
||||
*/
|
||||
|
||||
//=========================================================================
|
||||
|
||||
// TODO: this function may mix up the order of the fields
|
||||
'exportFields': function() {
|
||||
var deferredResult;
|
||||
var fields;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred('Record.Version.export', {trace:false});
|
||||
deferredResult.addMethod(this,'fields');
|
||||
deferredResult.addCallback(MochiKit.Base.values);
|
||||
deferredResult.addCallback(MochiKit.Base.map, function(fieldIn) {
|
||||
return fieldIn.content();
|
||||
});
|
||||
deferredResult.addCallback(Clipperz.Async.collectAll);
|
||||
deferredResult.addCallback(function(listIn) {
|
||||
// return listIn.reduce(function(result, field) {
|
||||
return MochiKit.Iter.reduce(function(result, field) {
|
||||
var ref = field.reference;
|
||||
result[ref] = field;
|
||||
delete result[ref].reference;
|
||||
return result;
|
||||
}, listIn, {});
|
||||
});
|
||||
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
__syntaxFix__: "syntax fix"
|
||||
});
|
||||
|
||||
@@ -45,9 +45,7 @@ Clipperz.PM.DataModel.Record = function(args) {
|
||||
this._createNewDirectLoginFunction = args.createNewDirectLoginFunction || null;
|
||||
|
||||
this._tags = [];
|
||||
|
||||
this._directLogins = {};
|
||||
|
||||
this._versions = {};
|
||||
|
||||
this._currentRecordVersion = null;
|
||||
@@ -163,16 +161,20 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
|
||||
|
||||
//............................................................................
|
||||
|
||||
'tagRegExp': function () { return Clipperz.PM.DataModel.Record.tagRegExp(); },
|
||||
'trimSpacesRegExp': function () { return Clipperz.PM.DataModel.Record.tagRegExp(); },
|
||||
'filterOutTags': function (aValue) { return Clipperz.PM.DataModel.Record.filterOutTags(aValue); },
|
||||
'extractLabelFromFullLabel': function (aValue) {
|
||||
return Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aValue);
|
||||
},
|
||||
|
||||
'extractTagsFromFullLabel': function (aLabel) {
|
||||
return Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aLabel);
|
||||
},
|
||||
|
||||
//............................................................................
|
||||
|
||||
'label': function () {
|
||||
return Clipperz.Async.callbacks("Record.label", [
|
||||
MochiKit.Base.method(this, 'fullLabel'),
|
||||
MochiKit.Base.method(this, 'filterOutTags')
|
||||
MochiKit.Base.method(this, 'extractLabelFromFullLabel')
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
@@ -193,22 +195,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
|
||||
|
||||
//.........................................................................
|
||||
|
||||
'extractTagsFromFullLabel': function (aLabel) {
|
||||
var tagRegEx;
|
||||
var result;
|
||||
var match;
|
||||
|
||||
result = {};
|
||||
tagRegEx = this.tagRegExp();
|
||||
match = tagRegEx.exec(aLabel);
|
||||
while (match != null) {
|
||||
result[match[1]] = true;
|
||||
match = tagRegEx.exec(aLabel);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
'tags': function () {
|
||||
return Clipperz.Async.callbacks("Record.label", [
|
||||
MochiKit.Base.method(this, 'fullLabel'),
|
||||
@@ -1163,6 +1149,64 @@ console.log("Record.hasPendingChanges RESULT", result);
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
'exportDirectLogins': function() {
|
||||
var result;
|
||||
var directLoginsObject = this.directLogins();
|
||||
|
||||
if (MochiKit.Base.keys(directLoginsObject).length == 0) {
|
||||
result = {};
|
||||
} else {
|
||||
var callbackObject = Object.keys(directLoginsObject).reduce(function(previous, current) {
|
||||
previous[current] = MochiKit.Base.method( directLoginsObject[current], 'serializedData' );
|
||||
return previous;
|
||||
}, {});
|
||||
|
||||
result = Clipperz.Async.collectResults("Record.exportDirectLogins", callbackObject,{trace:false})();
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
'export': function() {
|
||||
var deferredResult;
|
||||
var label;
|
||||
var data;
|
||||
var currentVersion;
|
||||
var directLogins;
|
||||
var currentVersionObject;
|
||||
|
||||
data = {};
|
||||
currentVersion = {};
|
||||
directLogins = {};
|
||||
deferredResult = new Clipperz.Async.Deferred('Record.export', {trace:false});
|
||||
deferredResult.addMethod(this, 'getCurrentRecordVersion');
|
||||
deferredResult.addCallback(function(recordVersionIn) { currentVersionObject = recordVersionIn; })
|
||||
deferredResult.addMethod(this, 'fullLabel');
|
||||
deferredResult.addMethod(this, function(labelIn) {label = labelIn});
|
||||
deferredResult.addMethod(this, 'exportDirectLogins');
|
||||
deferredResult.addCallback(function(directLoginsIn) { data['directLogins'] = directLoginsIn; });
|
||||
deferredResult.addCallback(function() { return currentVersionObject.getKey(); }),
|
||||
// deferredResult.addMethod(this,function(keyIn) { data['currentVersionKey'] = keyIn; });
|
||||
deferredResult.addMethod(this, 'notes');
|
||||
deferredResult.addMethod(this, function(notesIn) { data['notes'] = notesIn; });
|
||||
// deferredResult.addMethod(this, function() { currentVersion['reference'] = this.currentVersionReference(); });
|
||||
deferredResult.addCallback(function() { return currentVersionObject.exportFields(); }),
|
||||
deferredResult.addCallback(function(fieldsIn) { currentVersion['fields'] = fieldsIn; });
|
||||
deferredResult.addMethod(this, function() {
|
||||
return {
|
||||
'label': label,
|
||||
'data': data,
|
||||
'currentVersion': currentVersion
|
||||
};
|
||||
});
|
||||
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
__syntaxFix__: "syntax fix"
|
||||
});
|
||||
@@ -1203,20 +1247,33 @@ Clipperz.PM.DataModel.Record.regExpForSearch = function (aSearch) {
|
||||
return new RegExp(aSearch.replace(/[^A-Za-z0-9]/g, '\\$&'), 'i');
|
||||
};
|
||||
|
||||
Clipperz.PM.DataModel.Record.tagRegExp = function () {
|
||||
return new RegExp('\\' + Clipperz.PM.DataModel.Record.tagChar + '(' + Clipperz.PM.DataModel.Record.specialTagChar + '?\\w+)', 'g');
|
||||
};
|
||||
|
||||
Clipperz.PM.DataModel.Record.trimSpacesRegExp = function () {
|
||||
return new RegExp('^\\s+|\\s+$', 'g');
|
||||
};
|
||||
|
||||
Clipperz.PM.DataModel.Record.filterOutTags = function (aValue) {
|
||||
Clipperz.PM.DataModel.Record.tagRegExp = new RegExp('\\' + Clipperz.PM.DataModel.Record.tagChar + '(' + Clipperz.PM.DataModel.Record.specialTagChar + '?\\w+)', 'g');
|
||||
Clipperz.PM.DataModel.Record.trimSpacesRegExp = new RegExp('^\\s+|\\s+$', 'g');
|
||||
|
||||
Clipperz.PM.DataModel.Record.extractLabelFromFullLabel = function (aValue) {
|
||||
var value;
|
||||
|
||||
value = aValue;
|
||||
value = value.replace(Clipperz.PM.DataModel.Record.tagRegExp(), '');
|
||||
value = value.replace(Clipperz.PM.DataModel.Record.trimSpacesRegExp(), '');
|
||||
value = value.replace(Clipperz.PM.DataModel.Record.tagRegExp, '');
|
||||
value = value.replace(Clipperz.PM.DataModel.Record.trimSpacesRegExp, '');
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
Clipperz.PM.DataModel.Record.extractTagsFromFullLabel = function (aLabel) {
|
||||
var tagRegEx;
|
||||
var result;
|
||||
var match;
|
||||
|
||||
result = {};
|
||||
tagRegEx = Clipperz.PM.DataModel.Record.tagRegExp;
|
||||
match = tagRegEx.exec(aLabel);
|
||||
while (match != null) {
|
||||
result[match[1]] = true;
|
||||
match = tagRegEx.exec(aLabel);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -244,11 +244,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
// TODO: test (taken straight from /beta)
|
||||
'deleteAccount': function() {
|
||||
|
||||
console.log("deleting account from user");
|
||||
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new MochiKit.Async.Deferred("User.deleteAccount", {trace: true});
|
||||
@@ -257,15 +253,9 @@ console.log("deleting account from user");
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
|
||||
|
||||
},
|
||||
|
||||
// TODO: check (I have half of an idea what i'm doing)
|
||||
'resetAllLocalData': function() {
|
||||
|
||||
console.log("resetting all local data...");
|
||||
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new MochiKit.Async.Deferred("User.resetAllLocalData", {trace: true});
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
// featureSet: React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
|
||||
// 'level': React.PropTypes.oneOf(['hide', 'info', 'warning', 'error']).isRequired
|
||||
},
|
||||
/*
|
||||
jsonExport: function () {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export', 'json');
|
||||
},
|
||||
|
||||
htmlExport: function () {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export', 'html');
|
||||
},
|
||||
*/
|
||||
|
||||
isFeatureEnabled: function (aValue) {
|
||||
return (this.props['features'].indexOf(aValue) > -1);
|
||||
},
|
||||
|
||||
handleDownloadOfflineCopyLink: function (anEvent) {
|
||||
if (this.isFeatureEnabled('OFFLINE_COPY')) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'downloadOfflineCopy');
|
||||
}
|
||||
},
|
||||
|
||||
handleExportLink: function () {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'downloadExport');
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature devicePIN'}, [
|
||||
React.DOM.h1({}, "Export"),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.ul({}, [
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "Offline copy"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "Download a read-only portable version of Clipperz. Very convenient when no Internet connection is available."),
|
||||
React.DOM.p({}, "An offline copy is just a single HTML file that contains both the whole Clipperz web application and your encrypted data."),
|
||||
React.DOM.p({}, "It is as secure as the hosted Clipperz service since they both share the same code and security architecture.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.handleDownloadOfflineCopyLink}, "download offline copy")
|
||||
]),
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "HTML + JSON"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "Download a printer-friendly HTML file that lists the content of all your cards."),
|
||||
React.DOM.p({}, "This same file also contains all your data in JSON format."),
|
||||
React.DOM.p({}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.handleExportLink}, "download HTML+JSON")
|
||||
]),
|
||||
/*
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "Printing"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "Click on the button below to open a new window displaying all your cards in a printable format."),
|
||||
React.DOM.p({}, "If you are going to print for backup purposes, please consider the safer option provided by the “offline copy”.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.htmlExport}, "HTML")
|
||||
]),
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "Exporting to JSON"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "JSON enables a “lossless” export of your cards. All the information will be preserved, including direct login configurations."),
|
||||
React.DOM.p({}, "This custom format it’s quite convenient if you need to move some of all of your cards to a different Clipperz account. Or if you want to restore a card that has been accidentally deleted."),
|
||||
React.DOM.p({}, "Click on the button below to start the export process.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.jsonExport}, "JSON"),
|
||||
])
|
||||
*/
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataExport = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass);
|
||||
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures');
|
||||
|
||||
var _steps = ['Input', 'CsvColumns', 'CsvLabels', 'CsvTitles', 'CsvNotes', 'CsvHidden', 'Preview'];
|
||||
var _stepNames = ['Input', 'Columns', 'Labels', 'Titles', 'Notes','Hidden','Preview'];
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImportClass = React.createClass({
|
||||
_steps: _steps,
|
||||
_stepNames: _stepNames,
|
||||
_relevantSteps: {
|
||||
'csv': _steps,
|
||||
'json': [_steps[0], _steps[6]]
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'currentStep': this._steps[0],
|
||||
'importContext': new Clipperz.PM.UI.ImportContext(),
|
||||
'nextStepCallback': null,
|
||||
'error': null
|
||||
};
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
getStepIndex: function(aStep) {
|
||||
return this._steps.indexOf(aStep);
|
||||
},
|
||||
|
||||
getStepAfter: function() {
|
||||
return this._steps[this.getStepIndex(this.state.currentStep) + 1];
|
||||
},
|
||||
|
||||
getStepBefore: function() {
|
||||
return this._steps[this.getStepIndex(this.state.currentStep) - 1];
|
||||
},
|
||||
|
||||
isStepRelevant: function(aStep, aFormat) {
|
||||
if (!aFormat) {
|
||||
return true
|
||||
} else {
|
||||
return (this._relevantSteps[aFormat].indexOf(aStep) >= 0);
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
goToStep: function(aStep) {
|
||||
this.setState({
|
||||
'currentStep': aStep,
|
||||
'nextStepCallback': null,
|
||||
'error': null
|
||||
});
|
||||
},
|
||||
|
||||
handleNextStepOnClick: function() {
|
||||
if (this.state.nextStepCallback) {
|
||||
var newImportContext = this.state.nextStepCallback();
|
||||
|
||||
if (newImportContext) {
|
||||
MochiKit.Base.update(this.state.importContext, newImportContext);
|
||||
|
||||
if (this.state.currentStep == 'Input' && this.state.importContext.format == 'json') {
|
||||
this.goToStep('Preview');
|
||||
} else if (this.state.currentStep == 'Preview') {
|
||||
this.state.importContext.resetContext();
|
||||
this.goToStep('Input');
|
||||
} else {
|
||||
this.goToStep(this.getStepAfter());
|
||||
}
|
||||
} else {
|
||||
if (this.state.currentStep == "Input") {
|
||||
this.setState({'error': "unrecognized input format."});
|
||||
} else {
|
||||
this.setState({'error': "unknown error."});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleBackOnClick: function() {
|
||||
if (this.state.importContext.format == 'json' && this.state.currentStep == 'Preview') {
|
||||
delete this.state.importContext.format;
|
||||
this.goToStep('Input');
|
||||
} else if (this.state.currentStep != this._steps[0]) {
|
||||
this.goToStep(this.getStepBefore());
|
||||
}
|
||||
},
|
||||
|
||||
setNextStepCallback: function(aFunction) {
|
||||
this.setState({'nextStepCallback': aFunction});
|
||||
},
|
||||
|
||||
getStepNavbarClass: function(aStep) {
|
||||
var result;
|
||||
|
||||
if (aStep == this.state.currentStep) {
|
||||
result = 'active';
|
||||
} else if (this.state.importContext.format == 'json' && (aStep>=1&&aStep<=5) ) {
|
||||
result = 'disabled';
|
||||
} else {
|
||||
result = 'inactive';
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature dataImport'}, [
|
||||
React.DOM.h1({}, "Import"),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.ul({'className': 'stepNavbar'},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(aStep){
|
||||
var className;
|
||||
|
||||
if (this.isStepRelevant(aStep,this.state.importContext.format)) {
|
||||
className = (aStep == this.state.currentStep) ? 'active' : 'inactive';
|
||||
} else {
|
||||
className = 'disabled';
|
||||
}
|
||||
|
||||
return React.DOM.li({
|
||||
'className': className
|
||||
}, this._stepNames[this.getStepIndex(aStep)]);
|
||||
}, this),this._steps)
|
||||
),
|
||||
new Clipperz.PM.UI.Components.ExtraFeatures.DataImport[this.state.currentStep]({
|
||||
'importContext': this.state.importContext,
|
||||
'setNextStepCallback': this.setNextStepCallback,
|
||||
}),
|
||||
React.DOM.a({
|
||||
'className': 'button'+((this.state.currentStep == this._steps[0]) ? ' disabled' : ''),
|
||||
'onClick': this.handleBackOnClick,
|
||||
}, "Back"),
|
||||
React.DOM.a({
|
||||
'className': 'button'+((! this.state.nextStepCallback) ? ' disabled' : ''),
|
||||
'onClick': this.handleNextStepOnClick,
|
||||
}, "Next"),
|
||||
(this.state.error) ? React.DOM.p({'className': 'error'}, "Error: " + this.state.error) : null
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImportClass);
|
||||
@@ -26,26 +26,44 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass = React.createClass({
|
||||
|
||||
toggleColumn: function(columnN) {
|
||||
var newState;
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'selectedColumns': this.props.importContext.selectedColumns
|
||||
};
|
||||
},
|
||||
|
||||
newState = {'importData': this.props.importState.importData};
|
||||
newState.importData.selectedColumns[columnN] = ! newState.importData.selectedColumns[columnN];
|
||||
componentDidMount() {
|
||||
this.props.setNextStepCallback(this.handleNextStep);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
handleNextStep: function() {
|
||||
return this.state;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
toggleColumn: function(columnN) {
|
||||
var newSelectedColumns;
|
||||
|
||||
newSelectedColumns = this.state.selectedColumns;
|
||||
newSelectedColumns[columnN] = ! newSelectedColumns[columnN];
|
||||
|
||||
this.props.setImportStateCallback(newState);
|
||||
this.setState({'selectedColumns': newSelectedColumns});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
//console.log(this.props.importContext);
|
||||
var columnSelectors;
|
||||
var rowCount;
|
||||
var i;
|
||||
|
||||
columnSelectors = [];
|
||||
for (i=0; i<this.props.importState.importData.nColumns; i++) {
|
||||
columnSelectors.push( React.DOM.td({'key': 'csv-colsel-'+i}, React.DOM.input({
|
||||
for (i=0; i<this.props.importContext.nColumns; i++) {
|
||||
columnSelectors.push( React.DOM.td({'key': 'csv-colsel-' + i}, React.DOM.input({
|
||||
'type': 'checkbox',
|
||||
'checked': this.props.importState.importData.selectedColumns[i],
|
||||
'checked': this.state.selectedColumns[i],
|
||||
'onChange': MochiKit.Base.partial(this.toggleColumn,i)
|
||||
}) ) );
|
||||
}
|
||||
@@ -53,16 +71,8 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass = React.creat
|
||||
rowCount = 0;
|
||||
|
||||
return React.DOM.div({},[
|
||||
React.DOM.h2({},"Columns"),
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigation({
|
||||
'format': 'csv',
|
||||
'stepId': 'csv-columns',
|
||||
'prevStep': 'input',
|
||||
'nextStep': 'csv-labels',
|
||||
'goToStepCallback': this.props.goToStepCallback,
|
||||
}),
|
||||
React.DOM.p({}, "Select the columns you want to import."),
|
||||
React.DOM.table({'style': {'background': 'white'}},[
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({}, React.DOM.tr({'className': 'columnSelectors', 'key': 'csv-colsel'}, columnSelectors)),
|
||||
React.DOM.tbody({},
|
||||
MochiKit.Base.map(function(row){
|
||||
@@ -70,13 +80,13 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass = React.creat
|
||||
var result
|
||||
|
||||
cellCount = 0;
|
||||
result = React.DOM.tr({'key': 'csv-row-'+(rowCount++)}, MochiKit.Base.map(function(cell) {
|
||||
return React.DOM.td({'key': 'csv-cell-'+rowCount+'-'+(cellCount++)},cell);
|
||||
result = React.DOM.tr({'key': 'csv-row-' + (rowCount++)}, MochiKit.Base.map(function(cell) {
|
||||
return React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount++)},cell);
|
||||
}, row));
|
||||
rowCount++;
|
||||
|
||||
return result;
|
||||
}, this.props.importState.importData.parsedCSV)
|
||||
}, this.props.importContext.parsedCsv)
|
||||
),
|
||||
])
|
||||
]);
|
||||
|
||||
@@ -25,53 +25,118 @@ refer to http://www.clipperz.com.
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvHiddenClass = React.createClass({
|
||||
|
||||
checkedCallback: function(columnN) {
|
||||
return this.props.importState.importData.hiddenColumns[columnN];
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'hiddenColumns': this.props.importContext.hiddenColumns
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.props.setNextStepCallback(this.handleNextStep);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
handleNextStep: function() {
|
||||
//var importData = this.props.importState.importData;
|
||||
//var json = this.props.csvToJsonCallback();
|
||||
//this.props.setImportStateCallback({
|
||||
// 'importData': importData,
|
||||
// 'jsonToImport': json,
|
||||
// 'recordsToImport': MochiKit.Base.map(function(r){return r._importId},json),
|
||||
// 'currentStep': 'preview',
|
||||
// 'previousStep': 'csv-hidden'
|
||||
//})
|
||||
|
||||
MochiKit.Base.update(this.props.importContext, this.state);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
onChangeCallback: function(columnN) {
|
||||
var newState = {'importData': this.props.importState.importData};
|
||||
var newHiddenColumns = this.state.hiddenColumns;
|
||||
|
||||
newState.importData.hiddenColumns[columnN] = ! newState.importData.hiddenColumns[columnN];
|
||||
newHiddenColumns[columnN] = ! newHiddenColumns[columnN];
|
||||
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
disabledCallback: function(columnN) {
|
||||
return (columnN == this.props.importState.importData.titlesColumn || columnN == this.props.importState.importData.notesColumn)
|
||||
this.setState({'hiddenColumns': newHiddenColumns});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var importData = this.props.importState.importData;
|
||||
var cellCount, rowCount;
|
||||
|
||||
var importContext = this.props.importContext;
|
||||
|
||||
cellCount = 0;
|
||||
rowCount = 0;
|
||||
return React.DOM.div({},[
|
||||
React.DOM.h2({},"Hidden"),
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigation({
|
||||
'format': 'csv',
|
||||
'stepId': 'csv-hidden'
|
||||
}),
|
||||
React.DOM.button({'onClick': MochiKit.Base.partial(this.props.goToStepCallback, 'csv-notes') }, "Back"),
|
||||
React.DOM.span({}, " - "),
|
||||
React.DOM.button({'onClick': MochiKit.Base.bind(function() {
|
||||
var importData = this.props.importState.importData;
|
||||
var json = this.props.csvToJsonCallback();
|
||||
this.props.setImportStateCallback({
|
||||
'importData': importData,
|
||||
'jsonToImport': json,
|
||||
'recordsToImport': MochiKit.Base.map(function(r){return r._importId},json),
|
||||
'currentStep': 'preview',
|
||||
'previousStep': 'csv-hidden'
|
||||
});
|
||||
}, this) }, "Preview"),
|
||||
React.DOM.p({}, "Select the fields that should be hidden. (passwords, PINs, ...)"),
|
||||
React.DOM.table({'style': {'background': 'white'}},[
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
this.props.csvRenderTheadInputCallback('hidden', 'checkbox', this.checkedCallback, this.onChangeCallback, this.disabledCallback, true)
|
||||
|
||||
React.DOM.tr({},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
|
||||
var result;
|
||||
|
||||
var thId = 'csv-notes-header-' + cellCount;
|
||||
var inputId = 'csv-notes-input-' + cellCount;
|
||||
|
||||
if (! importContext.selectedColumns[cellCount]) {
|
||||
result = null;
|
||||
} else {
|
||||
result = React.DOM.th({'key': thId}, [
|
||||
React.DOM.label({'htmlFor': inputId}, importContext.getCsvLabels()[cellCount]),
|
||||
React.DOM.input({
|
||||
'type': 'checkbox',
|
||||
'id': inputId,
|
||||
'key': inputId,
|
||||
'ref': inputId,
|
||||
'checked': this.state.hiddenColumns[cellCount],
|
||||
'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount),
|
||||
'disabled': (cellCount == importContext.titlesColumn || cellCount == importContext.notesColumn)
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
cellCount++;
|
||||
|
||||
return result;
|
||||
}, this), importContext.parsedCsv[0])
|
||||
)
|
||||
|
||||
),
|
||||
React.DOM.tbody({},
|
||||
this.props.csvRenderTbodyCallback()
|
||||
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(row){
|
||||
var result;
|
||||
|
||||
cellCount = 0;
|
||||
|
||||
if (rowCount == 0 && importContext.firstRowAsLabels) {
|
||||
result = null;
|
||||
} else {
|
||||
result = React.DOM.tr({'key': 'csv-row-' + (rowCount)}, MochiKit.Base.map( function(cell) {
|
||||
var result;
|
||||
|
||||
if (importContext.selectedColumns[cellCount]) {
|
||||
result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
|
||||
} else{
|
||||
result = null;
|
||||
}
|
||||
|
||||
cellCount++;
|
||||
|
||||
return result;
|
||||
}, row));
|
||||
}
|
||||
|
||||
rowCount++;
|
||||
|
||||
return result;
|
||||
},this), importContext.parsedCsv)
|
||||
|
||||
)
|
||||
|
||||
])
|
||||
|
||||
@@ -26,84 +26,153 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvLabelsClass = React.createClass({
|
||||
|
||||
toggleFirstRow: function() {
|
||||
var newState;
|
||||
var cellCount;
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'firstRowAsLabels': this.props.importContext.firstRowAsLabels,
|
||||
'columnLabels': this.props.importContext.columnLabels,
|
||||
'columnLabelsFirstrow': this.props.importContext.columnLabelsFirstrow
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.props.setNextStepCallback((this.isNextDisabled()) ? null : this.handleNextStep);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
newState = {'importData': this.props.importState.importData};
|
||||
newState.importData.firstRowAsLabels = ! newState.importData.firstRowAsLabels;
|
||||
|
||||
cellCount = 0;
|
||||
MochiKit.Base.map(function(cell){
|
||||
newState.importData.columnLabelsFirstrow[cellCount++] = cell;
|
||||
}, this.props.importState.importData.parsedCSV[0]);
|
||||
|
||||
this.props.setImportStateCallback(newState);
|
||||
handleNextStep: function() {
|
||||
return this.state;
|
||||
},
|
||||
|
||||
updateNextStatus: function() {
|
||||
this.props.setNextStepCallback((! this.isNextDisabled()) ? this.handleNextStep : null);
|
||||
},
|
||||
|
||||
isNextDisabled: function() {
|
||||
var result;
|
||||
|
||||
var importData = this.props.importState.importData;
|
||||
|
||||
var columnLabels = (importData.firstRowAsLabels) ? importData.columnLabelsFirstrow : importData.columnLabels;
|
||||
var importContext = this.props.importContext;
|
||||
var columnLabels = this.getLabels();
|
||||
|
||||
result = false;
|
||||
for (i in columnLabels) {
|
||||
result = result || (columnLabels[i] == '');
|
||||
result = result || ((columnLabels[i] == '')&&(importContext.selectedColumns[i]));
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
valueCallback: function(columnN) {
|
||||
var columnLabels = this.props.csvGetColumnLabelsCallback();
|
||||
return columnLabels[columnN];
|
||||
//=========================================================================
|
||||
|
||||
getLabels: function() {
|
||||
return (this.state.firstRowAsLabels) ? this.state.columnLabelsFirstrow : this.state.columnLabels;
|
||||
},
|
||||
|
||||
toggleFirstRow: function() {
|
||||
var newState;
|
||||
var cellCount;
|
||||
|
||||
newState = this.state;
|
||||
newState.firstRowAsLabels = ! newState.firstRowAsLabels;
|
||||
|
||||
cellCount = 0;
|
||||
MochiKit.Base.map(function(cell){
|
||||
newState.columnLabelsFirstrow[cellCount++] = cell;
|
||||
}, this.props.importContext.parsedCsv[0]);
|
||||
|
||||
this.updateNextStatus();
|
||||
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
onChangeCallback: function(columnN) {
|
||||
var newState;
|
||||
|
||||
newState = {'importData': this.props.importState.importData};
|
||||
if (this.props.importState.importData.firstRowAsLabels) {
|
||||
newState.importData.columnLabelsFirstrow[columnN] = this.refs['csv-labels-input-'+columnN].getDOMNode().value;
|
||||
newState = this.state;
|
||||
if (newState.firstRowAsLabels) {
|
||||
newState.columnLabelsFirstrow[columnN] = this.refs['csv-labels-input-' + columnN].getDOMNode().value;
|
||||
} else {
|
||||
newState.importData.columnLabels[columnN] = this.refs['csv-labels-input-'+columnN].getDOMNode().value;
|
||||
newState.columnLabels[columnN] = this.refs['csv-labels-input-' + columnN].getDOMNode().value;
|
||||
}
|
||||
|
||||
this.props.setImportStateCallback(newState);
|
||||
this.updateNextStatus();
|
||||
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
//console.log("labels-render",this.props.importContext);
|
||||
//return React.DOM.p({}, "labels")
|
||||
var rowCount, cellCount;
|
||||
|
||||
var importContext = this.props.importContext;
|
||||
var columnLabels = this.getLabels();
|
||||
|
||||
var importData = this.props.importState.importData;
|
||||
|
||||
rowCount = 0;
|
||||
cellCount = 0;
|
||||
return React.DOM.div({},[
|
||||
React.DOM.h2({},"Labels"),
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigation({
|
||||
'format': 'csv',
|
||||
'stepId': 'csv-labels',
|
||||
'prevStep': 'csv-columns',
|
||||
'nextStep': 'csv-titles',
|
||||
'goToStepCallback': this.props.goToStepCallback,
|
||||
'nextDisabled': this.isNextDisabled()
|
||||
}),
|
||||
|
||||
React.DOM.p({}, "Set a label for each field in your data. If the first row of the CSV file contains field labels, tick off the checkbox below."),
|
||||
React.DOM.input({
|
||||
'id': 'csv-labels-firstrow',
|
||||
'type': 'checkbox',
|
||||
'checked': this.props.importState.importData.firstRowAsLabels,
|
||||
'checked': this.state.firstRowAsLabels,
|
||||
'onChange': this.toggleFirstRow
|
||||
}),
|
||||
React.DOM.label({'htmlFor':'csv-labels-firstrow'}, "Use the first row as labels"),
|
||||
React.DOM.table({'style': {'background': 'white'}},[
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
this.props.csvRenderTheadInputCallback('labels', 'text', this.valueCallback, this.onChangeCallback, null, false)
|
||||
React.DOM.tr({},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
|
||||
var result;
|
||||
|
||||
if (! importContext.selectedColumns[cellCount]) {
|
||||
result = null;
|
||||
} else {
|
||||
result = React.DOM.th({'key': 'csv-labels-header-' + cellCount}, [
|
||||
React.DOM.input({
|
||||
'type': 'text',
|
||||
'id': 'csv-labels-input-' + cellCount,
|
||||
'key': 'csv-labels-input-' + cellCount,
|
||||
'ref': 'csv-labels-input-' + cellCount,
|
||||
'value': columnLabels[cellCount],
|
||||
'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount)
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
cellCount++;
|
||||
|
||||
return result;
|
||||
}, this), this.props.importContext.parsedCsv[0])
|
||||
)
|
||||
),
|
||||
React.DOM.tbody({},
|
||||
this.props.csvRenderTbodyCallback()
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(row){
|
||||
var result;
|
||||
|
||||
cellCount = 0;
|
||||
|
||||
if (rowCount == 0 && this.state.firstRowAsLabels) {
|
||||
result = null;
|
||||
} else {
|
||||
result = React.DOM.tr({'key': 'csv-row-' + (rowCount)}, MochiKit.Base.map( function(cell) {
|
||||
var result;
|
||||
|
||||
if (importContext.selectedColumns[cellCount]) {
|
||||
result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
|
||||
} else{
|
||||
result = null;
|
||||
}
|
||||
|
||||
cellCount++;
|
||||
return result;
|
||||
}, row));
|
||||
}
|
||||
|
||||
rowCount++;
|
||||
|
||||
return result;
|
||||
},this), importContext.parsedCsv)
|
||||
)
|
||||
|
||||
])
|
||||
|
||||
@@ -26,50 +26,109 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvNotesClass = React.createClass({
|
||||
|
||||
checkedCallback: function(columnN) {
|
||||
return columnN == this.props.importState.importData.notesColumn;
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'notesColumn': this.props.importContext.notesColumn
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.props.setNextStepCallback(this.handleNextStep);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
handleNextStep: function() {
|
||||
return this.state;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
onChangeCallback: function(columnN) {
|
||||
var newState = {'importData': this.props.importState.importData};
|
||||
|
||||
newState.importData.notesColumn = columnN;
|
||||
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
disabledCallback: function(columnN) {
|
||||
return columnN == this.props.importState.importData.titlesColumn;
|
||||
this.setState({'notesColumn': columnN});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var cellCount, rowCount;
|
||||
|
||||
var importContext = this.props.importContext;
|
||||
|
||||
cellCount = 0;
|
||||
rowCount = 0;
|
||||
return React.DOM.div({},[
|
||||
React.DOM.h2({},"Notes"),
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigation({
|
||||
'format': 'csv',
|
||||
'stepId': 'csv-notes',
|
||||
'prevStep': 'csv-titles',
|
||||
'nextStep': 'csv-hidden',
|
||||
'goToStepCallback': this.props.goToStepCallback
|
||||
}),
|
||||
|
||||
React.DOM.p({}, "Select the column that represents a \"notes\" field. (optional)"),
|
||||
React.DOM.input({
|
||||
'id': 'csv-notes-nonotes',
|
||||
'type': 'radio',
|
||||
'checked': ! this.props.importState.importData.notesColumn,
|
||||
'checked': ! this.state.notesColumn,
|
||||
'onChange': MochiKit.Base.partial(this.onChangeCallback, null)
|
||||
}),
|
||||
React.DOM.label({'htmlFor': 'csv-notes-nonotes'}, "\"notes\" field not present"),
|
||||
React.DOM.table({'style': {'background': 'white'}},[
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
this.props.csvRenderTheadInputCallback('notes', 'radio', this.checkedCallback, this.onChangeCallback, this.disabledCallback, true)
|
||||
|
||||
React.DOM.tr({},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
|
||||
var result;
|
||||
|
||||
var thId = 'csv-notes-header-' + cellCount;
|
||||
var inputId = 'csv-notes-input-' + cellCount;
|
||||
|
||||
if (! importContext.selectedColumns[cellCount]) {
|
||||
result = null;
|
||||
} else {
|
||||
result = React.DOM.th({'key': thId}, [
|
||||
React.DOM.label({'htmlFor': inputId}, importContext.getCsvLabels()[cellCount]),
|
||||
React.DOM.input({
|
||||
'type': 'radio',
|
||||
'id': inputId,
|
||||
'key': inputId,
|
||||
'ref': inputId,
|
||||
'checked': cellCount == this.state.notesColumn,
|
||||
'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount),
|
||||
'disabled': cellCount == importContext.titlesColumn
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
cellCount++;
|
||||
|
||||
return result;
|
||||
}, this), importContext.parsedCsv[0])
|
||||
)
|
||||
|
||||
),
|
||||
React.DOM.tbody({},
|
||||
this.props.csvRenderTbodyCallback()
|
||||
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(row){
|
||||
var result;
|
||||
|
||||
cellCount = 0;
|
||||
|
||||
if (rowCount == 0 && importContext.firstRowAsLabels) {
|
||||
result = null;
|
||||
} else {
|
||||
result = React.DOM.tr({'key': 'csv-row-' + (rowCount)}, MochiKit.Base.map( function(cell) {
|
||||
var result;
|
||||
|
||||
if (importContext.selectedColumns[cellCount]) {
|
||||
result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
|
||||
} else{
|
||||
result = null;
|
||||
}
|
||||
|
||||
cellCount++;
|
||||
|
||||
return result;
|
||||
}, row));
|
||||
}
|
||||
|
||||
rowCount++;
|
||||
|
||||
return result;
|
||||
},this), importContext.parsedCsv)
|
||||
)
|
||||
|
||||
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -26,45 +26,115 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvTitlesClass = React.createClass({
|
||||
|
||||
checkedCallback: function(columnN) {
|
||||
return columnN == this.props.importState.importData.titlesColumn;
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'titlesColumn': this.props.importContext.titlesColumn,
|
||||
'notesColumn': this.props.importContext.notesColumn
|
||||
};
|
||||
},
|
||||
|
||||
onChangeCallback: function(columnN) {
|
||||
var newState = {'importData': this.props.importState.importData};
|
||||
componentDidMount() {
|
||||
this.props.setNextStepCallback((this.isNextDisabled()) ? null : this.handleNextStep);
|
||||
},
|
||||
|
||||
if (this.props.importState.importData.notesColumn == columnN) {
|
||||
newState.importData.notesColumn = null;
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
handleNextStep: function() {
|
||||
return this.state;
|
||||
},
|
||||
|
||||
updateNextStatus: function() {
|
||||
this.props.setNextStepCallback((! this.isNextDisabled()) ? this.handleNextStep : null);
|
||||
},
|
||||
|
||||
isNextDisabled: function() {
|
||||
return (this.state.titlesColumn != 0 && ! this.state.titlesColumn );
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
onChangeCallback: function(columnN) {
|
||||
var newState = this.state;
|
||||
|
||||
if (newState.notesColumn == columnN) {
|
||||
newState.notesColumn = null;
|
||||
}
|
||||
newState.importData.titlesColumn = columnN;
|
||||
newState.titlesColumn = columnN;
|
||||
|
||||
this.props.setImportStateCallback(newState);
|
||||
this.updateNextStatus();
|
||||
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var rowCount, cellCount;
|
||||
|
||||
var importData = this.props.importState.importData;
|
||||
|
||||
var importContext = this.props.importContext;
|
||||
var columnLabels = importContext.getCsvLabels();
|
||||
|
||||
rowCount = 0;
|
||||
cellCount = 0;
|
||||
return React.DOM.div({},[
|
||||
React.DOM.h2({},"Titles"),
|
||||
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigation({
|
||||
'format': 'csv',
|
||||
'stepId': 'csv-titles',
|
||||
'prevStep': 'csv-labels',
|
||||
'nextStep': 'csv-notes',
|
||||
'goToStepCallback': this.props.goToStepCallback,
|
||||
'nextDisabled': (importData.titlesColumn != 0 && ! importData.titlesColumn )
|
||||
}),
|
||||
|
||||
React.DOM.p({}, "Select the column that contains titles of the cards you are importing. (mandatory)"),
|
||||
React.DOM.table({'style': {'background': 'white'}},[
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
this.props.csvRenderTheadInputCallback('titles', 'radio', this.checkedCallback, this.onChangeCallback, null, true)
|
||||
React.DOM.tr({},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
|
||||
var result;
|
||||
|
||||
var thId = 'csv-titles-header-' + cellCount;
|
||||
var inputId = 'csv-titles-input-' + cellCount;
|
||||
|
||||
if (! importContext.selectedColumns[cellCount]) {
|
||||
result = null;
|
||||
} else {
|
||||
result = React.DOM.th({'key': thId}, [
|
||||
React.DOM.label({'htmlFor': inputId}, columnLabels[cellCount]),
|
||||
React.DOM.input({
|
||||
'type': 'radio',
|
||||
'id': inputId,
|
||||
'key': inputId,
|
||||
'ref': inputId,
|
||||
'checked': cellCount == this.state.titlesColumn,
|
||||
'onChange': MochiKit.Base.partial(this.onChangeCallback,cellCount)
|
||||
})
|
||||
]);
|
||||
}
|
||||
|
||||
cellCount++;
|
||||
|
||||
return result;
|
||||
}, this), this.props.importContext.parsedCsv[0])
|
||||
)
|
||||
),
|
||||
React.DOM.tbody({},
|
||||
this.props.csvRenderTbodyCallback()
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(row){
|
||||
var result;
|
||||
|
||||
cellCount = 0;
|
||||
|
||||
if (rowCount == 0 && importContext.firstRowAsLabels) {
|
||||
result = null;
|
||||
} else {
|
||||
result = React.DOM.tr({'key': 'csv-row-'+(rowCount)}, MochiKit.Base.map( function(cell) {
|
||||
var result;
|
||||
|
||||
if (importContext.selectedColumns[cellCount]) {
|
||||
result = React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount)},cell);
|
||||
} else{
|
||||
result = null;
|
||||
}
|
||||
|
||||
cellCount++;
|
||||
|
||||
return result;
|
||||
}, row));
|
||||
}
|
||||
|
||||
rowCount++;
|
||||
|
||||
return result;
|
||||
},this), importContext.parsedCsv)
|
||||
)
|
||||
|
||||
])
|
||||
|
||||
@@ -25,6 +25,129 @@ refer to http://www.clipperz.com.
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'inputString': (this.props.importContext.inputString) ? this.props.importContext.inputString : null,
|
||||
'format': (this.props.importContext.format) ? this.props.importContext.format : null,
|
||||
//'parsedInput': (this.props.importContext.parsedInput) ? this.props.importContext.parsedInput : null,
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.updateNextStatus(this.state.inputString);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
handleNextStep: function() {
|
||||
var result;
|
||||
var jsonData;
|
||||
var parsedInput;
|
||||
|
||||
var inputString = this.refs['input-textarea'].getDOMNode().value.trim();
|
||||
|
||||
result = {'inputString': inputString};
|
||||
|
||||
parsedInput = this.parseJson(inputString);
|
||||
if (parsedInput) {
|
||||
MochiKit.Base.update(result,this.props.importContext.getInitialJsonContext(parsedInput));
|
||||
} else {
|
||||
parsedInput = this.parseCsv(inputString);
|
||||
if (parsedInput) {
|
||||
MochiKit.Base.update(result, this.props.importContext.getInitialCsvContext(parsedInput));
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
updateNextStatus: function(newInputString) {
|
||||
this.props.setNextStepCallback((newInputString) ? this.handleNextStep : null);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
extractJsonFromClipperzExport: function(someHtml) {
|
||||
var textarea;
|
||||
var regexMatch;
|
||||
var result;
|
||||
|
||||
var re = new RegExp('.*<textarea>(.*)<\/textarea>.*','g');
|
||||
|
||||
if (re.test(someHtml)) {
|
||||
textarea = this.refs['input-textarea'].getDOMNode();
|
||||
textarea.innerHTML = someHtml.replace(re, '$1');
|
||||
result = textarea.innerHTML;
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
addImportIds: function (someJson) {
|
||||
var count;
|
||||
|
||||
for (count=0; count < someJson.length; count++) {
|
||||
someJson[count]['_importId'] = count;
|
||||
}
|
||||
},
|
||||
|
||||
parseJson: function(aJsonString) {
|
||||
var result;
|
||||
var jsonData;
|
||||
|
||||
try {
|
||||
jsonData = JSON.parse(aJsonString);
|
||||
this.addImportIds(jsonData);
|
||||
result = jsonData;
|
||||
} catch(e) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
parseCsv: function(aCsvString) {
|
||||
var result;
|
||||
var i;
|
||||
|
||||
var parsedCsv = Papa.parse(aCsvString);
|
||||
|
||||
if (parsedCsv.errors.length != 0) {
|
||||
result = false;
|
||||
} else {
|
||||
result = this.csvFillEmptyCells(parsedCsv.data);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
csvFillEmptyCells: function(table) {
|
||||
var i,j;
|
||||
|
||||
var result = [];
|
||||
|
||||
var maxColumns = MochiKit.Iter.reduce(function(prev,next) {
|
||||
return Math.max(prev,next)
|
||||
}, MochiKit.Base.map(function(row) {return row.length;}, table) );
|
||||
|
||||
for (i=0; i<table.length; i++) {
|
||||
|
||||
result[i] = [];
|
||||
for (j=0; j<maxColumns; j++) {
|
||||
result[i][j] = (typeof(table[i][j]) != "undefined") ? table[i][j] : "";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
handleUploadFiles: function(someFiles) {
|
||||
var file;
|
||||
var reader;
|
||||
@@ -33,19 +156,24 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
|
||||
file = someFiles[0];
|
||||
reader = new FileReader();
|
||||
|
||||
// TODO: check what happens with binary files
|
||||
// Binary files are just thrown in the textarea as weird UTF-8 characters: should we do something about it?
|
||||
reader.onloadend = MochiKit.Base.bind(function() {
|
||||
var extractedJson = this.props.extractJsonFromClipperzExportCallback(reader.result);
|
||||
var extractedJson = this.extractJsonFromClipperzExport(reader.result);
|
||||
var newInputString;
|
||||
|
||||
if (extractedJson) {
|
||||
this.props.setImportStateCallback({'importData': {'input': extractedJson}});
|
||||
newInputString = extractedJson;
|
||||
} else {
|
||||
this.props.setImportStateCallback({'importData': {'input': reader.result}});
|
||||
newInputString = reader.result;
|
||||
}
|
||||
|
||||
this.setState({'inputString': newInputString});
|
||||
this.updateNextStatus(newInputString);
|
||||
},this,reader);
|
||||
|
||||
reader.readAsText(file);
|
||||
} else {
|
||||
// Should this be removed?
|
||||
alert("Error: expecting a file as input.");
|
||||
}
|
||||
},
|
||||
@@ -70,36 +198,17 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
|
||||
e.preventDefault();
|
||||
},
|
||||
|
||||
handleSubmit: function(event) {
|
||||
var jsonData;
|
||||
var newState;
|
||||
|
||||
var inputString = this.refs['input-textarea'].getDOMNode().value.trim();
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (newState = this.props.parseJsonCallback(inputString)) {
|
||||
this.props.setImportStateCallback(newState);
|
||||
} else if (newState = this.props.parseCsvCallback(inputString)) {
|
||||
this.props.setImportStateCallback(newState);
|
||||
} else {
|
||||
alert("Unrecognized input format...");
|
||||
}
|
||||
},
|
||||
|
||||
handleTextareaChange: function() {
|
||||
this.props.setImportStateCallback({'importData': {'input': this.refs['input-textarea'].getDOMNode().value}});
|
||||
var newInputString = this.refs['input-textarea'].getDOMNode().value;
|
||||
this.setState({'inputString': newInputString});
|
||||
this.updateNextStatus(newInputString);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div({},[
|
||||
React.DOM.h2({},"Input"),
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigation({
|
||||
'format': 'json',
|
||||
'stepId': 'input'
|
||||
}),
|
||||
React.DOM.form({'key':'form', 'className':'importForm', 'onSubmit': this.handleSubmit }, [
|
||||
React.DOM.button({'key':'input-next', 'type':'submit', 'className':'button'}, "Next"),
|
||||
React.DOM.form({'key':'form', 'className':'importForm' }, [
|
||||
React.DOM.input({
|
||||
'type': 'file',
|
||||
'ref': 'upload-input',
|
||||
@@ -108,15 +217,10 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
|
||||
'style': {'display': 'none'}
|
||||
}),
|
||||
React.DOM.div({
|
||||
'style': { // TODO: replace with proper CSS
|
||||
'width': '90%',
|
||||
'textAlign': 'center',
|
||||
'lineHeight': '3em',
|
||||
'border': '3px dashed white'
|
||||
},
|
||||
'onDragOver': this.handleOnDragOver,
|
||||
'onDrop': this.handleOnDrop,
|
||||
'onClick': MochiKit.Base.bind(function() { this.refs['upload-input'].getDOMNode().click() }, this)
|
||||
'onClick': MochiKit.Base.bind(function() { this.refs['upload-input'].getDOMNode().click() }, this),
|
||||
'className': 'dropArea'
|
||||
}, "Drag your Clipperz export file here or click select it manually."),
|
||||
React.DOM.p({}, "or"),
|
||||
React.DOM.div({'key':'fields'},[
|
||||
@@ -125,7 +229,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
|
||||
'name':'input-textarea',
|
||||
'ref':'input-textarea',
|
||||
'placeholder':"Open the JSON file exported from Clipperz in a text editor. Then copy and paste its content here.",
|
||||
'value': this.props.importState.importData.input,
|
||||
'value': this.state.inputString,
|
||||
'onChange': this.handleTextareaChange,
|
||||
'onDragOver': this.handleOnDragOver,
|
||||
'onDrop': this.handleOnDrop,
|
||||
|
||||
@@ -26,28 +26,81 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createClass({
|
||||
|
||||
// UNCOMMENT AFTER MERGE (uses methods in Record that were added in another branch)
|
||||
// getTags: function (aTitle) {
|
||||
// var result;
|
||||
// var tagList;
|
||||
//
|
||||
// var tagObject = Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aTitle);
|
||||
//
|
||||
// tagList = MochiKit.Base.keys(tagObject);
|
||||
// tagList = MochiKit.Base.filter(function(aTag) { return tagObject[aTag] }, tagList);
|
||||
//
|
||||
// if (tagList.length > 0) {
|
||||
// result = React.DOM.ul({'className': 'tagList'},
|
||||
// MochiKit.Base.map(function(aTag){
|
||||
// return React.DOM.li({}, aTag);
|
||||
// }, tagList)
|
||||
// );
|
||||
// } else {
|
||||
// result = null;
|
||||
// }
|
||||
//
|
||||
// return result;
|
||||
// },
|
||||
getInitialState: function() {
|
||||
if (this.props.importContext.format == 'csv') {
|
||||
return this.props.importContext.processCsv()
|
||||
} else {
|
||||
return {
|
||||
'jsonToImport': this.props.importContext.jsonToImport,
|
||||
'recordsToImport': this.props.importContext.recordsToImport,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.props.setNextStepCallback(this.handleImport);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
handleImport: function() {
|
||||
MochiKit.Base.update(this.props.importContext, this.state);
|
||||
|
||||
var filteredImportData = MochiKit.Base.filter(
|
||||
MochiKit.Base.bind(function(r) {
|
||||
return this.isRecordToImport(r);
|
||||
}, this),
|
||||
this.state.jsonToImport
|
||||
);
|
||||
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'importCards', filteredImportData);
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
toggleRecordToImport: function(record) {
|
||||
var newRecordsToImport;
|
||||
var recordPosition;
|
||||
|
||||
newRecordsToImport = this.state.recordsToImport;
|
||||
recordPosition = newRecordsToImport.indexOf(record._importId);
|
||||
|
||||
if (recordPosition === -1) {
|
||||
newRecordsToImport.push(record._importId);
|
||||
} else {
|
||||
newRecordsToImport.splice(recordPosition,1);
|
||||
}
|
||||
|
||||
this.setState({'recordsToImport': newRecordsToImport});
|
||||
},
|
||||
|
||||
isRecordToImport: function(record) {
|
||||
return (this.state.recordsToImport.indexOf(record._importId)>=0) ? true : false;
|
||||
},
|
||||
|
||||
getTags: function (aTitle) {
|
||||
var result;
|
||||
var tagList;
|
||||
|
||||
var tagObject = Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aTitle);
|
||||
|
||||
tagList = MochiKit.Base.keys(tagObject);
|
||||
tagList = MochiKit.Base.filter(function(aTag) { return tagObject[aTag] }, tagList);
|
||||
|
||||
if (tagList.length > 0) {
|
||||
result = React.DOM.ul({'className': 'tagList'},
|
||||
MochiKit.Base.map(function(aTag){
|
||||
return React.DOM.li({}, aTag);
|
||||
}, tagList)
|
||||
);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
renderCardFields: function(someFields) {
|
||||
return MochiKit.Base.map(function(key) {
|
||||
@@ -65,13 +118,11 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createCl
|
||||
return React.DOM.li({'className': 'card'}, [
|
||||
React.DOM.input({
|
||||
'type': 'checkbox',
|
||||
'checked': this.props.isRecordToImportCallback(aCard),
|
||||
'onChange': MochiKit.Base.partial(this.props.toggleRecordToImportCallback,aCard)
|
||||
'checked': this.isRecordToImport(aCard),
|
||||
'onChange': MochiKit.Base.partial(this.toggleRecordToImport,aCard)
|
||||
}),
|
||||
React.DOM.h3({}, Clipperz.PM.DataModel.Record.filterOutTags(aCard.label)),
|
||||
// REMOVE THE PREVIOUS LINE AND UNCOMMENT THE FOLLOWING 2 AFTER MERGE
|
||||
// React.DOM.h3({}, Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aCard.label)),
|
||||
// this.getTags(aCard.label),
|
||||
React.DOM.h3({}, Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aCard.label)),
|
||||
this.getTags(aCard.label),
|
||||
React.DOM.dl({'className': 'fields'}, this.renderCardFields(aCard.currentVersion.fields)),
|
||||
notesParagraph
|
||||
]);
|
||||
@@ -80,38 +131,17 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createCl
|
||||
render: function() {
|
||||
var result;
|
||||
|
||||
if (! this.props.importState.importData || typeof(this.props.importState.jsonToImport)=='undefined' || !this.props.importState.jsonToImport) {
|
||||
if (typeof(this.state.jsonToImport)=='undefined' || !this.state.jsonToImport) {
|
||||
result = "Error";
|
||||
} else {
|
||||
var renderedPreview = React.DOM.ul({},
|
||||
MochiKit.Base.map(this.renderCard, this.props.importState.jsonToImport)
|
||||
MochiKit.Base.map(this.renderCard, this.state.jsonToImport)
|
||||
);
|
||||
|
||||
result = [
|
||||
React.DOM.h2({},"Preview"),
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigation({
|
||||
'format': this.props.importState.importData.format,
|
||||
'stepId': 'preview'
|
||||
}),
|
||||
React.DOM.button({
|
||||
'onClick': MochiKit.Base.partial(this.props.goToStepCallback, this.props.importState.previousStep)}, "Back"),
|
||||
React.DOM.span({}, " - "),
|
||||
React.DOM.button({
|
||||
'onClick': MochiKit.Base.bind(function() {
|
||||
var filteredImportData = MochiKit.Base.filter(
|
||||
MochiKit.Base.bind(function(r) {
|
||||
return this.props.isRecordToImportCallback(r);
|
||||
}, this),
|
||||
this.props.importState.jsonToImport
|
||||
);
|
||||
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'importCards', filteredImportData);
|
||||
|
||||
this.props.resetImportStateCallback();
|
||||
}, this)
|
||||
}, "Import"),
|
||||
React.DOM.div({'className': 'jsonPreview'},renderedPreview),
|
||||
];
|
||||
result =
|
||||
React.DOM.div({'className': 'jsonPreview'}, React.DOM.ul({},
|
||||
MochiKit.Base.map(this.renderCard, this.state.jsonToImport)
|
||||
) );
|
||||
}
|
||||
|
||||
return React.DOM.div({},result);
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigationClass = React.createClass({
|
||||
|
||||
_stepsInfo: [
|
||||
{
|
||||
id: 'input',
|
||||
name: 'Input',
|
||||
formats: ['json', 'csv']
|
||||
},
|
||||
{
|
||||
id: 'csv-columns',
|
||||
name: 'Columns',
|
||||
formats: ['csv']
|
||||
},
|
||||
{
|
||||
id: 'csv-labels',
|
||||
name: 'Labels',
|
||||
formats: ['csv']
|
||||
},
|
||||
{
|
||||
id: 'csv-titles',
|
||||
name: 'Titles',
|
||||
formats: ['csv']
|
||||
},
|
||||
{
|
||||
id: 'csv-notes',
|
||||
name: 'Notes',
|
||||
formats: ['csv']
|
||||
},
|
||||
{
|
||||
id: 'csv-hidden',
|
||||
name: 'Hidden',
|
||||
formats: ['csv']
|
||||
},
|
||||
{
|
||||
id: 'preview',
|
||||
name: 'Preview',
|
||||
formats: ['json', 'csv']
|
||||
}
|
||||
],
|
||||
|
||||
render: function() {
|
||||
var navigationButtons;
|
||||
|
||||
if (this.props.prevStep && this.props.nextStep) {
|
||||
navigationButtons = [
|
||||
React.DOM.button({'onClick': MochiKit.Base.partial(this.props.goToStepCallback, this.props.prevStep)}, "Back"),
|
||||
React.DOM.span({}, " - "),
|
||||
React.DOM.button({'onClick': MochiKit.Base.partial(this.props.goToStepCallback, this.props.nextStep), 'disabled': this.props.nextDisabled }, "Next")
|
||||
];
|
||||
} else {
|
||||
|
||||
}
|
||||
|
||||
return React.DOM.div({},[
|
||||
React.DOM.ul({'className': 'stepsOverview'},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(aStep) {
|
||||
var className;
|
||||
|
||||
className = (aStep.id == this.props.stepId) ? 'active' : 'inactive';
|
||||
className = (MochiKit.Base.findValue(aStep.formats,this.props.format)>= 0) ? className+' enabled' : className+' disabled';
|
||||
|
||||
// TODO: replace with proper CSS
|
||||
var style = (aStep.id == this.props.stepId) ? {'display': 'inline-block', 'textDecoration': 'underline'} : {'display': 'inline-block'};
|
||||
return React.DOM.li({'className': className, 'style': style}, aStep.name);
|
||||
},this), this._stepsInfo)
|
||||
),
|
||||
navigationButtons
|
||||
]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigation = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigationClass);
|
||||
@@ -43,7 +43,6 @@ Clipperz.PM.UI.Components.ExtraFeatures.DeleteAccountClass = React.createClass({
|
||||
|
||||
handleDeleteAccount: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'deleteAccount');
|
||||
},
|
||||
|
||||
@@ -77,14 +76,13 @@ Clipperz.PM.UI.Components.ExtraFeatures.DeleteAccountClass = React.createClass({
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature deleteAccount'}, [
|
||||
React.DOM.h1({}, "Delete Account"),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.form({'key':'form', 'className':'deleteAccountForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleDeleteAccount}, [
|
||||
React.DOM.div({'key':'fields'},[
|
||||
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
|
||||
React.DOM.input({'key':'username', 'className': this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
|
||||
React.DOM.span({'className': 'invalidMsg'},'Invalid username!'),
|
||||
React.DOM.label({'key':'passphrase-label', 'autoFocus': 'true', 'htmlFor' :'passphrase'}, "passphrase"),
|
||||
React.DOM.input({'key':'passphrase', 'className': this.state['passphrase'], 'type':'password', 'name':'passphrase', 'ref':'passphrase', 'placeholder':"passphrase"}),
|
||||
React.DOM.span({'className': 'invalidMsg'},'Invalid passphrase!'),
|
||||
React.DOM.p({}, [
|
||||
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
|
||||
React.DOM.span({}, "I understand that all my data will be deleted and that this action is irreversible.")
|
||||
@@ -92,6 +90,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DeleteAccountClass = React.createClass({
|
||||
]),
|
||||
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableDeleteAccountButton(), 'className':'button'}, "Delete my account")
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@@ -36,7 +36,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.DevicePINClass = React.createClass({
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature devicePIN'}, [
|
||||
React.DOM.h1({}, "Device PIN"),
|
||||
React.DOM.h3({}, this.props['PIN'])
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.h3({}, this.props['PIN'])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@@ -1,409 +0,0 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImportClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'currentStep': 'input',
|
||||
'importData': {'input': ""},
|
||||
'recordsToImport': null
|
||||
};
|
||||
},
|
||||
|
||||
goToStep: function(aStep) {
|
||||
this.setState({'currentStep': aStep});
|
||||
},
|
||||
|
||||
resetState: function() {
|
||||
this.replaceState( this.getInitialState() );
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
addImportIds: function (someJson) {
|
||||
var count;
|
||||
|
||||
for (count=0; count < someJson.length; count++) {
|
||||
someJson[count]['_importId'] = count;
|
||||
}
|
||||
},
|
||||
|
||||
toggleRecordToImport: function(record) {
|
||||
var newRecordsToImport;
|
||||
var recordPosition;
|
||||
|
||||
newRecordsToImport = this.state.recordsToImport;
|
||||
recordPosition = newRecordsToImport.indexOf(record._importId);
|
||||
|
||||
if (recordPosition === -1) {
|
||||
newRecordsToImport.push(record._importId);
|
||||
} else {
|
||||
newRecordsToImport.splice(recordPosition,1);
|
||||
}
|
||||
|
||||
this.setState({'recordsToImport': newRecordsToImport});
|
||||
},
|
||||
|
||||
isRecordToImport: function(record) {
|
||||
return (this.state.recordsToImport.indexOf(record._importId)>=0) ? true : false;
|
||||
},
|
||||
|
||||
extractJsonFromClipperzExport: function(someHtml) {
|
||||
var temporaryTextarea;
|
||||
var regexMatch;
|
||||
var result;
|
||||
|
||||
// Should move the regex to global?
|
||||
var re = new RegExp('.*<textarea>(.*)<\/textarea>.*','g');
|
||||
|
||||
if (re.test(someHtml)) {
|
||||
// Needed to escape HTML entities
|
||||
temporaryTextarea = document.createElement('textarea');
|
||||
temporaryTextarea.innerHTML = someHtml.replace(re, '$1');
|
||||
result = temporaryTextarea.innerHTML;
|
||||
} else {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
parseJson: function(aJsonString) {
|
||||
var result;
|
||||
var jsonData;
|
||||
|
||||
try {
|
||||
jsonData = JSON.parse(aJsonString);
|
||||
this.addImportIds(jsonData);
|
||||
result = {
|
||||
'importData': {
|
||||
'format': 'json',
|
||||
'input': aJsonString,
|
||||
},
|
||||
'jsonToImport': jsonData,
|
||||
'recordsToImport': jsonData.map(function(d){return d._importId}),
|
||||
'currentStep': 'preview',
|
||||
'previousStep': 'input'
|
||||
};
|
||||
} catch(e) {
|
||||
result = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
parseCsv: function(aCsvString) {
|
||||
var result;
|
||||
var parsedCSV;
|
||||
var nColumns;
|
||||
var defaultSelectedColumns;
|
||||
var defaultHiddenColumns;
|
||||
var defaultColumnLabels;
|
||||
var columnLabelsFirstrow;
|
||||
var i;
|
||||
|
||||
var papaParsedCSV = Papa.parse(aCsvString);
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (papaParsedCSV.errors.length != 0) {
|
||||
result = false;
|
||||
} else {
|
||||
parsedCSV = this.csvFillEmptyCells(papaParsedCSV.data);
|
||||
nColumns = parsedCSV[0].length;
|
||||
|
||||
defaultSelectedColumns = {};
|
||||
defaultHiddenColumns = {};
|
||||
defaultColumnLabels = {};
|
||||
columnLabelsFirstrow = {};
|
||||
for (i=0; i<nColumns; i++) {
|
||||
defaultSelectedColumns[i] = true;
|
||||
defaultHiddenColumns[i] = false;
|
||||
defaultColumnLabels[i] = "";
|
||||
columnLabelsFirstrow[i] = parsedCSV[0][i];
|
||||
}
|
||||
|
||||
result = {
|
||||
'importData': {
|
||||
'format': 'csv',
|
||||
'input': aCsvString,
|
||||
'parsedCSV': parsedCSV,
|
||||
'nColumns': nColumns,
|
||||
'selectedColumns': defaultSelectedColumns,
|
||||
'firstRowAsLabels': false,
|
||||
'columnLabels': defaultColumnLabels,
|
||||
'columnLabelsFirstrow': columnLabelsFirstrow,
|
||||
'titlesColumn': null,
|
||||
'notesColumn': null,
|
||||
'hiddenColumns': defaultHiddenColumns,
|
||||
'json': []
|
||||
},
|
||||
'currentStep': 'csv-columns'
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
csvFillEmptyCells: function(table) {
|
||||
var i,j;
|
||||
|
||||
var result = [];
|
||||
|
||||
var maxColumns = MochiKit.Iter.reduce(function(prev,next) {
|
||||
return Math.max(prev,next)
|
||||
}, MochiKit.Base.map(function(row) {return row.length;}, table) );
|
||||
|
||||
for (i=0; i<table.length; i++) {
|
||||
|
||||
result[i] = [];
|
||||
for (j=0; j<maxColumns; j++) {
|
||||
result[i][j] = (typeof(table[i][j]) != "undefined") ? table[i][j] : "";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
csvGetColumnLabels: function() {
|
||||
return (this.state.importData.firstRowAsLabels) ? this.state.importData.columnLabelsFirstrow : this.state.importData.columnLabels;
|
||||
},
|
||||
|
||||
csvToJson: function() {
|
||||
var result;
|
||||
|
||||
var importData = this.state.importData;
|
||||
var columnLabels = this.csvGetColumnLabels();
|
||||
|
||||
result = [];
|
||||
|
||||
for (rowCount=0; rowCount<importData.parsedCSV.length; rowCount++) {
|
||||
var rowCount,cellCount;
|
||||
|
||||
if (rowCount != 0 || ! importData.firstRowAsLabels) {
|
||||
var record;
|
||||
|
||||
record = {};
|
||||
record._importId = rowCount;
|
||||
record.label = importData.parsedCSV[rowCount][importData.titlesColumn];
|
||||
record.data = {'notes': ""};
|
||||
record.currentVersion = {'fields': {}};
|
||||
|
||||
for (cellCount=0; cellCount<importData.parsedCSV[rowCount].length; cellCount++) {
|
||||
if (importData.selectedColumns[cellCount] && cellCount != importData.notesColumn && cellCount != importData.titlesColumn) {
|
||||
var fieldKey = rowCount+"-"+cellCount;
|
||||
var field = {
|
||||
'label': columnLabels[cellCount],
|
||||
'value': importData.parsedCSV[rowCount][cellCount],
|
||||
'hidden': importData.hiddenColumns[cellCount]
|
||||
};
|
||||
record.currentVersion.fields[fieldKey] = field;
|
||||
} else if (cellCount == importData.notesColumn) {
|
||||
record.data.notes = importData.parsedCSV[rowCount][cellCount];
|
||||
}
|
||||
}
|
||||
|
||||
result.push(record);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
csvRenderTbody: function() {
|
||||
var rowCount;
|
||||
var cellCount;
|
||||
|
||||
var firstRowAsLabels = this.state.importData.firstRowAsLabels;
|
||||
var selectedColumns = this.state.importData.selectedColumns;
|
||||
|
||||
rowCount = 0;
|
||||
return MochiKit.Base.map(function(row){
|
||||
var result;
|
||||
|
||||
cellCount = 0;
|
||||
|
||||
if (rowCount == 0 && firstRowAsLabels) {
|
||||
result = null;
|
||||
} else {
|
||||
result = React.DOM.tr({'key': 'csv-row-'+(rowCount)}, MochiKit.Base.map(function(cell) {
|
||||
return (selectedColumns[cellCount]) ? React.DOM.td({'key': 'csv-cell-'+rowCount+'-'+(cellCount++)},cell) : null;
|
||||
}, row));
|
||||
}
|
||||
|
||||
rowCount++;
|
||||
|
||||
return result;
|
||||
}, this.state.importData.parsedCSV);
|
||||
},
|
||||
|
||||
csvRenderTheadInput: function(stepName, inputType, valueCallback, onChange, disabledCallback, showLabels) {
|
||||
var cellCount;
|
||||
|
||||
var importData = this.state.importData;
|
||||
|
||||
cellCount = 0;
|
||||
return React.DOM.tr({},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(cell) {
|
||||
var result;
|
||||
|
||||
var columnLabels = (importData.firstRowAsLabels) ? importData.columnLabelsFirstrow : importData.columnLabels;
|
||||
var inputLabel = (showLabels) ? React.DOM.label({'htmlFor': 'csv-'+stepName+'-input-'+cellCount}, columnLabels[cellCount]) : null;
|
||||
|
||||
if (! importData.selectedColumns[cellCount]) {
|
||||
result = null;
|
||||
} else {
|
||||
var inputProps = {
|
||||
'type': inputType,
|
||||
'id': 'csv-'+stepName+'-input-'+cellCount,
|
||||
'key': 'csv-'+stepName+'-input-'+cellCount,
|
||||
'ref': 'csv-'+stepName+'-input-'+cellCount,
|
||||
'onChange': MochiKit.Base.partial(onChange,cellCount)
|
||||
}
|
||||
|
||||
if (inputType == 'radio' || inputType == 'checkbox') {
|
||||
inputProps['checked'] = MochiKit.Base.partial(valueCallback,cellCount)();
|
||||
} else {
|
||||
inputProps['value'] = MochiKit.Base.partial(valueCallback,cellCount)();
|
||||
}
|
||||
|
||||
if (disabledCallback) {
|
||||
inputProps['disabled'] = MochiKit.Base.partial(disabledCallback,cellCount)();
|
||||
}
|
||||
|
||||
result = React.DOM.th({'key': 'csv-'+stepName+'-header-'+cellCount}, [
|
||||
inputLabel,
|
||||
React.DOM.input(inputProps)
|
||||
]);
|
||||
}
|
||||
|
||||
cellCount++;
|
||||
|
||||
return result;
|
||||
}, this), this.state.importData.parsedCSV[0])
|
||||
)
|
||||
},
|
||||
|
||||
setStateCB: function(aState) {
|
||||
this.setState(aState);
|
||||
},
|
||||
|
||||
_renderStepMethods: {
|
||||
'input': function() {
|
||||
return new Clipperz.PM.UI.Components.ExtraFeatures.DataImport.Input({
|
||||
'importState': this.state,
|
||||
'setImportStateCallback': this.setStateCB,
|
||||
'goToStepCallback': this.goToStep,
|
||||
'extractJsonFromClipperzExportCallback': this.extractJsonFromClipperzExport,
|
||||
'parseJsonCallback': this.parseJson,
|
||||
'parseCsvCallback': this.parseCsv
|
||||
});
|
||||
},
|
||||
|
||||
'csv-columns': function() {
|
||||
return new Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumns({
|
||||
'importState': this.state,
|
||||
'setImportStateCallback': this.setStateCB,
|
||||
'goToStepCallback': this.goToStep,
|
||||
});
|
||||
},
|
||||
|
||||
'csv-labels': function() {
|
||||
return new Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvLabels({
|
||||
'importState': this.state,
|
||||
'setImportStateCallback': this.setStateCB,
|
||||
'goToStepCallback': this.goToStep,
|
||||
'csvRenderTheadInputCallback': this.csvRenderTheadInput,
|
||||
'csvRenderTbodyCallback': this.csvRenderTbody,
|
||||
'csvGetColumnLabelsCallback': this.csvGetColumnLabels
|
||||
});
|
||||
},
|
||||
|
||||
'csv-titles': function() {
|
||||
return new Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvTitles({
|
||||
'importState': this.state,
|
||||
'setImportStateCallback': this.setStateCB,
|
||||
'goToStepCallback': this.goToStep,
|
||||
'csvRenderTheadInputCallback': this.csvRenderTheadInput,
|
||||
'csvRenderTbodyCallback': this.csvRenderTbody,
|
||||
});
|
||||
},
|
||||
|
||||
'csv-notes': function() {
|
||||
return new Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvNotes({
|
||||
'importState': this.state,
|
||||
'setImportStateCallback': this.setStateCB,
|
||||
'goToStepCallback': this.goToStep,
|
||||
'csvRenderTheadInputCallback': this.csvRenderTheadInput,
|
||||
'csvRenderTbodyCallback': this.csvRenderTbody,
|
||||
});
|
||||
},
|
||||
|
||||
'csv-hidden': function() {
|
||||
return new Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvHidden({
|
||||
'importState': this.state,
|
||||
'setImportStateCallback': this.setStateCB,
|
||||
'goToStepCallback': this.goToStep,
|
||||
'csvRenderTheadInputCallback': this.csvRenderTheadInput,
|
||||
'csvRenderTbodyCallback': this.csvRenderTbody,
|
||||
'csvToJsonCallback': this.csvToJson
|
||||
});
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
'preview': function() {
|
||||
return new Clipperz.PM.UI.Components.ExtraFeatures.DataImport.Preview({
|
||||
'importState': this.state,
|
||||
'resetImportStateCallback': this.resetState,
|
||||
'goToStepCallback': this.goToStep,
|
||||
'isRecordToImportCallback': this.isRecordToImport,
|
||||
'toggleRecordToImportCallback': this.toggleRecordToImport
|
||||
});
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
|
||||
renderStep: function(step) {
|
||||
return MochiKit.Base.method(this, this._renderStepMethods[step])();
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature'}, [
|
||||
React.DOM.h1({}, "Import"),
|
||||
this.renderStep(this.state.currentStep)
|
||||
]);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImportClass);
|
||||
@@ -27,96 +27,103 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures');
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.PassphraseClass = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
// featureSet: React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
|
||||
// 'level': React.PropTypes.oneOf(['hide', 'info', 'warning', 'error']).isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'username': '',
|
||||
'old-passphrase': '',
|
||||
'new-passphrase': '',
|
||||
'confirm-new-passphrase': '',
|
||||
'error': ''
|
||||
'username': 'empty',
|
||||
'old-passphrase': 'empty',
|
||||
'new-passphrase': 'empty',
|
||||
'confirm-new-passphrase': 'empty',
|
||||
'confirm': '',
|
||||
};
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
shouldEnableChangePassphraseButton: function() {
|
||||
return (
|
||||
this.state['username'] &&
|
||||
this.state['old-passphrase'] &&
|
||||
this.state['new-passphrase'] &&
|
||||
this.state['confirm-new-passphrase'] &&
|
||||
(this.state['new-passphrase'] == this.state['confirm-new-passphrase'])
|
||||
);
|
||||
},
|
||||
|
||||
handleFormChange: function() {
|
||||
this.setState({
|
||||
'username': this.refs['username'].getDOMNode().value,
|
||||
'old-passphrase': this.refs['old-passphrase'].getDOMNode().value,
|
||||
'new-passphrase': this.refs['new-passphrase'].getDOMNode().value,
|
||||
'confirm-new-passphrase': this.refs['confirm-new-passphrase'].getDOMNode().value
|
||||
});
|
||||
resetForm: function () {
|
||||
this.setState(this.getInitialState());
|
||||
|
||||
this.refs['username'].getDOMNode().value = '';
|
||||
this.refs['old-passphrase'].getDOMNode().value = '';
|
||||
this.refs['new-passphrase'].getDOMNode().value = '';
|
||||
this.refs['confirm-new-passphrase'].getDOMNode().value = '';
|
||||
this.refs['confirm'].getDOMNode().checked = false;
|
||||
},
|
||||
|
||||
handleChangePassphrase: function(event) {
|
||||
event.preventDefault();
|
||||
|
||||
if (this.refs['username'].getDOMNode().value != this.props.userInfo['username']) {
|
||||
this.setState({error: "Invalid username"});
|
||||
return;
|
||||
}
|
||||
var newPassphrase;
|
||||
|
||||
event.preventDefault();
|
||||
newPassphrase = this.refs['new-passphrase'].getDOMNode().value;
|
||||
this.resetForm();
|
||||
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'changePassphrase', newPassphrase);
|
||||
},
|
||||
|
||||
handleFormChange: function() {
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("Passphrase.handleChangePassphrase", {trace: false});
|
||||
deferredResult = new Clipperz.Async.Deferred("Passphrase.handleFormChange", {trace: false});
|
||||
deferredResult.addCallback(this.props.userInfo['checkPassphraseCallback'], this.refs['old-passphrase'].getDOMNode().value);
|
||||
deferredResult.addIf(
|
||||
[
|
||||
MochiKit.Base.partial(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'changePassphrase', this.refs['new-passphrase'].getDOMNode().value),
|
||||
MochiKit.Base.method(this, function() {
|
||||
this.refs['username'].getDOMNode().value = '';
|
||||
this.refs['old-passphrase'].getDOMNode().value = '';
|
||||
this.refs['new-passphrase'].getDOMNode().value = '';
|
||||
this.refs['confirm-new-passphrase'].getDOMNode().value = '';
|
||||
this.setState({'error': ''});
|
||||
})
|
||||
],
|
||||
[MochiKit.Base.bind(this.setState, this, {error: "Invalid password"})]
|
||||
);
|
||||
deferredResult.addMethod(this, function(passCheck){
|
||||
var username = this.refs['username'].getDOMNode().value;
|
||||
var oldPassphrase = this.refs['old-passphrase'].getDOMNode().value;
|
||||
var newPassphrase = this.refs['new-passphrase'].getDOMNode().value;
|
||||
var confirmNewPassphrase = this.refs['confirm-new-passphrase'].getDOMNode().value;
|
||||
|
||||
this.setState({
|
||||
'username': (username != '') ? [(username == this.props.userInfo['username']) ? 'valid' : 'invalid'] : 'empty',
|
||||
'old-passphrase': (oldPassphrase != '') ? [(passCheck) ? 'valid' : 'invalid'] : 'empty',
|
||||
'new-passphrase': (newPassphrase != '') ? 'valid' : 'empty',
|
||||
'confirm-new-passphrase': (confirmNewPassphrase != '') ? [(confirmNewPassphrase == newPassphrase) ? 'valid' : 'invalid'] : 'empty',
|
||||
'confirm': this.refs['confirm'].getDOMNode().checked,
|
||||
});
|
||||
});
|
||||
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
|
||||
// MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'changePassphrase', this.refs['new-passphrase'].getDOMNode().value);
|
||||
|
||||
},
|
||||
|
||||
shouldEnableChangePassphraseButton: function() {
|
||||
return (
|
||||
this.state['username'] == 'valid' &&
|
||||
this.state['old-passphrase'] == 'valid' &&
|
||||
this.state['new-passphrase'] == 'valid' &&
|
||||
this.state['confirm-new-passphrase'] == 'valid' &&
|
||||
this.state['confirm']
|
||||
);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
render: function () {
|
||||
var errorVisibility = (this.state.error) ? 'visible' : 'hidden';
|
||||
|
||||
return React.DOM.div({className:'extraFeature passphrase'}, [
|
||||
React.DOM.h1({}, "Change Passphrase"),
|
||||
React.DOM.form({'key':'form', 'className':'changePassphraseForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleChangePassphrase}, [
|
||||
React.DOM.div({'key':'fields'},[
|
||||
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
|
||||
React.DOM.input({'key':'username', 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
|
||||
React.DOM.label({'key':'old-passphrase-label', 'htmlFor' :'old-passphrase'}, "old passphrase"),
|
||||
React.DOM.input({'key':'old-passphrase', 'type':'password', 'name':'old-passphrase', 'ref':'old-passphrase', 'placeholder':"old passphrase"}),
|
||||
React.DOM.label({'key':'new-passphrase-label', 'autoFocus': 'true', 'htmlFor' :'new-passphrase'}, "new passphrase"),
|
||||
React.DOM.input({'key':'new-passphrase', 'type':'password', 'name':'new-passphrase', 'ref':'new-passphrase', 'placeholder':"new passphrase"}),
|
||||
React.DOM.label({'key':'confirm-new-passphrase-label', 'htmlFor' :'confirm-new-passphrase'}, "confirm new passphrase"),
|
||||
React.DOM.input({'key':'confirm-new-passphrase', 'type':'password', 'name':'confirm-new-passphrase', 'ref':'confirm-new-passphrase', 'placeholder':"confirm new passphrase"})
|
||||
]),
|
||||
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableChangePassphraseButton(), 'className':'button'}, "Change"),
|
||||
React.DOM.div({ref: 'errorMessage', className: 'errorMessage', style: {visibility: errorVisibility} }, this.state.error)
|
||||
]),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.form({'key':'form', 'className':'changePassphraseForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleChangePassphrase}, [
|
||||
React.DOM.div({'key':'fields'},[
|
||||
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
|
||||
React.DOM.input({'key':'username', 'className':this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
|
||||
|
||||
React.DOM.label({'key':'old-passphrase-label', 'htmlFor' :'old-passphrase'}, "old passphrase"),
|
||||
React.DOM.input({'key':'old-passphrase', 'className':this.state['old-passphrase'], 'type':'password', 'name':'old-passphrase', 'ref':'old-passphrase', 'placeholder':"old passphrase"}),
|
||||
|
||||
React.DOM.label({'key':'new-passphrase-label', 'autoFocus': 'true', 'htmlFor' :'new-passphrase'}, "new passphrase"),
|
||||
React.DOM.input({'key':'new-passphrase', 'className':this.state['new-passphrase'], 'type':'password', 'name':'new-passphrase', 'ref':'new-passphrase', 'placeholder':"new passphrase"}),
|
||||
|
||||
React.DOM.label({'key':'confirm-new-passphrase-label', 'htmlFor' :'confirm-new-passphrase'}, "confirm new passphrase"),
|
||||
React.DOM.input({'key':'confirm-new-passphrase', 'className':this.state['confirm-new-passphrase'], 'type':'password', 'name':'confirm-new-passphrase', 'ref':'confirm-new-passphrase', 'placeholder':"confirm new passphrase"}),
|
||||
|
||||
React.DOM.p({}, [
|
||||
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
|
||||
React.DOM.span({}, "I understand that Clipperz will not be able to recover a lost passphrase.")
|
||||
]),
|
||||
]),
|
||||
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableChangePassphraseButton(), 'className':'button'}, "Change passphrase"),
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@@ -53,11 +53,15 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
'show': function (aMessage, showMask) {
|
||||
'show': function (aMessage, showMask, showProgress) {
|
||||
if (showMask === true) {
|
||||
this.showMask();
|
||||
}
|
||||
|
||||
if (showProgress === true) {
|
||||
this.showProgressBar();
|
||||
}
|
||||
|
||||
this.resetStatus();
|
||||
this.setMessage(aMessage);
|
||||
MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-hide');
|
||||
@@ -66,6 +70,7 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
|
||||
|
||||
'done': function (aMessage, aDelayBeforeHiding) {
|
||||
this.hideMask();
|
||||
this.hideProgressBar();
|
||||
this.completed(this.showDoneIcon, aMessage, aDelayBeforeHiding);
|
||||
},
|
||||
|
||||
@@ -109,14 +114,15 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
|
||||
MochiKit.Base.bind(aFunctionToShowResult, this)();
|
||||
this.setMessage(aMessage);
|
||||
|
||||
MochiKit.Async.callLater(delay, MochiKit.Base.bind(this.hide, this))
|
||||
return MochiKit.Async.callLater(delay, MochiKit.Base.bind(this.hide, this))
|
||||
},
|
||||
|
||||
'hide': function () {
|
||||
var element = this.element();
|
||||
this.hideProgressBar();
|
||||
MochiKit.DOM.removeElementClass(element, 'ios-overlay-show');
|
||||
MochiKit.DOM.addElementClass(element, 'ios-overlay-hide');
|
||||
MochiKit.Async.callLater(1, MochiKit.Style.hideElement, element);
|
||||
return MochiKit.Async.callLater(1, MochiKit.Style.hideElement, element);
|
||||
},
|
||||
|
||||
'hideSpinner': function () {
|
||||
@@ -133,6 +139,21 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
'showProgressBar': function () {
|
||||
MochiKit.Style.showElement(this.getElement('progressBar'));
|
||||
},
|
||||
|
||||
'hideProgressBar': function () {
|
||||
MochiKit.Style.hideElement(this.getElement('progressBar'));
|
||||
},
|
||||
|
||||
'updateProgress': function (aProgressPercentage) {
|
||||
MochiKit.Style.setElementDimensions(this.getElement('progress'), {'w': aProgressPercentage}, '%');
|
||||
//console.log("OVERLAY - updating progress: " + aProgressPercentage + "%");
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
'defaultDelay': function () {
|
||||
return this._defaultDelay;
|
||||
},
|
||||
|
||||
@@ -49,7 +49,9 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
'subscription': false,
|
||||
'data': false,
|
||||
},
|
||||
'isFullyOpen': false
|
||||
'isFullyOpen': false,
|
||||
'extraFeatureComponentName': null,
|
||||
'extraFeatureContent': null
|
||||
};
|
||||
},
|
||||
|
||||
@@ -68,13 +70,13 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
|
||||
//=========================================================================
|
||||
|
||||
// showDevicePin: function () {
|
||||
// this.showExtraFeatureContent(Clipperz.PM.UI.Components.ExtraFeatures.DevicePIN());
|
||||
// },
|
||||
|
||||
showExtraFeatureComponent: function (aComponentName) {
|
||||
toggleExtraFeatureComponent: function (aComponentName) {
|
||||
return MochiKit.Base.bind(function () {
|
||||
this.showExtraFeatureContent(Clipperz.PM.UI.Components.ExtraFeatures[aComponentName]);
|
||||
if (this.state['extraFeatureComponentName'] != aComponentName) {
|
||||
this.showExtraFeatureContent(Clipperz.PM.UI.Components.ExtraFeatures[aComponentName], aComponentName);
|
||||
} else {
|
||||
this.hideExtraFeatureContent();
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
@@ -85,20 +87,21 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
hideExtraFeatureContent: function () {
|
||||
this.setState({'isFullyOpen':false});
|
||||
this.setState({
|
||||
'isFullyOpen': false,
|
||||
'extraFeatureComponentName': null,
|
||||
'extraFeatureContent': null
|
||||
});
|
||||
},
|
||||
|
||||
showExtraFeatureContent: function (aComponent) {
|
||||
showExtraFeatureContent: function (aComponent, aComponentName) {
|
||||
this.setState({
|
||||
'isFullyOpen':true,
|
||||
'extraFeatureComponentName': aComponentName,
|
||||
'extraFeatureContent': aComponent(this.extraFeaturesProps())
|
||||
});
|
||||
},
|
||||
|
||||
toggleExtraFeatureContent: function () {
|
||||
this.setState({'isFullyOpen':!this.state['isFullyOpen']});
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
renderIndex: function () {
|
||||
@@ -119,10 +122,10 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.li({'key':'account', 'className':this.state['index']['account'] ? 'open' : 'closed'}, [
|
||||
React.DOM.h1({'key':'accountH1', 'onClick':this.toggleIndexState('account')}, "Account"),
|
||||
React.DOM.ul({'key':'accountUL'}, [
|
||||
React.DOM.li({'key':'account_1', 'onClick':this.showExtraFeatureComponent('Passphrase')}, [
|
||||
React.DOM.li({'key':'account_1', 'onClick':this.toggleExtraFeatureComponent('Passphrase'), 'className':(this.state['extraFeatureComponentName'] == 'Passphrase') ? 'selected' : ''}, [
|
||||
React.DOM.h2({'key':'account_1_h2'}, "Passphrase"),
|
||||
React.DOM.div({'key':'account_1_div'}, [
|
||||
React.DOM.p({'key':'account_1_p'}, "")
|
||||
React.DOM.p({'key':'account_1_p'}, "Change your account passphrase.")
|
||||
])
|
||||
]),
|
||||
React.DOM.li({'key':'account_2'}, [
|
||||
@@ -131,7 +134,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.p({}, "")
|
||||
])
|
||||
]),
|
||||
React.DOM.li({'key':'account_3', 'onClick':this.showExtraFeatureComponent('DevicePIN')}, [
|
||||
React.DOM.li({'key':'account_3', 'onClick':this.toggleExtraFeatureComponent('DevicePIN')}, [
|
||||
React.DOM.h2({}, "Device PIN"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "Configure a PIN that will allow to get access to your cards, but only on this device.")
|
||||
@@ -143,10 +146,10 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.p({}, "")
|
||||
])
|
||||
]),
|
||||
React.DOM.li({'key':'account_5', 'onClick':this.showExtraFeatureComponent('DeleteAccount')}, [
|
||||
React.DOM.li({'key':'account_5', 'onClick':this.toggleExtraFeatureComponent('DeleteAccount'), 'className':(this.state['extraFeatureComponentName'] == 'DeleteAccount') ? 'selected' : ''}, [
|
||||
React.DOM.h2({}, "Delete account"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "")
|
||||
React.DOM.p({}, "Delete your account for good.")
|
||||
])
|
||||
])
|
||||
])
|
||||
@@ -183,29 +186,29 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.li({'key':'data', 'className':this.state['index']['data'] ? 'open' : 'closed'}, [
|
||||
React.DOM.h1({'onClick':this.toggleIndexState('data')}, "Data"),
|
||||
React.DOM.ul({'key':'data'}, [
|
||||
React.DOM.li({'key':'data_1'}, [
|
||||
React.DOM.h2({}, "Offline copy"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "With just one click you can dump all your encrypted data from Clipperz servers to your hard disk and create a read-only offline version of Clipperz to be used when you are not connected to the Internet."),
|
||||
React.DOM.a({'className':Clipperz.PM.UI.Components.classNames(offlineCopyButtonClasses), 'onClick':this.handleDownloadOfflineCopyLink}, "Download")
|
||||
])
|
||||
]),
|
||||
React.DOM.li({'key':'data_2', 'onClick':this.showExtraFeatureComponent('DataImport')}, [
|
||||
// React.DOM.li({'key':'data_1'}, [
|
||||
// React.DOM.h2({}, "Offline copy"),
|
||||
// React.DOM.div({}, [
|
||||
// React.DOM.p({}, "With just one click you can dump all your encrypted data from Clipperz servers to your hard disk and create a read-only offline version of Clipperz to be used when you are not connected to the Internet."),
|
||||
// React.DOM.a({'className':Clipperz.PM.UI.Components.classNames(offlineCopyButtonClasses), 'onClick':this.handleDownloadOfflineCopyLink}, "Download")
|
||||
// ])
|
||||
// ]),
|
||||
React.DOM.li({'key':'data_2', 'onClick':this.toggleExtraFeatureComponent('DataImport'), 'className':(this.state['extraFeatureComponentName'] == 'DataImport') ? 'selected' : ''}, [
|
||||
React.DOM.h2({}, "Import"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "")
|
||||
React.DOM.p({}, "CSV, JSON, …")
|
||||
])
|
||||
]),
|
||||
React.DOM.li({'key':'data_3'}, [
|
||||
React.DOM.li({'key':'data_3', 'onClick':this.toggleExtraFeatureComponent('DataExport'), 'className':(this.state['extraFeatureComponentName'] == 'DataExport') ? 'selected' : ''}, [
|
||||
React.DOM.h2({}, "Export"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "")
|
||||
React.DOM.p({}, "Offline copy, printable version, JSON, …")
|
||||
])
|
||||
]),
|
||||
React.DOM.li({'key':'data_4'}, [
|
||||
React.DOM.h2({}, "Sharing"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "")
|
||||
React.DOM.p({}, "Securely share cards with other users")
|
||||
])
|
||||
])
|
||||
])
|
||||
@@ -230,17 +233,20 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
|
||||
render: function () {
|
||||
//console.log("ExtraFeaturesPanel props", this.props);
|
||||
var isOpen = (this.props['settingsPanelStatus'] == 'OPEN');
|
||||
var isFullyOpen = isOpen && this.state['isFullyOpen'];
|
||||
|
||||
var classes = {
|
||||
'panel': true,
|
||||
'right': true,
|
||||
'open': this.props['settingsPanelStatus'] == 'OPEN',
|
||||
'fullOpen': this.state['isFullyOpen']
|
||||
'open': isOpen,
|
||||
'fullOpen': isFullyOpen
|
||||
}
|
||||
|
||||
|
||||
return React.DOM.div({'key':'extraFeaturesPanel', 'id':'extraFeaturesPanel', 'className':Clipperz.PM.UI.Components.classNames(classes)}, [
|
||||
this.renderIndex(),
|
||||
this.renderContent(),
|
||||
// (this.props['settingsPanelStatus'] == 'OPEN') ? this.renderContent() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
278
frontend/delta/js/Clipperz/PM/UI/ExportController.js
Normal file
278
frontend/delta/js/Clipperz/PM/UI/ExportController.js
Normal file
@@ -0,0 +1,278 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI');
|
||||
|
||||
// https://github.com/eligrey/FileSaver.js
|
||||
// https://github.com/eligrey/Blob.js
|
||||
|
||||
Clipperz.PM.UI.ExportController = function(args) {
|
||||
this._recordsInfo = args['recordsInfo'] || Clipperz.Base.exception.raise('MandatoryParameter');
|
||||
this._processedRecords = 0;
|
||||
|
||||
this._style =
|
||||
"body {" +
|
||||
"font-family: 'Dejavu Sans', monospace;" +
|
||||
"margin: 0px;" +
|
||||
"}" +
|
||||
|
||||
"header {" +
|
||||
"padding: 10px;" +
|
||||
"border-bottom: 2px solid black;" +
|
||||
"}" +
|
||||
|
||||
"h1 {" +
|
||||
"margin: 0px;" +
|
||||
"}" +
|
||||
|
||||
"h2 {" +
|
||||
"margin: 0px;" +
|
||||
"padding-top: 10px;" +
|
||||
"}" +
|
||||
|
||||
"h3 {" +
|
||||
"margin: 0px;" +
|
||||
"}" +
|
||||
|
||||
"h5 {" +
|
||||
"margin: 0px;" +
|
||||
"color: gray;" +
|
||||
"}" +
|
||||
|
||||
"ul {" +
|
||||
"margin: 0px;" +
|
||||
"padding: 0px;" +
|
||||
"}" +
|
||||
|
||||
"div > ul > li {" +
|
||||
"border-bottom: 1px solid black;" +
|
||||
"padding: 10px;" +
|
||||
"}" +
|
||||
|
||||
"div > ul > li.archived {" +
|
||||
"background-color: #ddd;" +
|
||||
"}" +
|
||||
|
||||
|
||||
"ul > li > ul > li {" +
|
||||
"font-size: 9pt;" +
|
||||
"display: inline-block;" +
|
||||
"}" +
|
||||
|
||||
"ul > li > ul > li:after {" +
|
||||
"content: \",\";" +
|
||||
"padding-right: 5px;" +
|
||||
"}" +
|
||||
|
||||
"ul > li > ul > li:last-child:after {" +
|
||||
"content: \"\";" +
|
||||
"padding-right: 0px;" +
|
||||
"}" +
|
||||
|
||||
"dl {" +
|
||||
"}" +
|
||||
|
||||
"dt {" +
|
||||
"color: gray;" +
|
||||
"font-size: 9pt;" +
|
||||
"}" +
|
||||
|
||||
"dd {" +
|
||||
"margin: 0px;" +
|
||||
"margin-bottom: 5px;" +
|
||||
"padding-left: 10px;" +
|
||||
"}" +
|
||||
|
||||
"div > div {" +
|
||||
"background-color: black;" +
|
||||
"color: white;" +
|
||||
"padding: 10px;" +
|
||||
"}" +
|
||||
|
||||
"textarea {" +
|
||||
"width: 100%;" +
|
||||
"height: 200px;" +
|
||||
"}" +
|
||||
|
||||
"@media print {" +
|
||||
"div > div, header > div {" +
|
||||
"display: none !important;" +
|
||||
"}" +
|
||||
|
||||
"ul > li {" +
|
||||
"page-break-inside: avoid;" +
|
||||
"} " +
|
||||
"}" +
|
||||
|
||||
"";
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
|
||||
|
||||
'toString': function() {
|
||||
return "Clipperz.PM.UI.ExportController";
|
||||
},
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
'recordsInfo': function () {
|
||||
return this._recordsInfo;
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'reportRecordExport': function (aRecordData) {
|
||||
var percentage;
|
||||
var exportedCardsCount;
|
||||
var totalCardsToExport;
|
||||
|
||||
this._processedRecords = this._processedRecords + 1;
|
||||
|
||||
exportedCardsCount = this._processedRecords;
|
||||
totalCardsToExport = this.recordsInfo().length;
|
||||
percentage = Math.round(100 * exportedCardsCount / totalCardsToExport);
|
||||
|
||||
//console.log("PROCESSING " + exportedCardsCount + "/" + totalCardsToExport + " - " + percentage + "%");
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'updateProgress', percentage);
|
||||
|
||||
return MochiKit.Async.succeed(aRecordData);
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'renderCardToHtml': function (jsonCardData) {
|
||||
var label = Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(jsonCardData.label);
|
||||
var allTags = MochiKit.Base.keys(Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(jsonCardData.label));
|
||||
var regularTags = MochiKit.Base.filter(Clipperz.PM.DataModel.Record.isRegularTag, allTags);
|
||||
var isArchived = MochiKit.Iter.some(allTags, MochiKit.Base.partial(MochiKit.Base.objEqual, Clipperz.PM.DataModel.Record.archivedTag));
|
||||
|
||||
return MochiKit.DOM.LI({'class': isArchived ? 'archived' : ""},
|
||||
MochiKit.DOM.H2({}, label),
|
||||
(regularTags.length > 0) ? MochiKit.DOM.UL({}, MochiKit.Base.map(function (tag) { return MochiKit.DOM.LI({}, tag);}, regularTags)): null,
|
||||
MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.DL({},
|
||||
MochiKit.Base.map(function(key) {
|
||||
return [
|
||||
MochiKit.DOM.DT(jsonCardData.currentVersion.fields[key].label),
|
||||
MochiKit.DOM.DD(jsonCardData.currentVersion.fields[key].value),
|
||||
];
|
||||
}, MochiKit.Base.keys(jsonCardData.currentVersion.fields))
|
||||
)
|
||||
),
|
||||
jsonCardData.data.notes ? MochiKit.DOM.P({}, jsonCardData.data.notes) : null
|
||||
);
|
||||
},
|
||||
|
||||
'renderToHtml': function (jsonData) {
|
||||
var title;
|
||||
var style;
|
||||
var date;
|
||||
var body;
|
||||
|
||||
title = "Clipperz data";
|
||||
style = this._style;
|
||||
date = "dd/mm/yyyy";
|
||||
|
||||
body = MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.HEADER({},
|
||||
MochiKit.DOM.H1({}, "Your data on Clipperz"),
|
||||
MochiKit.DOM.H5({}, "Export date: " + date),
|
||||
MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.P({}, "Security warning - This file lists the content of all your cards in a printer-friendly format. At the very bottom, the same content is also available in JSON format."),
|
||||
MochiKit.DOM.P({}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file. We recommend to delete it as soon as it is no longer needed."),
|
||||
MochiKit.DOM.P({}, "If you are going to print its content on paper, store the printout in a safe and private place!"),
|
||||
MochiKit.DOM.P({}, "And, if you need to access your data when no Internet connection is available, please consider the much safer option of creating an offline copy.")
|
||||
)
|
||||
),
|
||||
|
||||
MochiKit.DOM.UL({}, MochiKit.Base.map(this.renderCardToHtml, jsonData)),
|
||||
MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.H3({}, "JSON content"),
|
||||
MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.P({}, "Instructions on how to use JSON content"),
|
||||
MochiKit.DOM.P({}, "The JSON version of your data may be useful if you want to move the whole content of your Clipperz account to a new Clipperz account or recover a card that has been accidentally deleted. Just follow these instructions:"),
|
||||
MochiKit.DOM.OL({},
|
||||
MochiKit.DOM.LI({}, "Login to your Clipperz account and go to \"Data > Import\"."),
|
||||
MochiKit.DOM.LI({}, "Select the JSON option."),
|
||||
MochiKit.DOM.LI({}, "Copy and paste the JSON content in the form.")
|
||||
),
|
||||
MochiKit.DOM.P({}, "Of course, the unencrypted JSON content won't be transmitted to the Clipperz server.")
|
||||
),
|
||||
MochiKit.DOM.TEXTAREA({}, Clipperz.Base.serializeJSON(jsonData)),
|
||||
MochiKit.DOM.FOOTER({},
|
||||
MochiKit.DOM.P({},
|
||||
"This file has been downloaded from clipperz.is, a service by Clipperz Srl. - ",
|
||||
MochiKit.DOM.A({'href':'https://clipperz.is/terms_service/'}, "Terms of service"),
|
||||
" - ",
|
||||
MochiKit.DOM.A({'href':'https://clipperz.is/privacy_policy/'}, "Privacy policy")
|
||||
),
|
||||
MochiKit.DOM.H4({}, "Clipperz - keep it to yourself")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
return '<html><head><title>' + title + '</title><style type="text/css">' + style + '</style></head><body>' + MochiKit.DOM.toHTML(body) + '</body></html>';
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
'saveResult': function (exportedJSON) {
|
||||
var blob;
|
||||
var sortedJSON;
|
||||
|
||||
sortedJSON = MochiKit.Iter.sorted(exportedJSON, function(a,b) { return a.label.toUpperCase().localeCompare(b.label.toUpperCase()); } );
|
||||
|
||||
blob = new Blob([this.renderToHtml(sortedJSON)], {type: "text/html;charset=utf-8"});
|
||||
saveAs(blob, "clipperz_data.html");
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'run': function () {
|
||||
var deferredResult;
|
||||
var self = this;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("ExportController.run", {trace:false});
|
||||
deferredResult.addCallback(MochiKit.Base.map, function(recordIn) {
|
||||
var innerDeferredResult;
|
||||
|
||||
innerDeferredResult = new Clipperz.Async.Deferred("ExportController.run__exportRecord", {trace:false});
|
||||
innerDeferredResult.addMethod(recordIn._rowObject, 'export');
|
||||
innerDeferredResult.addMethod(self, 'reportRecordExport');
|
||||
innerDeferredResult.callback();
|
||||
|
||||
return innerDeferredResult;
|
||||
});
|
||||
deferredResult.addCallback(Clipperz.Async.collectAll);
|
||||
deferredResult.addMethod(this, 'saveResult');
|
||||
deferredResult.callback(this.recordsInfo());
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
__syntaxFix__: "syntax fix"
|
||||
});
|
||||
148
frontend/delta/js/Clipperz/PM/UI/ImportContext.js
Normal file
148
frontend/delta/js/Clipperz/PM/UI/ImportContext.js
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI');
|
||||
|
||||
Clipperz.PM.UI.ImportContext = function(args) {
|
||||
|
||||
this.inputString = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
MochiKit.Base.update(Clipperz.PM.UI.ImportContext.prototype, {
|
||||
|
||||
'toString': function() {
|
||||
return "Clipperz.PM.UI.ImportContext";
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'resetContext': function() {
|
||||
delete this.inputString;
|
||||
delete this.format;
|
||||
delete this.jsonToImport;
|
||||
delete this.recordsToImport;
|
||||
},
|
||||
|
||||
'getInitialJsonContext': function(aJsonList) {
|
||||
return {
|
||||
'format': 'json',
|
||||
'jsonToImport': aJsonList,
|
||||
'recordsToImport': aJsonList.map(function(d){return d._importId})
|
||||
};
|
||||
},
|
||||
|
||||
'getInitialCsvContext': function(aCsvTable) {
|
||||
var result;
|
||||
var nColumns;
|
||||
var defaultSelectedColumns;
|
||||
var defaultHiddenColumns;
|
||||
var defaultColumnLabels;
|
||||
var columnLabelsFirstrow;
|
||||
var i;
|
||||
|
||||
nColumns = aCsvTable[0].length;
|
||||
|
||||
defaultSelectedColumns = {};
|
||||
defaultHiddenColumns = {};
|
||||
defaultColumnLabels = {};
|
||||
columnLabelsFirstrow = {};
|
||||
for (i=0; i<nColumns; i++) {
|
||||
defaultSelectedColumns[i] = true;
|
||||
defaultHiddenColumns[i] = false;
|
||||
defaultColumnLabels[i] = "";
|
||||
columnLabelsFirstrow[i] = aCsvTable[0][i];
|
||||
}
|
||||
|
||||
return {
|
||||
'format': 'csv',
|
||||
'parsedCsv': aCsvTable,
|
||||
'nColumns': nColumns,
|
||||
'selectedColumns': defaultSelectedColumns,
|
||||
'firstRowAsLabels': false,
|
||||
'columnLabels': defaultColumnLabels,
|
||||
'columnLabelsFirstrow': columnLabelsFirstrow,
|
||||
'titlesColumn': null,
|
||||
'notesColumn': null,
|
||||
'hiddenColumns': defaultHiddenColumns,
|
||||
};
|
||||
},
|
||||
|
||||
'getCsvLabels': function() {
|
||||
return (this.firstRowAsLabels) ? this.columnLabelsFirstrow : this.columnLabels;
|
||||
},
|
||||
|
||||
'processCsv': function() {
|
||||
var jsonToImport;
|
||||
var recordsToImport;
|
||||
var columnLabels = this.getCsvLabels();
|
||||
|
||||
jsonToImport = [];
|
||||
|
||||
for (rowCount=0; rowCount<this.parsedCsv.length; rowCount++) {
|
||||
var rowCount,cellCount;
|
||||
|
||||
if (rowCount != 0 || ! this.firstRowAsLabels) {
|
||||
var record;
|
||||
|
||||
record = {};
|
||||
record._importId = rowCount;
|
||||
record.label = this.parsedCsv[rowCount][this.titlesColumn];
|
||||
record.data = {'notes': ""};
|
||||
record.currentVersion = {'fields': {}};
|
||||
|
||||
for (cellCount=0; cellCount<this.parsedCsv[rowCount].length; cellCount++) {
|
||||
if (this.selectedColumns[cellCount] && cellCount != this.notesColumn && cellCount != this.titlesColumn) {
|
||||
var fieldKey = rowCount+"-"+cellCount;
|
||||
var field = {
|
||||
'label': columnLabels[cellCount],
|
||||
'value': this.parsedCsv[rowCount][cellCount],
|
||||
'hidden': this.hiddenColumns[cellCount]
|
||||
};
|
||||
record.currentVersion.fields[fieldKey] = field;
|
||||
} else if (cellCount == this.notesColumn) {
|
||||
record.data.notes = this.parsedCsv[rowCount][cellCount];
|
||||
}
|
||||
}
|
||||
|
||||
jsonToImport.push(record);
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof(this.recordsToImport) == 'undefined') {
|
||||
recordsToImport = MochiKit.Base.map(function(r){return r._importId},jsonToImport);
|
||||
} else {
|
||||
recordsToImport = this.recordsToImport;
|
||||
}
|
||||
|
||||
return {
|
||||
'jsonToImport': jsonToImport,
|
||||
'recordsToImport': recordsToImport
|
||||
};
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
__syntaxFix__: "syntax fix"
|
||||
});
|
||||
@@ -62,7 +62,11 @@ Clipperz.PM.UI.MainController = function() {
|
||||
|
||||
this.registerForNotificationCenterEvents([
|
||||
'doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack',
|
||||
'changePassphrase', 'deleteAccount', 'importCards',
|
||||
'changePassphrase', 'deleteAccount',
|
||||
// 'export',
|
||||
'importCards',
|
||||
'downloadExport',
|
||||
'updateProgress',
|
||||
'toggleSelectionPanel', 'toggleSettingsPanel',
|
||||
'matchMediaQuery', 'unmatchMediaQuery',
|
||||
'selectAllCards', 'selectRecentCards', 'search', 'tagSelected', 'selectUntaggedCards',
|
||||
@@ -105,6 +109,10 @@ MochiKit.Base.update(Clipperz.PM.UI.MainController.prototype, {
|
||||
return this._overlay;
|
||||
},
|
||||
|
||||
updateProgress_handler: function (aProgressPercentage) {
|
||||
this.overlay().updateProgress(aProgressPercentage);
|
||||
},
|
||||
|
||||
loginForm: function () {
|
||||
return this._loginForm;
|
||||
},
|
||||
@@ -257,7 +265,7 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
checkPassphrase: function( passphraseIn ) {
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.deleteAccount_handler", {trace: false});
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.checkPassphrase", {trace: false});
|
||||
deferredResult.addMethod(this.user(), 'getPassphrase');
|
||||
deferredResult.addCallback(function (candidatePassphrase, realPassphrase) { return candidatePassphrase == realPassphrase; }, passphraseIn );
|
||||
|
||||
@@ -499,6 +507,9 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred('MainController.updateSelectedCard', {trace:false});
|
||||
deferredResult.addMethod(this.user(), 'getRecord', someInfo['reference']);
|
||||
|
||||
// deferredResult.addMethod(this, function(d) {console.log(d); return d;});
|
||||
|
||||
deferredResult.addMethod(this, 'collectRecordInfo');
|
||||
|
||||
deferredResult.addMethod(this, 'setPageProperties', 'mainPage', 'selectedCard');
|
||||
@@ -1236,6 +1247,28 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
// export_handler: function(exportType) {
|
||||
// return Clipperz.PM.UI.ExportController.exportJSON( this.recordsInfo(), exportType );
|
||||
// },
|
||||
|
||||
downloadExport_handler: function () {
|
||||
var exportController;
|
||||
var deferredResult;
|
||||
|
||||
exportController = new Clipperz.PM.UI.ExportController({'recordsInfo': this.recordsInfo()});
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.downloadExport_handler", {trace: false});
|
||||
deferredResult.addMethod(this.overlay(), 'show', "exporting …", true, true);
|
||||
// deferredResult.addCallback(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'toggleSettingsPanel');
|
||||
deferredResult.addMethod(exportController, 'run');
|
||||
deferredResult.addMethod(this.overlay(), 'done', "", 1);
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
changePassphrase_handler: function(newPassphrase) {
|
||||
var currentPage = this.pages()[this.currentPage()];
|
||||
var deferredResult;
|
||||
@@ -1261,11 +1294,20 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
deleteAccount_handler: function() {
|
||||
var deferredResult;
|
||||
var doneMessageDelay = 2;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.deleteAccount_handler", {trace: false});
|
||||
deferredResult.addCallback(MochiKit.Base.method(this, 'ask', {
|
||||
'question': "Do you really want to permanently delete your account?",
|
||||
'possibleAnswers':{
|
||||
'cancel': {'label':"No", 'isDefault':true, 'answer':MochiKit.Base.methodcaller('cancel', new MochiKit.Async.CancelledError())},
|
||||
'revert': {'label':"Yes", 'isDefault':false, 'answer':MochiKit.Base.methodcaller('callback')}
|
||||
}
|
||||
})),
|
||||
deferredResult.addMethod(this.overlay(), 'show', "deleting …", true);
|
||||
deferredResult.addMethod(this.user(), 'deleteAccount');
|
||||
deferredResult.addCallback(function() { window.location.href = '/'; });
|
||||
deferredResult.addMethod(this.overlay(), 'done', "deleted", doneMessageDelay);
|
||||
deferredResult.addCallback(MochiKit.Async.callLater, doneMessageDelay, function() { window.location.href = '/'; });
|
||||
|
||||
deferredResult.callback();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user