1
0
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:
Giulio Cesare Solaroli
2015-06-27 19:08:20 +02:00
parent 83b40aea50
commit e02ba2c20c
54 changed files with 4535 additions and 1659 deletions

View File

@@ -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);
},

View File

@@ -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) {

View File

@@ -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")
]);
}

View File

@@ -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")
]),

View File

@@ -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"))
])
])
]);
},

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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')))
);
},
});

View File

@@ -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")

View File

@@ -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'])
])

View 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);

View File

@@ -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"),

View File

@@ -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),

View File

@@ -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(),

View File

@@ -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>';
},
//----------------------------------------------------------------------------

View File

@@ -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"
});

View File

@@ -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 () {