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

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