mirror of
http://git.whoc.org.uk/git/password-manager.git
synced 2025-10-30 02:47:36 +01:00
Merged all pending work done on the private repository
This commit is contained in:
@@ -226,7 +226,9 @@ console.log("DROP"); //, anEvent);
|
||||
|
||||
handleKeyDown: function (aField) {
|
||||
var self = this;
|
||||
|
||||
return function (anEvent) {
|
||||
|
||||
switch (anEvent.keyCode) {
|
||||
case 9: // tab
|
||||
var fieldReferences = MochiKit.Base.map(function (aValue) { return aValue['_reference']}, self.fields());
|
||||
@@ -236,12 +238,14 @@ console.log("DROP"); //, anEvent);
|
||||
MochiKit.Base.method(aField, 'isEmpty'),
|
||||
Clipperz.Async.deferredIf('isEmpty',[
|
||||
], [
|
||||
MochiKit.Base.method(self, 'addNewField')
|
||||
MochiKit.Base.method(anEvent, 'preventDefault'),
|
||||
MochiKit.Base.method(self, 'addNewField'),
|
||||
// TODO: set the focus to the newly created field
|
||||
// hints: http://stackoverflow.com/questions/24248234/react-js-set-input-value-from-sibling-component
|
||||
])
|
||||
], {trace:false});
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
};
|
||||
@@ -393,6 +397,7 @@ console.log("DROP"); //, anEvent);
|
||||
if (this.state['draggedFieldReference'] != null) {
|
||||
renderedFields = this.updateRenderedFieldsWithDropArea(renderedFields);
|
||||
}
|
||||
|
||||
return React.DOM.div({'className':'cardFields' /*, 'dropzone':'move'*/}, renderedFields);
|
||||
},
|
||||
|
||||
|
||||
@@ -72,8 +72,10 @@ Clipperz.PM.UI.Components.Cards.TagEditorClass = React.createClass({
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
addTagValue: function (anEvent) {
|
||||
this.addTag(anEvent.currentTarget.value);
|
||||
anEvent.currentTarget.value = "";
|
||||
if (anEvent.currentTarget.value) {
|
||||
this.addTag(anEvent.currentTarget.value);
|
||||
anEvent.currentTarget.value = "";
|
||||
}
|
||||
},
|
||||
|
||||
handleKeyDown: function (anEvent) {
|
||||
|
||||
@@ -36,9 +36,9 @@ Clipperz.PM.UI.Components.CheckboxClass = React.createClass({
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({className:'checkbox', onClick:this.props['eventHandler']}, [
|
||||
React.DOM.input({name:this.props['id'], id:this.props['id'], value:this.props['id'], type:'checkbox', checked:this.props['checked']}),
|
||||
React.DOM.label({className:'check', 'for':this.props['id']}),
|
||||
React.DOM.label({className:'info', 'for':this.props['id']}, "enable local storage")
|
||||
React.DOM.input({'name':this.props['id'], 'id':this.props['id'], 'value':this.props['id'], 'type':'checkbox', 'checked':this.props['checked']}),
|
||||
React.DOM.label({'className':'check', 'htmlFor':this.props['id']}),
|
||||
React.DOM.label({'className':'info', 'htmlFor':this.props['id']}, "enable local storage")
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +59,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass = React.createClass({
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature devicePIN'}, [
|
||||
React.DOM.h1({}, "Export"),
|
||||
React.DOM.div({'className':'header'}, [
|
||||
React.DOM.h1({}, "Export"),
|
||||
]),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.ul({}, [
|
||||
React.DOM.li({}, [
|
||||
@@ -76,7 +78,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass = React.createClass({
|
||||
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.p({'className':'warning'}, "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")
|
||||
]),
|
||||
|
||||
@@ -24,144 +24,76 @@ refer to http://www.clipperz.com.
|
||||
"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
|
||||
'importContext': new Clipperz.PM.UI.ImportContext(this),
|
||||
};
|
||||
},
|
||||
|
||||
componentWillUnmount: function () {
|
||||
this.state['importContext'].release();
|
||||
this.setState({'importContext': null})
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
getStepIndex: function(aStep) {
|
||||
return this._steps.indexOf(aStep);
|
||||
},
|
||||
|
||||
getStepAfter: function() {
|
||||
return this._steps[this.getStepIndex(this.state.currentStep) + 1];
|
||||
importContext: function () {
|
||||
return this.state.importContext;
|
||||
},
|
||||
|
||||
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';
|
||||
componentWithName: function (aName) {
|
||||
var result;
|
||||
var path;
|
||||
var i, c;
|
||||
|
||||
path = aName.split('.');
|
||||
|
||||
result = Clipperz.PM.UI.Components.ExtraFeatures.DataImport;
|
||||
c = path.length;
|
||||
for (i=0; i<c; i++) {
|
||||
result = result[path[i]];
|
||||
}
|
||||
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
renderNavbar: function (currentStep) {
|
||||
return React.DOM.ul({'className': 'stepNavbar' + ' ' + currentStep},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(aStep){
|
||||
// return React.DOM.li({'className': this.importContext().stepStatus(aStep)}, this.importContext().stepName(aStep));
|
||||
return React.DOM.li({'className': this.importContext().stepStatus(aStep)}, '.');
|
||||
}, this),this.importContext().steps())
|
||||
)
|
||||
},
|
||||
|
||||
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
|
||||
var currentStep = this.importContext().currentStep().replace('.','_');
|
||||
|
||||
return React.DOM.div({className:'extraFeature dataImport'}, [
|
||||
React.DOM.div({'className':'header'}, [
|
||||
React.DOM.h1({}, "Import"),
|
||||
]),
|
||||
React.DOM.div({'className': 'content' + ' ' + currentStep + ' ' + this.importContext().inputFormat()}, [
|
||||
React.DOM.div({'className': 'step' + ' ' + currentStep}, [
|
||||
new this.componentWithName(this.importContext().currentStep())({
|
||||
'importContext': this.importContext(),
|
||||
}),
|
||||
]),
|
||||
this.renderNavbar(currentStep),
|
||||
React.DOM.div({'className': 'buttons' + ' ' + currentStep}, [
|
||||
React.DOM.a({
|
||||
'className': 'button back ' + this.importContext().backButtonStatus(),
|
||||
'onClick': this.importContext().goBackHandler()
|
||||
}, React.DOM.span({}, "Back")),
|
||||
React.DOM.a({
|
||||
'className': 'button next ' + this.importContext().forwardButtonStatus(),
|
||||
'onClick': this.importContext().goForwardHandler()
|
||||
}, React.DOM.span({}, "Next"))
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
|
||||
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.CSV');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.ColumnsClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'selectedColumns': this.props.importContext.state('csvData.selectedColumns'),
|
||||
};
|
||||
},
|
||||
|
||||
toggleColumn: function(columnIndex) {
|
||||
var newSelectedColumns;
|
||||
|
||||
newSelectedColumns = this.state['selectedColumns'];
|
||||
newSelectedColumns[columnIndex] = ! newSelectedColumns[columnIndex];
|
||||
|
||||
this.setState({'selectedColumns': newSelectedColumns});
|
||||
this.props.importContext.setState('csvData.selectedColumns', newSelectedColumns);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var importContext = this.props.importContext;
|
||||
|
||||
return React.DOM.div({},[
|
||||
React.DOM.p({}, "Select the columns you want to import."),
|
||||
React.DOM.table({'className':'csvTable', 'key':'csvTableColumns'},[
|
||||
React.DOM.thead({}, React.DOM.tr({'className':'columnSelectors', 'key':'csv-colsel'}, MochiKit.Base.map(MochiKit.Base.bind(function (columnIndex) {
|
||||
var thClasses = {
|
||||
'title': (columnIndex == importContext.state('csvData.titleIndex')),
|
||||
'notes': (columnIndex == importContext.state('csvData.notesIndex')),
|
||||
}
|
||||
|
||||
return React.DOM.th({
|
||||
'key': 'csv-colsel-' + columnIndex,
|
||||
'className': Clipperz.PM.UI.Components.classNames(thClasses)
|
||||
}, React.DOM.input({
|
||||
'key': 'csv-label-input-' + columnIndex,
|
||||
'type': 'checkbox',
|
||||
'checked': this.state['selectedColumns'][columnIndex],
|
||||
'onChange': MochiKit.Base.partial(this.toggleColumn, columnIndex)
|
||||
}));
|
||||
}, this), MochiKit.Iter.range(this.state['selectedColumns'].length)))),
|
||||
this.props.importContext.renderCsvTableBody(false)
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Columns = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.ColumnsClass);
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
|
||||
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.CSV');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.HiddenClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'hiddenFields': this.props.importContext.state('csvData.hiddenFields'),
|
||||
};
|
||||
},
|
||||
|
||||
onChangeCallback: function (columnIndex) {
|
||||
var newHiddenColumns = this.state['hiddenFields'];
|
||||
|
||||
newHiddenColumns[columnIndex] = ! newHiddenColumns[columnIndex];
|
||||
|
||||
this.setState({'hiddenFields': newHiddenColumns});
|
||||
this.props.importContext.setState('csvData.hiddenFields', newHiddenColumns);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var importContext = this.props.importContext;
|
||||
|
||||
return React.DOM.div({},[
|
||||
React.DOM.p({}, "Select the fields that should be hidden. (passwords, PINs, ...)"),
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
React.DOM.tr({},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function (cellInfo) {
|
||||
var result;
|
||||
var columnIndex = cellInfo[0];
|
||||
var columnValue = cellInfo[1];
|
||||
var thClasses = {
|
||||
'title': (columnIndex == importContext.state('csvData.titleIndex')),
|
||||
'notes': (columnIndex == importContext.state('csvData.notesIndex')),
|
||||
}
|
||||
|
||||
if (importContext.state('csvData.selectedColumns')[columnIndex]) {
|
||||
result = React.DOM.th({
|
||||
'key': 'csv-notes-header-' + columnIndex,
|
||||
'className': Clipperz.PM.UI.Components.classNames(thClasses)
|
||||
}, [
|
||||
React.DOM.input({
|
||||
'type': 'checkbox',
|
||||
'id': 'csv-notes-input-' + columnIndex,
|
||||
'key': 'csv-notes-input-' + columnIndex,
|
||||
'ref': 'csv-notes-input-' + columnIndex,
|
||||
'checked': this.state['hiddenFields'][columnIndex],
|
||||
'onChange': MochiKit.Base.partial(this.onChangeCallback, columnIndex),
|
||||
'disabled': ((columnIndex == importContext.state('csvData.titleIndex')) || (columnIndex == importContext.state('csvData.notesIndex')))
|
||||
}),
|
||||
React.DOM.label({'htmlFor': 'csv-notes-input-' + columnIndex}, columnValue),
|
||||
]);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, this), Clipperz.Base.zipWithRange(importContext.state('csvData.labels')))
|
||||
)
|
||||
),
|
||||
importContext.renderCsvTableBody(true)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Hidden = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.HiddenClass);
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
|
||||
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.CSV');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.LabelsClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'useFirstRowAsLabels': this.props.importContext.state('csvData.useFirstRowAsLabels'),
|
||||
'labels': this.props.importContext.state('csvData.labels'),
|
||||
};
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
updateImportContextState: function () {
|
||||
this.props.importContext.setState('csvData.useFirstRowAsLabels', this.state['useFirstRowAsLabels']);
|
||||
this.props.importContext.setState('csvData.labels', this.state['labels']);
|
||||
},
|
||||
|
||||
toggleFirstRow: function() {
|
||||
var newState;
|
||||
|
||||
newState = this.state;
|
||||
newState['useFirstRowAsLabels'] = ! this.state['useFirstRowAsLabels'];
|
||||
if (newState['useFirstRowAsLabels']) {
|
||||
newState['labels'] = MochiKit.Base.map(Clipperz.Base.trim, this.props.importContext.state('csvData.data')[0]);
|
||||
}
|
||||
|
||||
this.setState(newState);
|
||||
this.updateImportContextState();
|
||||
},
|
||||
|
||||
onChangeLabelCallback: function(columnIndex) {
|
||||
var newState;
|
||||
|
||||
newState = this.state;
|
||||
newState['labels'][columnIndex] = this.refs['csv-labels-input-' + columnIndex].getDOMNode().value;
|
||||
|
||||
this.setState(newState);
|
||||
this.updateImportContextState();
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var importContext = this.props.importContext;
|
||||
|
||||
return React.DOM.div({},[
|
||||
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.div({}, [
|
||||
React.DOM.input({
|
||||
'id': 'csv-labels-firstrow',
|
||||
'type': 'checkbox',
|
||||
'checked': this.state['useFirstRowAsLabels'],
|
||||
'onChange': this.toggleFirstRow
|
||||
}),
|
||||
React.DOM.label({'htmlFor':'csv-labels-firstrow'}, "Use the first row as labels")
|
||||
]),
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
React.DOM.tr({},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(cellInfo) {
|
||||
var result;
|
||||
var columnIndex = cellInfo[0];
|
||||
var columnValue = cellInfo[1];
|
||||
var thClasses = {
|
||||
'title': (columnIndex == importContext.state('csvData.titleIndex')),
|
||||
'notes': (columnIndex == importContext.state('csvData.notesIndex')),
|
||||
}
|
||||
|
||||
if (importContext.state('csvData.selectedColumns')[columnIndex]) {
|
||||
result = React.DOM.th({
|
||||
'key':'csv-labels-header-' + columnIndex,
|
||||
'className': Clipperz.PM.UI.Components.classNames(thClasses)
|
||||
}, [
|
||||
React.DOM.input({
|
||||
'type': 'text',
|
||||
'id': 'csv-labels-input-' + columnIndex,
|
||||
'key': 'csv-labels-input-' + columnIndex,
|
||||
'ref': 'csv-labels-input-' + columnIndex,
|
||||
'value': columnValue,
|
||||
'placeholder': "…",
|
||||
'onChange': MochiKit.Base.partial(this.onChangeLabelCallback, columnIndex)
|
||||
})
|
||||
]);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, this), Clipperz.Base.zipWithRange(this.state['labels']))
|
||||
)
|
||||
),
|
||||
importContext.renderCsvTableBody(true)
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Labels = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.LabelsClass);
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
|
||||
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.CSV');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.NotesClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'notesIndex': this.props.importContext.state('csvData.notesIndex'),
|
||||
};
|
||||
},
|
||||
|
||||
onChangeCallback: function (columnIndex) {
|
||||
this.setState({'notesIndex': columnIndex});
|
||||
this.props.importContext.setState('csvData.notesIndex', columnIndex);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var importContext = this.props.importContext;
|
||||
|
||||
return React.DOM.div({},[
|
||||
React.DOM.p({}, "Select the column that represents a \"notes\" field. (optional)"),
|
||||
React.DOM.input({
|
||||
'id': 'csv-notes-nonotes',
|
||||
'type': 'radio',
|
||||
'checked': ! this.state['notesIndex'],
|
||||
'onChange': MochiKit.Base.partial(this.onChangeCallback, null)
|
||||
}),
|
||||
React.DOM.label({'htmlFor': 'csv-notes-nonotes'}, "\"notes\" field not present"),
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
React.DOM.tr({}, MochiKit.Base.map(MochiKit.Base.bind(function (cellInfo) {
|
||||
var result;
|
||||
var columnIndex = cellInfo[0];
|
||||
var columnValue = cellInfo[1];
|
||||
var thClasses = {
|
||||
'title': (columnIndex == importContext.state('csvData.titleIndex')),
|
||||
'notes': (columnIndex == importContext.state('csvData.notesIndex')),
|
||||
}
|
||||
|
||||
if (importContext.state('csvData.selectedColumns')[columnIndex]) {
|
||||
result = React.DOM.th({
|
||||
'key': 'csv-notes-header-' + columnIndex,
|
||||
'className': Clipperz.PM.UI.Components.classNames(thClasses)
|
||||
}, [
|
||||
React.DOM.input({
|
||||
'type': 'radio',
|
||||
'id': 'csv-notes-input-' + columnIndex,
|
||||
'key': 'csv-notes-input-' + columnIndex,
|
||||
'ref': 'csv-notes-input-' + columnIndex,
|
||||
'checked': (columnIndex == this.state['notesIndex']),
|
||||
'onChange': MochiKit.Base.partial(this.onChangeCallback, columnIndex),
|
||||
'disabled': (columnIndex == importContext.state('csvData.titleIndex'))
|
||||
}),
|
||||
React.DOM.label({'htmlFor': 'csv-notes-input-' + columnIndex}, columnValue),
|
||||
]);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, this), Clipperz.Base.zipWithRange(this.props.importContext.state('csvData.labels'))))
|
||||
),
|
||||
importContext.renderCsvTableBody(true)
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Notes = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.NotesClass);
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
|
||||
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.CSV');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.TitlesClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'titleIndex': this.props.importContext.state('csvData.titleIndex'),
|
||||
};
|
||||
},
|
||||
|
||||
onChangeCallback: function(columnIndex) {
|
||||
this.setState({'titleIndex': columnIndex});
|
||||
this.props.importContext.setState('csvData.titleIndex', columnIndex);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var importContext = this.props.importContext;
|
||||
|
||||
return React.DOM.div({},[
|
||||
React.DOM.p({}, "Select the column that contains the title of cards you are importing."),
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
React.DOM.tr({}, MochiKit.Base.map(MochiKit.Base.bind(function (cellInfo) {
|
||||
var result;
|
||||
var columnIndex = cellInfo[0];
|
||||
var columnValue = cellInfo[1];
|
||||
var thClasses = {
|
||||
'title': (columnIndex == importContext.state('csvData.titleIndex')),
|
||||
'notes': (columnIndex == importContext.state('csvData.notesIndex')),
|
||||
}
|
||||
|
||||
if (importContext.state('csvData.selectedColumns')[columnIndex]) {
|
||||
result = React.DOM.th({
|
||||
'key':'csv-titles-header-' + columnIndex,
|
||||
'className': Clipperz.PM.UI.Components.classNames(thClasses)
|
||||
}, [
|
||||
React.DOM.input({
|
||||
'type': 'radio',
|
||||
'id': 'csv-titles-input-' + columnIndex,
|
||||
'key': 'csv-titles-input-' + columnIndex,
|
||||
'ref': 'csv-titles-input-' + columnIndex,
|
||||
'checked': (columnIndex == this.state['titleIndex']),
|
||||
'onChange': MochiKit.Base.partial(this.onChangeCallback, columnIndex)
|
||||
}),
|
||||
React.DOM.label({'htmlFor': 'csv-titles-input-' + columnIndex}, columnValue),
|
||||
]);
|
||||
} else {
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, this), Clipperz.Base.zipWithRange(importContext.state('csvData.labels'))))
|
||||
),
|
||||
importContext.renderCsvTableBody(true)
|
||||
])
|
||||
]);
|
||||
}
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.Titles = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CSV.TitlesClass);
|
||||
@@ -1,97 +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.CsvColumnsClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'selectedColumns': this.props.importContext.selectedColumns
|
||||
};
|
||||
},
|
||||
|
||||
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.setState({'selectedColumns': newSelectedColumns});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
//console.log(this.props.importContext);
|
||||
var columnSelectors;
|
||||
var rowCount;
|
||||
var i;
|
||||
|
||||
columnSelectors = [];
|
||||
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.state.selectedColumns[i],
|
||||
'onChange': MochiKit.Base.partial(this.toggleColumn,i)
|
||||
}) ) );
|
||||
}
|
||||
|
||||
rowCount = 0;
|
||||
|
||||
return React.DOM.div({},[
|
||||
React.DOM.p({}, "Select the columns you want to import."),
|
||||
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){
|
||||
var cellCount;
|
||||
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);
|
||||
}, row));
|
||||
rowCount++;
|
||||
|
||||
return result;
|
||||
}, this.props.importContext.parsedCsv)
|
||||
),
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumns = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass);
|
||||
@@ -1,148 +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.CsvHiddenClass = React.createClass({
|
||||
|
||||
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 newHiddenColumns = this.state.hiddenColumns;
|
||||
|
||||
newHiddenColumns[columnN] = ! newHiddenColumns[columnN];
|
||||
|
||||
this.setState({'hiddenColumns': newHiddenColumns});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var cellCount, rowCount;
|
||||
|
||||
var importContext = this.props.importContext;
|
||||
|
||||
cellCount = 0;
|
||||
rowCount = 0;
|
||||
return React.DOM.div({},[
|
||||
React.DOM.p({}, "Select the fields that should be hidden. (passwords, PINs, ...)"),
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
|
||||
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({},
|
||||
|
||||
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)
|
||||
|
||||
)
|
||||
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvHidden = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvHiddenClass);
|
||||
@@ -1,184 +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.CsvLabelsClass = React.createClass({
|
||||
|
||||
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);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
handleNextStep: function() {
|
||||
return this.state;
|
||||
},
|
||||
|
||||
updateNextStatus: function() {
|
||||
this.props.setNextStepCallback((! this.isNextDisabled()) ? this.handleNextStep : null);
|
||||
},
|
||||
|
||||
isNextDisabled: function() {
|
||||
var result;
|
||||
|
||||
var importContext = this.props.importContext;
|
||||
var columnLabels = this.getLabels();
|
||||
|
||||
result = false;
|
||||
for (i in columnLabels) {
|
||||
result = result || ((columnLabels[i] == '')&&(importContext.selectedColumns[i]));
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
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 = this.state;
|
||||
if (newState.firstRowAsLabels) {
|
||||
newState.columnLabelsFirstrow[columnN] = this.refs['csv-labels-input-' + columnN].getDOMNode().value;
|
||||
} else {
|
||||
newState.columnLabels[columnN] = this.refs['csv-labels-input-' + columnN].getDOMNode().value;
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
rowCount = 0;
|
||||
cellCount = 0;
|
||||
return React.DOM.div({},[
|
||||
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.state.firstRowAsLabels,
|
||||
'onChange': this.toggleFirstRow
|
||||
}),
|
||||
React.DOM.label({'htmlFor':'csv-labels-firstrow'}, "Use the first row as labels"),
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
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({},
|
||||
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)
|
||||
)
|
||||
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvLabels = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvLabelsClass);
|
||||
@@ -1,138 +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.CsvNotesClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'notesColumn': this.props.importContext.notesColumn
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.props.setNextStepCallback(this.handleNextStep);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
handleNextStep: function() {
|
||||
return this.state;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
onChangeCallback: function(columnN) {
|
||||
this.setState({'notesColumn': columnN});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var cellCount, rowCount;
|
||||
|
||||
var importContext = this.props.importContext;
|
||||
|
||||
cellCount = 0;
|
||||
rowCount = 0;
|
||||
return React.DOM.div({},[
|
||||
React.DOM.p({}, "Select the column that represents a \"notes\" field. (optional)"),
|
||||
React.DOM.input({
|
||||
'id': 'csv-notes-nonotes',
|
||||
'type': 'radio',
|
||||
'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({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
|
||||
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({},
|
||||
|
||||
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)
|
||||
)
|
||||
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvNotes = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvNotesClass);
|
||||
@@ -1,146 +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.CsvTitlesClass = React.createClass({
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'titlesColumn': this.props.importContext.titlesColumn,
|
||||
'notesColumn': this.props.importContext.notesColumn
|
||||
};
|
||||
},
|
||||
|
||||
componentDidMount() {
|
||||
this.props.setNextStepCallback((this.isNextDisabled()) ? null : this.handleNextStep);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
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.titlesColumn = columnN;
|
||||
|
||||
this.updateNextStatus();
|
||||
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var rowCount, cellCount;
|
||||
|
||||
var importContext = this.props.importContext;
|
||||
var columnLabels = importContext.getCsvLabels();
|
||||
|
||||
rowCount = 0;
|
||||
cellCount = 0;
|
||||
return React.DOM.div({},[
|
||||
React.DOM.p({}, "Select the column that contains titles of the cards you are importing. (mandatory)"),
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({},
|
||||
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({},
|
||||
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)
|
||||
)
|
||||
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvTitles = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvTitlesClass);
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
|
||||
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.ImportClass = React.createClass({
|
||||
//=========================================================================
|
||||
|
||||
importHandler: function (anEvent) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'importCards', this.props.importContext.state('recordsToImport'));
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div({}, [
|
||||
React.DOM.h5({}, "Cards to import: " + this.props.importContext.state('recordsToImport').length + " (of " + this.props.importContext.state('jsonData').length + ")"),
|
||||
React.DOM.a({'className': 'button import', 'onClick': this.importHandler}, "Import")
|
||||
|
||||
]);
|
||||
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.Import = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImport.ImportClass);
|
||||
@@ -25,15 +25,16 @@ 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,
|
||||
'inputString': this.props.importContext.inputString(),
|
||||
// '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);
|
||||
},
|
||||
@@ -46,12 +47,13 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
|
||||
var parsedInput;
|
||||
|
||||
var inputString = this.refs['input-textarea'].getDOMNode().value.trim();
|
||||
|
||||
// this.props.importContext.setData(inputString);
|
||||
|
||||
result = {'inputString': inputString};
|
||||
|
||||
/*
|
||||
parsedInput = this.parseJson(inputString);
|
||||
if (parsedInput) {
|
||||
MochiKit.Base.update(result,this.props.importContext.getInitialJsonContext(parsedInput));
|
||||
MochiKit.Base.update(result, this.props.importContext.getInitialJsonContext(parsedInput));
|
||||
} else {
|
||||
parsedInput = this.parseCsv(inputString);
|
||||
if (parsedInput) {
|
||||
@@ -60,12 +62,12 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
|
||||
result = false;
|
||||
}
|
||||
}
|
||||
|
||||
* /
|
||||
return result;
|
||||
},
|
||||
|
||||
updateNextStatus: function(newInputString) {
|
||||
this.props.setNextStepCallback((newInputString) ? this.handleNextStep : null);
|
||||
// this.props.setNextStepCallback((newInputString) ? this.handleNextStep : null);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
@@ -145,69 +147,87 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
*/
|
||||
//=========================================================================
|
||||
|
||||
handleUploadFiles: function(someFiles) {
|
||||
|
||||
updateTextAreaContent: function (aValue, shouldMoveForwardToo) {
|
||||
var value;
|
||||
value = this.props.importContext.setInputString(aValue, shouldMoveForwardToo);
|
||||
this.setState({'inputString': value});
|
||||
},
|
||||
|
||||
handleUploadFiles: function (someFiles) {
|
||||
var file;
|
||||
var reader;
|
||||
|
||||
if (someFiles.length == 1) {
|
||||
file = someFiles[0];
|
||||
reader = new FileReader();
|
||||
reader = new FileReader();
|
||||
|
||||
// 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.extractJsonFromClipperzExport(reader.result);
|
||||
var newInputString;
|
||||
|
||||
// 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.extractJsonFromClipperzExport(reader.result);
|
||||
var newInputString;
|
||||
|
||||
if (extractedJson) {
|
||||
newInputString = extractedJson;
|
||||
} else {
|
||||
newInputString = reader.result;
|
||||
}
|
||||
if (extractedJson) {
|
||||
newInputString = extractedJson;
|
||||
} else {
|
||||
newInputString = reader.result;
|
||||
}
|
||||
|
||||
this.setState({'inputString': newInputString});
|
||||
this.updateNextStatus(newInputString);
|
||||
},this,reader);
|
||||
|
||||
reader.readAsText(file);
|
||||
this.setState({'inputString': newInputString});
|
||||
this.updateNextStatus(newInputString);
|
||||
*/
|
||||
//console.log("handleUploadFiles", this.props.importContext, this.state, this);
|
||||
// this.props.importContext.setInputString(reader.result);
|
||||
this.updateTextAreaContent(reader.result, true);
|
||||
}, this);
|
||||
|
||||
reader.readAsText(file);
|
||||
} else {
|
||||
// Should this be removed?
|
||||
alert("Error: expecting a file as input.");
|
||||
}
|
||||
},
|
||||
|
||||
handleOnDrop: function(e) {
|
||||
e.preventDefault();
|
||||
handleOnDrop: function (anEvent) {
|
||||
anEvent.preventDefault();
|
||||
|
||||
this.handleUploadFiles(e.dataTransfer.files)
|
||||
this.handleUploadFiles(anEvent.dataTransfer.files)
|
||||
},
|
||||
|
||||
handleInputFiles: function(e) {
|
||||
e.preventDefault();
|
||||
handleInputFiles: function (anEvent) {
|
||||
anEvent.preventDefault();
|
||||
|
||||
this.handleUploadFiles(e.target.files)
|
||||
this.handleUploadFiles(anEvent.target.files)
|
||||
},
|
||||
|
||||
handleOnDragOver: function(e) {
|
||||
handleOnDragOver: function (anEvent) {
|
||||
// Somehow necessary:
|
||||
// http://enome.github.io/javascript/2014/03/24/drag-and-drop-with-react-js.html
|
||||
// https://code.google.com/p/chromium/issues/detail?id=168387
|
||||
// http://www.quirksmode.org/blog/archives/2009/09/the_html5_drag.html
|
||||
e.preventDefault();
|
||||
anEvent.preventDefault();
|
||||
},
|
||||
|
||||
handleTextareaChange: function() {
|
||||
var newInputString = this.refs['input-textarea'].getDOMNode().value;
|
||||
this.setState({'inputString': newInputString});
|
||||
this.updateNextStatus(newInputString);
|
||||
handleTextareaChange: function () {
|
||||
// var newInputString;
|
||||
//
|
||||
// newInputString = this.refs['input-textarea'].getDOMNode().value;
|
||||
// this.setState({'inputString': newInputString});
|
||||
// this.props.importContext.setInputString(newInputString);
|
||||
|
||||
this.updateTextAreaContent(this.refs['input-textarea'].getDOMNode().value, false);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
render: function() {
|
||||
return React.DOM.div({},[
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "You can import either CSV data, or Clipperz data exported in JSON"),
|
||||
]),
|
||||
React.DOM.form({'key':'form', 'className':'importForm' }, [
|
||||
React.DOM.input({
|
||||
'type': 'file',
|
||||
@@ -228,7 +248,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.InputClass = React.createClas
|
||||
'key':'input-textarea',
|
||||
'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.",
|
||||
'placeholder':"Copy or type your data here",
|
||||
'value': this.state.inputString,
|
||||
'onChange': this.handleTextareaChange,
|
||||
'onDragOver': this.handleOnDragOver,
|
||||
|
||||
@@ -27,57 +27,44 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createClass({
|
||||
|
||||
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
|
||||
var recordsToImport;
|
||||
|
||||
recordsToImport = MochiKit.Iter.reduce(
|
||||
function (acc, item) { acc[item['reference']] = item; return acc; },
|
||||
MochiKit.Base.filter(
|
||||
function (aRecord) { return !Clipperz.PM.DataModel.Record.labelContainsArchiveTag(aRecord['label']); },
|
||||
this.props.importContext.state('recordsToImport')
|
||||
),
|
||||
{}
|
||||
);
|
||||
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'importCards', filteredImportData);
|
||||
|
||||
return true;
|
||||
|
||||
this.props.importContext.setState('recordsToImport', MochiKit.Base.values(recordsToImport));
|
||||
|
||||
return {
|
||||
'recordsToImport': recordsToImport
|
||||
};
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
|
||||
|
||||
toggleRecordToImport: function(record) {
|
||||
var newRecordsToImport;
|
||||
var recordPosition;
|
||||
|
||||
newRecordsToImport = this.state.recordsToImport;
|
||||
recordPosition = newRecordsToImport.indexOf(record._importId);
|
||||
|
||||
if (recordPosition === -1) {
|
||||
newRecordsToImport.push(record._importId);
|
||||
|
||||
if (this.isRecordToImport(record)) {
|
||||
delete newRecordsToImport[record['reference']];
|
||||
} else {
|
||||
newRecordsToImport.splice(recordPosition,1);
|
||||
newRecordsToImport[record['reference']] = record;
|
||||
}
|
||||
|
||||
|
||||
this.setState({'recordsToImport': newRecordsToImport});
|
||||
this.props.importContext.setState('recordsToImport', MochiKit.Base.values(newRecordsToImport));
|
||||
},
|
||||
|
||||
|
||||
isRecordToImport: function(record) {
|
||||
return (this.state.recordsToImport.indexOf(record._importId)>=0) ? true : false;
|
||||
return (MochiKit.Base.keys(this.state.recordsToImport).indexOf(record['reference']) != -1) ? true : false;
|
||||
},
|
||||
|
||||
getTags: function (aTitle) {
|
||||
@@ -85,10 +72,10 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createCl
|
||||
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);
|
||||
|
||||
tagList = MochiKit.Base.filter(function(aTag) { return !Clipperz.PM.DataModel.Record.isSpecialTag(aTag); }, tagList);
|
||||
|
||||
if (tagList.length > 0) {
|
||||
result = React.DOM.ul({'className': 'tagList'},
|
||||
MochiKit.Base.map(function(aTag){
|
||||
@@ -102,50 +89,44 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.PreviewClass = React.createCl
|
||||
return result;
|
||||
},
|
||||
|
||||
renderCardFields: function(someFields) {
|
||||
return MochiKit.Base.map(function(key) {
|
||||
renderCardFields: function (someFields) {
|
||||
return MochiKit.Base.map(function (key) {
|
||||
var field = someFields[key];
|
||||
|
||||
return [
|
||||
React.DOM.dt({},field.label),
|
||||
React.DOM.dd({},field.value),
|
||||
React.DOM.dt({}, field['label']),
|
||||
React.DOM.dd({'className': field['actionType'] + (field['hidden'] ? ' password' : '')}, field['value']),
|
||||
];
|
||||
} ,MochiKit.Base.keys(someFields));
|
||||
}, MochiKit.Base.keys(someFields));
|
||||
},
|
||||
|
||||
renderCard: function(aCard) {
|
||||
var notesParagraph = (aCard.data.notes) ? React.DOM.p({'className': 'notes'}, aCard.data.notes) : null;
|
||||
return React.DOM.li({'className': 'card'}, [
|
||||
|
||||
renderCard: function (aCard) {
|
||||
var classes;
|
||||
|
||||
classes = {
|
||||
'card': true,
|
||||
'archived': Clipperz.PM.DataModel.Record.labelContainsArchiveTag(aCard['label'])
|
||||
}
|
||||
|
||||
return React.DOM.li({'className':Clipperz.PM.UI.Components.classNames(classes)}, [
|
||||
React.DOM.input({
|
||||
'type': 'checkbox',
|
||||
'checked': this.isRecordToImport(aCard),
|
||||
'onChange': MochiKit.Base.partial(this.toggleRecordToImport,aCard)
|
||||
'onChange': MochiKit.Base.partial(this.toggleRecordToImport, aCard)
|
||||
}),
|
||||
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
|
||||
React.DOM.div({'className': 'cardContent'}, [
|
||||
React.DOM.h3({}, Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aCard['label'])),
|
||||
this.getTags(aCard['label']),
|
||||
React.DOM.dl({'className': 'fields'}, this.renderCardFields(aCard['currentVersion']['fields'])),
|
||||
(aCard['data']['notes']) ? React.DOM.p({'className': 'notes'}, aCard['data']['notes']) : null
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var result;
|
||||
|
||||
if (typeof(this.state.jsonToImport)=='undefined' || !this.state.jsonToImport) {
|
||||
result = "Error";
|
||||
} else {
|
||||
var renderedPreview = React.DOM.ul({},
|
||||
MochiKit.Base.map(this.renderCard, this.state.jsonToImport)
|
||||
);
|
||||
|
||||
result =
|
||||
React.DOM.div({'className': 'jsonPreview'}, React.DOM.ul({},
|
||||
MochiKit.Base.map(this.renderCard, this.state.jsonToImport)
|
||||
) );
|
||||
}
|
||||
|
||||
return React.DOM.div({},result);
|
||||
}
|
||||
return React.DOM.div({'className': 'preview'},
|
||||
React.DOM.ul({}, MochiKit.Base.map(MochiKit.Base.method(this, 'renderCard'), this.props.importContext.state('jsonData')))
|
||||
);
|
||||
},
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -75,17 +75,19 @@ 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':'header'}, [
|
||||
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.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.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':'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.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.")
|
||||
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'id':'deleteAccountConfirmCheckbox', 'name':'confirm', 'ref':'confirm'}),
|
||||
React.DOM.label({'htmlFor':'deleteAccountConfirmCheckbox'}, "I understand that all my data will be deleted and that this action is not reversible.")
|
||||
]),
|
||||
]),
|
||||
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableDeleteAccountButton(), 'className':'button'}, "Delete my account")
|
||||
|
||||
@@ -35,7 +35,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.div({'className':'header'}, [
|
||||
React.DOM.h1({}, "Device PIN"),
|
||||
]),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.h3({}, this.props['PIN'])
|
||||
])
|
||||
|
||||
229
frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/OTP.js
Normal file
229
frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/OTP.js
Normal file
@@ -0,0 +1,229 @@
|
||||
/*
|
||||
|
||||
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.OTPClass = React.createClass({
|
||||
|
||||
// TODO: add print button!!!!
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
// 'selectedOTPs': [],
|
||||
'labelBeingEdited': null,
|
||||
'otpLabel': '',
|
||||
};
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
handleNew: function() {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'createNewOTP');
|
||||
},
|
||||
|
||||
handleDelete: function (anOtpReference) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'deleteOTPs', [anOtpReference]);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
enableOtpLabelEditing: function(anOTP) {
|
||||
var newState = this.state;
|
||||
|
||||
newState['labelBeingEdited'] = anOTP.reference();
|
||||
newState['otpLabel'] = anOTP.label();
|
||||
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
updateOtpLabel: function (anOTP, anEvent) {
|
||||
var newState = this.state;
|
||||
var newLabel = anEvent.target.value
|
||||
|
||||
newState['otpLabel'] = newLabel;
|
||||
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
handleKeyPressed: function (anOTP, anEvent) {
|
||||
switch (anEvent.keyCode) {
|
||||
case 9: // tab
|
||||
this.handleLabelSave(anOTP);
|
||||
// TODO: edit label of next OTP
|
||||
break;
|
||||
case 13: // enter
|
||||
this.handleLabelSave(anOTP);
|
||||
case 27: // escape
|
||||
this.handleLabelCancel(anOTP);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
handleLabelSave: function (anOTP) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'changeOTPLabel', anOTP.reference(), this.state['otpLabel']);
|
||||
this.handleLabelCancel()
|
||||
},
|
||||
|
||||
handleLabelCancel: function() {
|
||||
var newState;
|
||||
|
||||
newState = this.state;
|
||||
newState['labelBeingEdited'] = null;
|
||||
|
||||
this.setState(newState);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
handlePrint: function () {
|
||||
this.printOneTimePasswords();
|
||||
},
|
||||
|
||||
printOneTimePasswords: function () {
|
||||
var newWindow;
|
||||
|
||||
var filteredOtpList = MochiKit.Base.filter(MochiKit.Base.bind(function (anOTP) {
|
||||
return (this.props.userInfo.otpsDetails[anOTP.reference()]['status'] == 'ACTIVE');
|
||||
}, this), this.props.userInfo.otpList);
|
||||
|
||||
newWindow = window.open("", "");
|
||||
newWindow.document.write(
|
||||
'<!DOCTYPE html>' +
|
||||
'<html lang="en">' +
|
||||
'<head>' +
|
||||
'<meta charset="utf-8">' +
|
||||
'<title>Active One Time Passwords - Clipperz</title>' +
|
||||
'<style>' +
|
||||
'li { padding-bottom: 10px; }' +
|
||||
'li span { display: block; }' +
|
||||
'span.password { font-family: monospace; font-size: 16pt; padding-bottom: 5px; }' +
|
||||
'span.label { font-family: sans-serif; font-size: 12pt; }' +
|
||||
'</style>' +
|
||||
'</head>' +
|
||||
'<body>' +
|
||||
'<ul>' +
|
||||
MochiKit.Base.map(function (anOTP) {
|
||||
return '<li>' +
|
||||
'<span class="password">' + anOTP.password() + '</span>' +
|
||||
'<span class="label">' + anOTP.label() + '</span>' +
|
||||
'</li>';
|
||||
}, filteredOtpList).join('') +
|
||||
'</ul>' +
|
||||
'</body>' +
|
||||
'</html>'
|
||||
);
|
||||
|
||||
newWindow.document.close();
|
||||
newWindow.focus();
|
||||
newWindow.print();
|
||||
newWindow.close();
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
renderOtpRows: function() {
|
||||
var result;
|
||||
|
||||
if (this.props.userInfo.otpList) {
|
||||
result = MochiKit.Base.map(MochiKit.Base.bind(function (anOTP) {
|
||||
var reference = anOTP.reference();
|
||||
var otpDetailInfo = this.props.userInfo.otpsDetails[reference];
|
||||
var labelComponent;
|
||||
var otpStatusInfo;
|
||||
var otpClasses;
|
||||
var optLabel;
|
||||
|
||||
otpClasses = {
|
||||
'otpDetail': true,
|
||||
};
|
||||
otpClasses[otpDetailInfo['status']] = true;
|
||||
|
||||
if (otpDetailInfo['status'] != 'ACTIVE') {
|
||||
otpStatusInfo = React.DOM.div({'className':'otpStatusInfo'}, [
|
||||
React.DOM.span({'className':'otpStatus'}, otpDetailInfo['status']),
|
||||
React.DOM.span({'className':'requestDate'}, otpDetailInfo['requestDate']),
|
||||
React.DOM.span({'className':'connectionIp'}, otpDetailInfo['connection']['ip']),
|
||||
React.DOM.span({'className':'connectionBrowser'}, otpDetailInfo['connection']['browser']),
|
||||
])
|
||||
} else {
|
||||
otpStatusInfo = null;
|
||||
}
|
||||
|
||||
if (reference == this.state.labelBeingEdited) {
|
||||
labelComponent = React.DOM.input({
|
||||
'autoFocus':true,
|
||||
'value':this.state.otpLabel,
|
||||
'onChange':MochiKit.Base.partial(this.updateOtpLabel, anOTP),
|
||||
'onKeyDown':MochiKit.Base.partial(this.handleKeyPressed, anOTP),
|
||||
});
|
||||
} else {
|
||||
labelComponent = React.DOM.span({'onClick':MochiKit.Base.partial(this.enableOtpLabelEditing, anOTP)}, (anOTP.label()) ? anOTP.label() : "---")
|
||||
}
|
||||
|
||||
return React.DOM.li({
|
||||
'key':'otp-' + reference,
|
||||
'className':Clipperz.PM.UI.Components.classNames(otpClasses)
|
||||
}, [
|
||||
React.DOM.div({'className':'otpAction'}, [
|
||||
React.DOM.a({'onClick':MochiKit.Base.partial(this.handleDelete, reference)}, 'remove OTP'),
|
||||
]),
|
||||
React.DOM.div({'className':'otpInfo'}, [
|
||||
React.DOM.div({'className':'otpPassword'}, anOTP.password()),
|
||||
React.DOM.div({'className':'otpLabel'}, labelComponent),
|
||||
otpStatusInfo,
|
||||
]),
|
||||
]);
|
||||
}, this), this.props.userInfo.otpList);
|
||||
} else {
|
||||
result = React.DOM.li({}, React.DOM.div({}, "..."));
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({'className':'extraFeature OTP'}, [
|
||||
React.DOM.div({'className':'header'}, [
|
||||
React.DOM.h1({}, "One Time Passwords"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "A one-time passphrase works like your regular passphrase, but can be used only once. This makes it expecially useful for using it in places where keyloggers may be installed."),
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.handlePrint}, "Print")
|
||||
]),
|
||||
React.DOM.div({'className':'content'}, [
|
||||
React.DOM.ul({'className':'otpList'}, this.renderOtpRows()),
|
||||
React.DOM.div({'className':'actions'}, [
|
||||
React.DOM.a({'onClick': this.handleNew}, "create new OTP"),
|
||||
]),
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.OTP = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.OTPClass);
|
||||
@@ -100,7 +100,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.PassphraseClass = React.createClass({
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature passphrase'}, [
|
||||
React.DOM.h1({}, "Change Passphrase"),
|
||||
React.DOM.div({'className':'header'}, [
|
||||
React.DOM.h1({}, "Change Passphrase"),
|
||||
]),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.form({'key':'form', 'className':'changePassphraseForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleChangePassphrase}, [
|
||||
React.DOM.div({'key':'fields'},[
|
||||
@@ -117,8 +119,8 @@ Clipperz.PM.UI.Components.ExtraFeatures.PassphraseClass = React.createClass({
|
||||
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.input({'key':'confirm', 'id':'changePassphraseConfirmCheckbox', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
|
||||
React.DOM.label({'htmlFor':'changePassphraseConfirmCheckbox'}, "I understand that Clipperz will not be able to help me recovering a lost passphrase.")
|
||||
]),
|
||||
]),
|
||||
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableChangePassphraseButton(), 'className':'button'}, "Change passphrase"),
|
||||
|
||||
@@ -34,16 +34,16 @@ Clipperz.PM.UI.Components.Pages.MainPageClass = React.createClass({
|
||||
},
|
||||
|
||||
propTypes: {
|
||||
'tags': React.PropTypes.object,
|
||||
'allTags': React.PropTypes.array,
|
||||
'messageBox': React.PropTypes.object.isRequired,
|
||||
'featureSet': React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
|
||||
'features': React.PropTypes.array.isRequired,
|
||||
'userInfo': React.PropTypes.object.isRequired,
|
||||
'accountInfo': React.PropTypes.object.isRequired,
|
||||
// 'mediaQueryStyle': React.PropTypes.oneOf(['extra-short', 'narrow', 'wide', 'extra-wide']).isRequired,
|
||||
'tags': React.PropTypes.object,
|
||||
'allTags': React.PropTypes.array,
|
||||
'messageBox': React.PropTypes.object.isRequired,
|
||||
'featureSet': React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
|
||||
'features': React.PropTypes.array.isRequired,
|
||||
'userInfo': React.PropTypes.object.isRequired,
|
||||
'accountInfo': React.PropTypes.object.isRequired,
|
||||
'style': React.PropTypes.oneOf(Clipperz_PM_UI_availableStyles).isRequired,
|
||||
// 'cards': React.PropTypes.deferred.isRequired
|
||||
// 'mediaQueryStyle': React.PropTypes.oneOf(['extra-short', 'narrow', 'wide', 'extra-wide']).isRequired,
|
||||
// 'cards': React.PropTypes.deferred.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function () {
|
||||
@@ -60,8 +60,7 @@ Clipperz.PM.UI.Components.Pages.MainPageClass = React.createClass({
|
||||
};
|
||||
classes[this.props['style']] = true;
|
||||
|
||||
//console.log("MAIN PAGE", this.props['showGlobalMask']);
|
||||
return React.DOM.div({'key':'mainPage', 'className':Clipperz.PM.UI.Components.classNames(classes)/*Clipperz.PM.UI.Components.classNames(classes)*/}, [
|
||||
return React.DOM.div({'key':'mainPage', 'className':Clipperz.PM.UI.Components.classNames(classes)}, [
|
||||
this.props['style'] != 'extra-wide' ? Clipperz.PM.UI.Components.Panels.SelectionPanel(this.props) : null,
|
||||
Clipperz.PM.UI.Components.Panels.MainPanel(this.props),
|
||||
Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanel(this.props),
|
||||
|
||||
@@ -26,6 +26,10 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.Panels');
|
||||
|
||||
Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
|
||||
componentDidMount: function () {
|
||||
MochiKit.Signal.connect(Clipperz.Signal.NotificationCenter, 'closeSettingsPanel', MochiKit.Base.method(this, 'hideExtraFeatureContent'));
|
||||
},
|
||||
|
||||
settingsToggleHandler: function (anEvent) {
|
||||
//console.log("settingsToggleHandler");
|
||||
this.hideExtraFeatureContent();
|
||||
@@ -40,6 +44,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
'accountInfo': React.PropTypes.object.isRequired,
|
||||
'userInfo': React.PropTypes.object.isRequired
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
@@ -81,6 +86,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
},
|
||||
|
||||
extraFeaturesProps: function () {
|
||||
// console.log("ExtraFeaturesPanel, extraFeaturesProps:",this.props);
|
||||
return this.props;
|
||||
},
|
||||
|
||||
@@ -95,10 +101,16 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
},
|
||||
|
||||
showExtraFeatureContent: function (aComponent, aComponentName) {
|
||||
// console.log("ExtraFeaturesPanel, showExtraFeatureContent")
|
||||
if (aComponentName == 'OTP') {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'updateOTPListAndDetails');
|
||||
}
|
||||
|
||||
this.setState({
|
||||
'isFullyOpen':true,
|
||||
'extraFeatureComponentName': aComponentName,
|
||||
'extraFeatureContent': aComponent(this.extraFeaturesProps())
|
||||
// 'extraFeatureContent': aComponent(this.extraFeaturesProps()),
|
||||
'extraFeatureContentComponent': aComponent // Trying to instantiate the component at every render
|
||||
});
|
||||
},
|
||||
|
||||
@@ -124,16 +136,17 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.ul({'key':'accountUL'}, [
|
||||
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'}, "Change your account passphrase.")
|
||||
])
|
||||
// React.DOM.div({'key':'account_1_div'}, [
|
||||
// React.DOM.p({'key':'account_1_p'}, "Change your account passphrase.")
|
||||
// ])
|
||||
]),
|
||||
React.DOM.li({'key':'account_2'}, [
|
||||
React.DOM.li({'key':'account_2', 'onClick':this.toggleExtraFeatureComponent('OTP')}, [
|
||||
React.DOM.h2({}, "One Time Passwords"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "")
|
||||
])
|
||||
// React.DOM.div({}, [
|
||||
// React.DOM.p({}, "Manage your OTPs.")
|
||||
// ])
|
||||
]),
|
||||
/*
|
||||
React.DOM.li({'key':'account_3', 'onClick':this.toggleExtraFeatureComponent('DevicePIN')}, [
|
||||
React.DOM.h2({}, "Device PIN"),
|
||||
React.DOM.div({}, [
|
||||
@@ -146,14 +159,16 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.p({}, "")
|
||||
])
|
||||
]),
|
||||
*/
|
||||
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({}, "Delete your account for good.")
|
||||
])
|
||||
// React.DOM.div({}, [
|
||||
// React.DOM.p({}, "Delete your account for good.")
|
||||
// ])
|
||||
])
|
||||
])
|
||||
]),
|
||||
/*
|
||||
React.DOM.li({'key':'subscription', 'className':this.state['index']['subscription'] ? 'open' : 'closed'}, [
|
||||
React.DOM.h1({'onClick':this.toggleIndexState('subscription')}, "Subscription"),
|
||||
React.DOM.ul({'key':'subscription'}, [
|
||||
@@ -183,6 +198,7 @@ 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'}, [
|
||||
@@ -195,22 +211,24 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
// ]),
|
||||
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({}, "CSV, JSON, …")
|
||||
])
|
||||
// React.DOM.div({}, [
|
||||
// React.DOM.p({}, "CSV, JSON, …")
|
||||
// ])
|
||||
]),
|
||||
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({}, "Offline copy, printable version, JSON, …")
|
||||
])
|
||||
// React.DOM.div({}, [
|
||||
// React.DOM.p({}, "Offline copy, printable version, JSON, …")
|
||||
// ])
|
||||
]),
|
||||
/*
|
||||
React.DOM.li({'key':'data_4'}, [
|
||||
React.DOM.h2({}, "Sharing"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "Securely share cards with other users")
|
||||
])
|
||||
])
|
||||
*/
|
||||
])
|
||||
])
|
||||
])
|
||||
@@ -227,7 +245,8 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.header({}, [
|
||||
React.DOM.div({'className':'button', 'onClick':this.hideExtraFeatureContent}, "close")
|
||||
]),
|
||||
this.state['extraFeatureContent']
|
||||
// this.state['extraFeatureContent']
|
||||
this.state['extraFeatureContentComponent'] ? this.state['extraFeatureContentComponent'](this.props) : null
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -242,7 +261,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
'open': isOpen,
|
||||
'fullOpen': isFullyOpen
|
||||
}
|
||||
|
||||
|
||||
return React.DOM.div({'key':'extraFeaturesPanel', 'id':'extraFeaturesPanel', 'className':Clipperz.PM.UI.Components.classNames(classes)}, [
|
||||
this.renderIndex(),
|
||||
this.renderContent(),
|
||||
|
||||
@@ -41,6 +41,11 @@ Clipperz.PM.UI.ExportController = function(args) {
|
||||
"padding: 10px;" +
|
||||
"border-bottom: 2px solid black;" +
|
||||
"}" +
|
||||
|
||||
"header p span {" +
|
||||
// "padding: 0px 4px;" +
|
||||
"font-weight: bold;" +
|
||||
"}" +
|
||||
|
||||
"h1 {" +
|
||||
"margin: 0px;" +
|
||||
@@ -102,6 +107,7 @@ Clipperz.PM.UI.ExportController = function(args) {
|
||||
"margin: 0px;" +
|
||||
"margin-bottom: 5px;" +
|
||||
"padding-left: 10px;" +
|
||||
"font-size: 13pt;" +
|
||||
"}" +
|
||||
|
||||
"div > div {" +
|
||||
@@ -110,9 +116,20 @@ Clipperz.PM.UI.ExportController = function(args) {
|
||||
"padding: 10px;" +
|
||||
"}" +
|
||||
|
||||
"li p, dd.hidden {" +
|
||||
"white-space: pre-wrap;" +
|
||||
"word-wrap: break-word;" +
|
||||
"font-family: monospace;" +
|
||||
"}" +
|
||||
|
||||
"textarea {" +
|
||||
"width: 100%;" +
|
||||
"height: 200px;" +
|
||||
"display: none" +
|
||||
// "width: 100%;" +
|
||||
// "height: 200px;" +
|
||||
"}" +
|
||||
|
||||
"a {" +
|
||||
"color: white;" +
|
||||
"}" +
|
||||
|
||||
"@media print {" +
|
||||
@@ -120,6 +137,10 @@ Clipperz.PM.UI.ExportController = function(args) {
|
||||
"display: none !important;" +
|
||||
"}" +
|
||||
|
||||
"div > ul > li.archived {" +
|
||||
"color: #ddd;" +
|
||||
"}" +
|
||||
|
||||
"ul > li {" +
|
||||
"page-break-inside: avoid;" +
|
||||
"} " +
|
||||
@@ -175,9 +196,11 @@ MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
|
||||
MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.DL({},
|
||||
MochiKit.Base.map(function(key) {
|
||||
var isHiddenField = jsonCardData.currentVersion.fields[key]['hidden'];
|
||||
|
||||
return [
|
||||
MochiKit.DOM.DT(jsonCardData.currentVersion.fields[key].label),
|
||||
MochiKit.DOM.DD(jsonCardData.currentVersion.fields[key].value),
|
||||
MochiKit.DOM.DT({}, jsonCardData.currentVersion.fields[key]['label']),
|
||||
MochiKit.DOM.DD((isHiddenField ? {'class':'hidden'} : {}), jsonCardData.currentVersion.fields[key]['value']),
|
||||
];
|
||||
}, MochiKit.Base.keys(jsonCardData.currentVersion.fields))
|
||||
)
|
||||
@@ -187,22 +210,30 @@ MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
|
||||
},
|
||||
|
||||
'renderToHtml': function (jsonData) {
|
||||
var title;
|
||||
var style;
|
||||
var date;
|
||||
var body;
|
||||
var title;
|
||||
var style;
|
||||
var now;
|
||||
var dateString;
|
||||
var timeString
|
||||
var body;
|
||||
|
||||
title = "Clipperz data";
|
||||
style = this._style;
|
||||
date = "dd/mm/yyyy";
|
||||
now = new XDate();
|
||||
dateString = now.toString("MMM d, yyyy");
|
||||
timeString = now.toString("HH:mm");
|
||||
|
||||
body = MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.HEADER({},
|
||||
MochiKit.DOM.H1({}, "Your data on Clipperz"),
|
||||
MochiKit.DOM.H5({}, "Export date: " + date),
|
||||
MochiKit.DOM.H5({}, "Export generated on " + dateString + " at " + timeString),
|
||||
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({}, "Security warning - This file lists the content of all your cards in a printer-friendly format"),
|
||||
MochiKit.DOM.P({}, [
|
||||
"Beware: ",
|
||||
MochiKit.DOM.SPAN({'class':'warning'}, "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.")
|
||||
)
|
||||
@@ -210,31 +241,33 @@ MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
|
||||
|
||||
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.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. - ",
|
||||
"This file has been downloaded from ",
|
||||
MochiKit.DOM.A({'href':'https://clipperz.is'} ,"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")
|
||||
)
|
||||
// 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>';
|
||||
return '<html><head><title>' + title + '</title><style type="text/css">' + style + '</style><meta http-equiv="Content-Type" content="text/html; charset=utf-8" /></head><body>' + MochiKit.DOM.toHTML(body) + '</body></html>';
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
@@ -24,125 +24,422 @@ refer to http://www.clipperz.com.
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI');
|
||||
|
||||
Clipperz.PM.UI.ImportContext = function(args) {
|
||||
Clipperz.PM.UI.ImportContext = function(anInputComponent) {
|
||||
this._importComponent = anInputComponent;
|
||||
|
||||
this._status = {
|
||||
'inputString': '',
|
||||
'isInputStringValid': false,
|
||||
'inputFormat': 'UNDEFINED',
|
||||
'currentStep': 'Input',
|
||||
};
|
||||
|
||||
this.inputString = null;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
MochiKit.Base.update(Clipperz.PM.UI.ImportContext.prototype, {
|
||||
|
||||
'toString': function() {
|
||||
toString: function() {
|
||||
return "Clipperz.PM.UI.ImportContext";
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'resetContext': function() {
|
||||
delete this.inputString;
|
||||
delete this.format;
|
||||
delete this.jsonToImport;
|
||||
delete this.recordsToImport;
|
||||
release: function () {
|
||||
this._importComponent = null;
|
||||
},
|
||||
|
||||
'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;
|
||||
//=============================================================================
|
||||
|
||||
ensureStateConsistency: function () {
|
||||
var csvData;
|
||||
|
||||
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;
|
||||
csvData = this._status['csvData'];
|
||||
if (csvData != null) {
|
||||
if (csvData['titleIndex'] == csvData['notesIndex']) {
|
||||
csvData['notesIndex'] = null;
|
||||
}
|
||||
|
||||
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);
|
||||
csvData['hiddenFields'][csvData['titleIndex']] = false;
|
||||
csvData['hiddenFields'][csvData['notesIndex']] = false;
|
||||
}
|
||||
},
|
||||
|
||||
updateImportComponent: function () {
|
||||
this._importComponent.setState({'importContext': this});
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
state: function (aKeyPath) {
|
||||
var result;
|
||||
var keys;
|
||||
var i, c;
|
||||
|
||||
result = this._status;
|
||||
keys = aKeyPath.split('.');
|
||||
c = keys.length;
|
||||
|
||||
for (i=0; i<c; i++) {
|
||||
result = result[keys[i]];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
setState: function (aKeyPath, aValue) {
|
||||
var object;
|
||||
var keys;
|
||||
var i, c;
|
||||
|
||||
object = this._status;
|
||||
keys = aKeyPath.split('.');
|
||||
c = keys.length - 1;
|
||||
|
||||
for (i=0; i<c; i++) {
|
||||
object = object[keys[i]];
|
||||
}
|
||||
object[keys[c]] = aValue;
|
||||
|
||||
this.ensureStateConsistency();
|
||||
this.updateImportComponent();
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
stepStatus: function (aStep) {
|
||||
var result;
|
||||
|
||||
if (aStep == this.currentStep()) {
|
||||
result = 'active';
|
||||
} else {
|
||||
result = 'disabled';
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
currentStep: function () {
|
||||
return this.state('currentStep');
|
||||
},
|
||||
|
||||
setCurrentStep: function (aValue) {
|
||||
this.setState('currentStep', aValue);
|
||||
},
|
||||
|
||||
currentStepIndex: function () {
|
||||
return MochiKit.Base.findValue(this.steps(), this.currentStep());
|
||||
},
|
||||
|
||||
steps: function () {
|
||||
var result;
|
||||
|
||||
if (this.inputFormat() == 'JSON') {
|
||||
result = ['Input', 'Preview', 'Import'];
|
||||
} else if (this.inputFormat() == 'CSV') {
|
||||
result = ['Input', 'CSV.Columns', 'CSV.Labels', 'CSV.Titles', 'CSV.Notes', 'CSV.Hidden', 'Preview', 'Import'];
|
||||
} else {
|
||||
result = ['Input'];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
inputFormat: function () {
|
||||
return this.state('inputFormat');
|
||||
},
|
||||
|
||||
setInputFormat: function (aValue) {
|
||||
this.setState('inputFormat', aValue);
|
||||
|
||||
if (aValue == 'UNDEFINED') {
|
||||
this.setIsInputStringValid(false);
|
||||
} else {
|
||||
this.setIsInputStringValid(true);
|
||||
}
|
||||
},
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
isInputStringValid: function () {
|
||||
return this.state('isInputStringValid');
|
||||
},
|
||||
|
||||
setIsInputStringValid: function (aValue) {
|
||||
this.setState('isInputStringValid', aValue);
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
showJsonPreview: function (jsonData) {
|
||||
MochiKit.Async.callLater(0.1, MochiKit.Base.method(this, 'setCurrentStep', 'Preview'));
|
||||
},
|
||||
|
||||
setJsonData: function (someData) {
|
||||
if (someData != null) {
|
||||
this.setInputFormat('JSON');
|
||||
}
|
||||
this.setState('jsonData', someData);
|
||||
// TODO: before setting 'recordsToImport', filter 'someData' to remove cards marked as ARCHIVED
|
||||
this.setState('recordsToImport', someData);
|
||||
},
|
||||
|
||||
enhanceJsonDataWithCardReferences: function (someJsonData) {
|
||||
return MochiKit.Base.map(function (item) {
|
||||
item['reference'] = Clipperz.PM.Crypto.randomKey();
|
||||
item['label'] = "COPY - " + item['label'];
|
||||
return item;
|
||||
}, someJsonData);
|
||||
},
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
startCsvWizard: function (csvData) {
|
||||
MochiKit.Async.callLater(0.1, MochiKit.Base.method(this, 'setCurrentStep', 'CSV.Columns'));
|
||||
},
|
||||
|
||||
setCsvData: function (someData) {
|
||||
if (someData != null) {
|
||||
this.setInputFormat('CSV');
|
||||
|
||||
this.setState('csvData', {
|
||||
'data': someData,
|
||||
'selectedColumns': MochiKit.Base.map(function () { return true; }, someData[0]),
|
||||
'labels': MochiKit.Base.map(function () { return ""; }, someData[0]),
|
||||
'useFirstRowAsLabels': false,
|
||||
'titleIndex': null,
|
||||
'notesIndex': null,
|
||||
'hiddenFields': MochiKit.Base.map(function () { return false; }, someData[0]),
|
||||
});
|
||||
} else {
|
||||
this.setState('csvData', null);
|
||||
}
|
||||
},
|
||||
|
||||
csvFillEmptyCells: function(table) {
|
||||
var result = [];
|
||||
var i,j;
|
||||
|
||||
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] : "";
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof(this.recordsToImport) == 'undefined') {
|
||||
recordsToImport = MochiKit.Base.map(function(r){return r._importId},jsonToImport);
|
||||
} else {
|
||||
recordsToImport = this.recordsToImport;
|
||||
}
|
||||
|
||||
return {
|
||||
'jsonToImport': jsonToImport,
|
||||
'recordsToImport': recordsToImport
|
||||
};
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
|
||||
//............................................................................
|
||||
|
||||
'createJsonDataFromCSV': function(csvData) {
|
||||
return MochiKit.Base.map(function (row) {
|
||||
var fields;
|
||||
|
||||
fields = MochiKit.Base.map(function (cellInfo) {
|
||||
return {
|
||||
'label': csvData['labels'][cellInfo[0]],
|
||||
'value': cellInfo[1],
|
||||
'hidden': csvData['hiddenFields'][cellInfo[0]],
|
||||
}
|
||||
}, MochiKit.Base.filter(function (cellInfo) {
|
||||
return ((csvData['titleIndex'] != cellInfo[0]) && (csvData['notesIndex'] != cellInfo[0]) && (csvData['selectedColumns'][cellInfo[0]]));
|
||||
}, Clipperz.Base.zipWithRange(row))
|
||||
);
|
||||
|
||||
return {
|
||||
'reference': Clipperz.PM.Crypto.randomKey(),
|
||||
'label': row[csvData['titleIndex']],
|
||||
'data': {
|
||||
'notes': ((csvData['notesIndex'] != null) ? row[csvData['notesIndex']] : "")
|
||||
},
|
||||
'currentVersion': {
|
||||
'fields': MochiKit.Iter.reduce(function (accumulator, field) {
|
||||
accumulator[Clipperz.PM.Crypto.randomKey()] = field; return accumulator;
|
||||
}, fields, {})
|
||||
}
|
||||
};
|
||||
}, (csvData['useFirstRowAsLabels']) ? csvData['data'].slice(1) : csvData['data']);
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
inputString: function () {
|
||||
return this.state('inputString');
|
||||
},
|
||||
|
||||
setInputString: function (aValue, isUploadingFile) {
|
||||
var textarea;
|
||||
var result;
|
||||
|
||||
result = aValue;
|
||||
this.setInputFormat('UNDEFINED');
|
||||
this.setJsonData(null);
|
||||
this.setCsvData(null);
|
||||
|
||||
if (isUploadingFile) {
|
||||
var isExportContent;
|
||||
|
||||
isExportContent = new RegExp('.*<textarea>(.*)<\/textarea>.*', 'g');
|
||||
if (isExportContent.test(aValue)) {
|
||||
textarea = MochiKit.DOM.TEXTAREA();
|
||||
textarea.innerHTML = aValue.replace(isExportContent, '$1');
|
||||
result = textarea.innerHTML;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var jsonData;
|
||||
|
||||
jsonData = JSON.parse(result);
|
||||
jsonData = this.enhanceJsonDataWithCardReferences(jsonData);
|
||||
|
||||
this.setJsonData(jsonData);
|
||||
if (isUploadingFile == true) {
|
||||
this.showJsonPreview();
|
||||
}
|
||||
} catch(e) {
|
||||
var parsedCsv;
|
||||
|
||||
parsedCsv = Papa.parse(result);
|
||||
if (parsedCsv.errors.length == 0) {
|
||||
var csvData;
|
||||
csvData = this.csvFillEmptyCells(parsedCsv.data);
|
||||
|
||||
this.setCsvData(csvData);
|
||||
if (isUploadingFile == true) {
|
||||
this.startCsvWizard(csvData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.setState('inputString', result);
|
||||
return result;
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
backButtonStatus: function () {
|
||||
var result;
|
||||
|
||||
result = 'DISABLED';
|
||||
if (this.currentStepIndex() > 0) {
|
||||
result = 'ENABLED';
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//.............................................................................
|
||||
|
||||
forwardButtonStatus: function () {
|
||||
var result;
|
||||
|
||||
result = 'DISABLED';
|
||||
|
||||
if (this.currentStep() == 'Input') {
|
||||
if (this.isInputStringValid()) {
|
||||
result = 'ENABLED';
|
||||
}
|
||||
} else if (this.currentStep() == 'Preview') {
|
||||
if (this.state('recordsToImport').length > 0) {
|
||||
result = 'ENABLED';
|
||||
}
|
||||
} else if (this.currentStep() == 'CSV.Columns') {
|
||||
if (MochiKit.Iter.some(this.state('csvData.selectedColumns'), MochiKit.Base.operator.identity)) {
|
||||
result = 'ENABLED';
|
||||
}
|
||||
} else if (this.currentStep() == 'CSV.Labels') {
|
||||
var selectedColumns = this.state('csvData.selectedColumns');
|
||||
if (MochiKit.Iter.every(Clipperz.Base.zipWithRange(this.state('csvData.labels')), function (labelInfo) { return (Clipperz.Base.trim(labelInfo[1]).length > 0) || (selectedColumns[labelInfo[0]] == false)})) {
|
||||
result = 'ENABLED';
|
||||
}
|
||||
} else if (this.currentStep() == 'CSV.Titles') {
|
||||
if ((this.state('csvData.titleIndex') != null) && (this.state('csvData.selectedColumns')[this.state('csvData.titleIndex')] == true)) {
|
||||
result = 'ENABLED';
|
||||
}
|
||||
} else if (this.currentStep() == 'CSV.Notes') {
|
||||
result = 'ENABLED';
|
||||
} else if (this.currentStep() == 'CSV.Hidden') {
|
||||
result = 'ENABLED';
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
goBackHandler: function () {
|
||||
return MochiKit.Base.bind(function (anEvent) {
|
||||
if (this.backButtonStatus() == 'ENABLED') {
|
||||
this.goBack();
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
goForwardHandler: function () {
|
||||
return MochiKit.Base.bind(function (anEvent) {
|
||||
if (this.forwardButtonStatus() == 'ENABLED') {
|
||||
this.goForward();
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
goBack: function () {
|
||||
this.setCurrentStep(this.steps()[this.currentStepIndex() - 1]);
|
||||
},
|
||||
|
||||
goForward: function () {
|
||||
if (this.currentStep() == 'CSV.Hidden') {
|
||||
var jsonData;
|
||||
|
||||
jsonData = this.createJsonDataFromCSV(this.state('csvData'));
|
||||
this.setState('jsonData', jsonData);
|
||||
this.setState('recordsToImport', jsonData);
|
||||
}
|
||||
|
||||
this.setCurrentStep(this.steps()[this.currentStepIndex() + 1]);
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
renderCsvTableBody: function (hideDeselectedColumns) {
|
||||
var importContext = this;
|
||||
|
||||
return React.DOM.tbody({}, MochiKit.Base.map(function (rowInfo) {
|
||||
var result;
|
||||
var rowIndex = rowInfo[0];
|
||||
var row = rowInfo[1]
|
||||
|
||||
result = React.DOM.tr({'key': 'csv-row-' + rowIndex},
|
||||
MochiKit.Base.map(function (cellInfo) {
|
||||
var result;
|
||||
var columnIndex = cellInfo[0];
|
||||
var columnValue = cellInfo[1];
|
||||
|
||||
if (importContext.state('csvData.selectedColumns')[columnIndex] || !hideDeselectedColumns) {
|
||||
result = React.DOM.td({
|
||||
'key':'csv-cell-' + rowIndex + '-' + columnIndex,
|
||||
'className':(importContext.state('csvData.hiddenFields')[columnIndex]) ? 'PASSWORD' : null
|
||||
}, columnValue);
|
||||
} else{
|
||||
result = null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, Clipperz.Base.zipWithRange(row))
|
||||
);
|
||||
|
||||
return result;
|
||||
}, Clipperz.Base.zipWithRange((importContext.state('csvData.useFirstRowAsLabels')) ? importContext.state('csvData.data').slice(1) : importContext.state('csvData.data'))))
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
__syntaxFix__: "syntax fix"
|
||||
});
|
||||
|
||||
@@ -63,6 +63,7 @@ Clipperz.PM.UI.MainController = function() {
|
||||
this.registerForNotificationCenterEvents([
|
||||
'doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack',
|
||||
'changePassphrase', 'deleteAccount',
|
||||
'updateOTPListAndDetails', 'createNewOTP', 'deleteOTPs', 'changeOTPLabel',
|
||||
// 'export',
|
||||
'importCards',
|
||||
'downloadExport',
|
||||
@@ -122,6 +123,16 @@ MochiKit.Base.update(Clipperz.PM.UI.MainController.prototype, {
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
/*
|
||||
proxy: function () {
|
||||
return this._proxy;
|
||||
},
|
||||
|
||||
setProxy: function (aValue) {
|
||||
this._proxy = aValue;
|
||||
},
|
||||
*/
|
||||
//=========================================================================
|
||||
|
||||
isOnline: function() {
|
||||
return navigator.onLine;
|
||||
@@ -246,10 +257,8 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
var canRegisterNewUsers;
|
||||
|
||||
canRegisterNewUsers = Clipperz.PM.Proxy.defaultProxy.canRegisterNewUsers();
|
||||
//console.log("CAN REGISTER NEW USERS", canRegisterNewUsers);
|
||||
this.selectInitialProxy();
|
||||
shouldShowRegistrationForm = parameters['shouldShowRegistrationForm'] && canRegisterNewUsers;
|
||||
// this.pages()['loginPage'].setProps({'mode':this.loginMode(), 'isNewUserRegistrationAvailable':canRegisterNewUsers});
|
||||
|
||||
this.showLoginForm();
|
||||
if (shouldShowRegistrationForm) {
|
||||
@@ -262,7 +271,7 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
checkPassphrase: function (passphraseIn) {
|
||||
checkPassphrase: function( passphraseIn ) {
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.checkPassphrase", {trace: false});
|
||||
@@ -931,13 +940,12 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
//.........................................................................
|
||||
|
||||
userInfo: function() {
|
||||
userInfo: function () {
|
||||
var result;
|
||||
|
||||
result = {
|
||||
'checkPassphraseCallback': MochiKit.Base.bind(this.checkPassphrase,this)
|
||||
};
|
||||
result = {};
|
||||
|
||||
result['checkPassphraseCallback'] = MochiKit.Base.bind(this.checkPassphrase,this);
|
||||
if (this.user() != null) {
|
||||
result['username'] = this.user().username();
|
||||
}
|
||||
@@ -1116,6 +1124,9 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
toggleSettingsPanel_handler: function (anEvent) {
|
||||
this._isSettingsPanelOpen = !this._isSettingsPanelOpen;
|
||||
this.setCloseMaskAction(MochiKit.Base.method(this, 'toggleSettingsPanel_handler'));
|
||||
if (this._isSettingsPanelOpen == false) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'closeSettingsPanel');
|
||||
}
|
||||
this.refreshCurrentPage();
|
||||
},
|
||||
|
||||
@@ -1246,7 +1257,7 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
|
||||
// export_handler: function(exportType) {
|
||||
// return Clipperz.PM.UI.ExportController.exportJSON( this.recordsInfo(), exportType );
|
||||
// },
|
||||
@@ -1274,14 +1285,14 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
var deferredResult;
|
||||
var getPassphraseDelegate;
|
||||
var user;
|
||||
|
||||
|
||||
getPassphraseDelegate = MochiKit.Base.partial(MochiKit.Async.succeed, newPassphrase);
|
||||
user = new Clipperz.PM.DataModel.User({'username':this.user().username(), 'getPassphraseFunction':getPassphraseDelegate});
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.changePassphrase_handler", {trace: false});
|
||||
// deferredResult.addMethod(currentPage, 'setProps', {'showGlobalMask':true});
|
||||
deferredResult.addMethod(this.overlay(), 'show', "changing …", true);
|
||||
deferredResult.addMethod(this.user(), 'changePassphrase', newPassphrase);
|
||||
deferredResult.addMethod(this.user(), 'changePassphrase', getPassphraseDelegate);
|
||||
deferredResult.addMethod(user, 'login');
|
||||
deferredResult.addMethod(this, 'setUser', user);
|
||||
// deferredResult.addMethod(currentPage, 'setProps', {'mode':'view', 'showGlobalMask':false});
|
||||
@@ -1290,12 +1301,12 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
},
|
||||
|
||||
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?",
|
||||
@@ -1313,23 +1324,22 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
|
||||
importCards_handler: function(data) {
|
||||
return Clipperz.Async.callbacks("MainController.importCards_handler", [
|
||||
MochiKit.Base.method(this.overlay(), 'show', "importing …", true),
|
||||
function() { return data; },
|
||||
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this, function(recordData) {
|
||||
var newRecord;
|
||||
// I have the feeling this should be done in a more elegant way
|
||||
return Clipperz.Async.callbacks("MainController.importCards_handler-newRecord", [
|
||||
MochiKit.Base.method(this.user(), 'createNewRecord'),
|
||||
function (aValue) {
|
||||
newRecord = aValue;
|
||||
return newRecord;
|
||||
},
|
||||
MochiKit.Base.methodcaller('setUpWithJSON', recordData),
|
||||
])
|
||||
})),
|
||||
MochiKit.Base.partial(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'toggleSettingsPanel'),
|
||||
// MochiKit.Base.method(this.pages()[this.currentPage()], 'setProps', {'mode':'view', 'showGlobalMask':false}),
|
||||
function () { return data; },
|
||||
MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.method(this.user(), 'createNewRecordFromJSON')),
|
||||
|
||||
// MochiKit.Base.partial(MochiKit.Base.map, MochiKit.Base.bind(function (recordData) {
|
||||
// return Clipperz.Async.callbacks("MainController.importCards_handler-newRecord", [
|
||||
// MochiKit.Base.method(this.user(), 'createNewRecord'),
|
||||
// MochiKit.Base.methodcaller('setUpWithJSON', recordData),
|
||||
// ], {trace:false})
|
||||
// }, this)),
|
||||
|
||||
Clipperz.Async.collectAll,
|
||||
MochiKit.Base.method(this.user(), 'saveChanges'),
|
||||
MochiKit.Base.partial(MochiKit.Base.method(this, 'resetRecordsInfo')),
|
||||
@@ -1339,6 +1349,61 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
updateOTPListAndDetails: function() {
|
||||
|
||||
return Clipperz.Async.callbacks("MainController.updateOTPListAndDetails", [
|
||||
Clipperz.Async.collectResults("User.updateOTPListAndDetails <inner results>", {
|
||||
'userInfo': MochiKit.Base.method(this, 'userInfo'),
|
||||
'otpDetails': Clipperz.Async.collectResults("User.updateOTPListAndDetails <otpDetails>", {
|
||||
'otpList': MochiKit.Base.method(this.user(),'getOneTimePasswords'),
|
||||
'otpsDetails': MochiKit.Base.method(this.user(),'getOneTimePasswordsDetails'),
|
||||
}),
|
||||
}, {trace:false}),
|
||||
function (someData) {
|
||||
return MochiKit.Base.update(someData['userInfo'], someData['otpDetails']);
|
||||
},
|
||||
MochiKit.Base.bind(function(someUserInfo) {
|
||||
this.setPageProperties('mainPage', 'userInfo', someUserInfo);
|
||||
}, this)
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
/* Used only one time (the first time the OTP ExtraFeature loads), other times
|
||||
the list update is triggered by other operations. Maybe the first OTP list retrieval
|
||||
could be done during init, so that this would not be necessary. */
|
||||
updateOTPListAndDetails_handler: function () {
|
||||
return this.updateOTPListAndDetails();
|
||||
},
|
||||
|
||||
createNewOTP_handler: function () {
|
||||
return Clipperz.Async.callbacks("MainController.createNewOTP_handler", [
|
||||
MochiKit.Base.method(this.overlay(), 'show', "", true),
|
||||
MochiKit.Base.method(this.user(), 'createNewOTP'),
|
||||
MochiKit.Base.method(this, 'updateOTPListAndDetails'),
|
||||
MochiKit.Base.method(this.overlay(), 'done', "", 0.5),
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
deleteOTPs_handler: function (aList) {
|
||||
return Clipperz.Async.callbacks("MainController.deleteOTPs_handler", [
|
||||
MochiKit.Base.method(this.overlay(), 'show', "", true),
|
||||
MochiKit.Base.method(this.user(), 'deleteOTPs', aList),
|
||||
MochiKit.Base.method(this, 'updateOTPListAndDetails'),
|
||||
MochiKit.Base.method(this.overlay(), 'done', "", 0.5),
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
changeOTPLabel_handler: function (aReference, aLabel) {
|
||||
return Clipperz.Async.callbacks("MainController.changeOTPLabel_handler", [
|
||||
MochiKit.Base.method(this.overlay(), 'show', "", true),
|
||||
MochiKit.Base.method(this.user(), 'changeOTPLabel', aReference, aLabel),
|
||||
MochiKit.Base.method(this, 'updateOTPListAndDetails'),
|
||||
MochiKit.Base.method(this.overlay(), 'done', "", 0.5),
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
saveChanges: function () {
|
||||
|
||||
Reference in New Issue
Block a user