mirror of
http://git.whoc.org.uk/git/password-manager.git
synced 2025-10-29 18:37:35 +01:00
Reviewed and improved export feature
This commit is contained in:
@@ -332,12 +332,13 @@ console.log("Record.Version.hasPendingChanges");
|
||||
});
|
||||
deferredResult.addCallback(Clipperz.Async.collectAll);
|
||||
deferredResult.addCallback(function(listIn) {
|
||||
return listIn.reduce(function(result, field) {
|
||||
// return listIn.reduce(function(result, field) {
|
||||
return MochiKit.Iter.reduce(function(result, field) {
|
||||
var ref = field.reference;
|
||||
result[ref] = field;
|
||||
delete result[ref].reference;
|
||||
return result;
|
||||
}, {});
|
||||
}, listIn, {});
|
||||
});
|
||||
|
||||
deferredResult.callback();
|
||||
|
||||
@@ -45,9 +45,7 @@ Clipperz.PM.DataModel.Record = function(args) {
|
||||
this._createNewDirectLoginFunction = args.createNewDirectLoginFunction || null;
|
||||
|
||||
this._tags = [];
|
||||
|
||||
this._directLogins = {};
|
||||
|
||||
this._versions = {};
|
||||
|
||||
this._currentRecordVersion = null;
|
||||
@@ -163,34 +161,20 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
|
||||
|
||||
//............................................................................
|
||||
|
||||
'tagRegExp': function () {
|
||||
return new RegExp('\\' + Clipperz.PM.DataModel.Record.tagChar + '(' + Clipperz.PM.DataModel.Record.specialTagChar + '?\\w+)', 'g');
|
||||
'extractLabelFromFullLabel': function (aValue) {
|
||||
return Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aValue);
|
||||
},
|
||||
|
||||
'trimSpacesRegExp': function () {
|
||||
return new RegExp('^\\s+|\\s+$', 'g');
|
||||
'extractTagsFromFullLabel': function (aLabel) {
|
||||
return Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aLabel);
|
||||
},
|
||||
|
||||
// 'tagCleanupRegExp': function () {
|
||||
// return new RegExp('\\' + Clipperz.PM.DataModel.Record.tagSpace, 'g');
|
||||
// },
|
||||
|
||||
//............................................................................
|
||||
|
||||
'filterOutTags': function (aValue) {
|
||||
var value;
|
||||
|
||||
value = aValue;
|
||||
value = value.replace(this.tagRegExp(), '');
|
||||
value = value.replace(this.trimSpacesRegExp(), '');
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
'label': function () {
|
||||
return Clipperz.Async.callbacks("Record.label", [
|
||||
MochiKit.Base.method(this, 'fullLabel'),
|
||||
MochiKit.Base.method(this, 'filterOutTags')
|
||||
MochiKit.Base.method(this, 'extractLabelFromFullLabel')
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
@@ -211,22 +195,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
|
||||
|
||||
//.........................................................................
|
||||
|
||||
'extractTagsFromFullLabel': function (aLabel) {
|
||||
var tagRegEx;
|
||||
var result;
|
||||
var match;
|
||||
|
||||
result = {};
|
||||
tagRegEx = this.tagRegExp();
|
||||
match = tagRegEx.exec(aLabel);
|
||||
while (match != null) {
|
||||
result[match[1]] = true;
|
||||
match = tagRegEx.exec(aLabel);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
'tags': function () {
|
||||
return Clipperz.Async.callbacks("Record.label", [
|
||||
MochiKit.Base.method(this, 'fullLabel'),
|
||||
@@ -1173,10 +1141,9 @@ console.log("Record.hasPendingChanges RESULT", result);
|
||||
|
||||
'exportDirectLogins': function() {
|
||||
var result;
|
||||
|
||||
var directLoginsObject = this.directLogins();
|
||||
|
||||
if (Object.keys(directLoginsObject).length == 0) {
|
||||
if (MochiKit.Base.keys(directLoginsObject).length == 0) {
|
||||
result = {};
|
||||
} else {
|
||||
var callbackObject = Object.keys(directLoginsObject).reduce(function(previous, current) {
|
||||
@@ -1184,7 +1151,7 @@ console.log("Record.hasPendingChanges RESULT", result);
|
||||
return previous;
|
||||
}, {});
|
||||
|
||||
result = Clipperz.Async.collectResults("Record.exportDirectLogins",callbackObject,{trace:false})();
|
||||
result = Clipperz.Async.collectResults("Record.exportDirectLogins", callbackObject,{trace:false})();
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -1202,20 +1169,20 @@ console.log("Record.hasPendingChanges RESULT", result);
|
||||
currentVersion = {};
|
||||
directLogins = {};
|
||||
deferredResult = new Clipperz.Async.Deferred('Record.export', {trace:false});
|
||||
deferredResult.addMethod(this,'getCurrentRecordVersion');
|
||||
deferredResult.addMethod(this, 'getCurrentRecordVersion');
|
||||
deferredResult.addCallback(function(recordVersionIn) { currentVersionObject = recordVersionIn; })
|
||||
deferredResult.addMethod(this,'fullLabel');
|
||||
deferredResult.addMethod(this,function(labelIn) {label = labelIn});
|
||||
deferredResult.addMethod(this,'exportDirectLogins');
|
||||
deferredResult.addMethod(this, 'fullLabel');
|
||||
deferredResult.addMethod(this, function(labelIn) {label = labelIn});
|
||||
deferredResult.addMethod(this, 'exportDirectLogins');
|
||||
deferredResult.addCallback(function(directLoginsIn) { data['directLogins'] = directLoginsIn; });
|
||||
deferredResult.addCallback(function() { return currentVersionObject.getKey(); }),
|
||||
deferredResult.addMethod(this,function(keyIn) { data['currentVersionKey'] = keyIn; });
|
||||
deferredResult.addMethod(this,'notes');
|
||||
deferredResult.addMethod(this,function(notesIn) { data['notes'] = notesIn; });
|
||||
deferredResult.addMethod(this,function() { currentVersion['reference'] = this.currentVersionReference(); });
|
||||
// deferredResult.addMethod(this,function(keyIn) { data['currentVersionKey'] = keyIn; });
|
||||
deferredResult.addMethod(this, 'notes');
|
||||
deferredResult.addMethod(this, function(notesIn) { data['notes'] = notesIn; });
|
||||
// deferredResult.addMethod(this, function() { currentVersion['reference'] = this.currentVersionReference(); });
|
||||
deferredResult.addCallback(function() { return currentVersionObject.exportFields(); }),
|
||||
deferredResult.addCallback(function(fieldsIn) { currentVersion['fields'] = fieldsIn; });
|
||||
deferredResult.addMethod(this,function() {
|
||||
deferredResult.addMethod(this, function() {
|
||||
return {
|
||||
'label': label,
|
||||
'data': data,
|
||||
@@ -1267,3 +1234,34 @@ Clipperz.PM.DataModel.Record.isRegularTag = function (aTag) {
|
||||
Clipperz.PM.DataModel.Record.regExpForSearch = function (aSearch) {
|
||||
return new RegExp(aSearch.replace(/[^A-Za-z0-9]/g, '\\$&'), 'i');
|
||||
};
|
||||
|
||||
|
||||
|
||||
Clipperz.PM.DataModel.Record.tagRegExp = new RegExp('\\' + Clipperz.PM.DataModel.Record.tagChar + '(' + Clipperz.PM.DataModel.Record.specialTagChar + '?\\w+)', 'g');
|
||||
Clipperz.PM.DataModel.Record.trimSpacesRegExp = new RegExp('^\\s+|\\s+$', 'g');
|
||||
|
||||
Clipperz.PM.DataModel.Record.extractLabelFromFullLabel = function (aValue) {
|
||||
var value;
|
||||
|
||||
value = aValue;
|
||||
value = value.replace(Clipperz.PM.DataModel.Record.tagRegExp, '');
|
||||
value = value.replace(Clipperz.PM.DataModel.Record.trimSpacesRegExp, '');
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
Clipperz.PM.DataModel.Record.extractTagsFromFullLabel = function (aLabel) {
|
||||
var tagRegEx;
|
||||
var result;
|
||||
var match;
|
||||
|
||||
result = {};
|
||||
tagRegEx = Clipperz.PM.DataModel.Record.tagRegExp;
|
||||
match = tagRegEx.exec(aLabel);
|
||||
while (match != null) {
|
||||
result[match[1]] = true;
|
||||
match = tagRegEx.exec(aLabel);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
@@ -30,18 +30,77 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass = React.createClass({
|
||||
// featureSet: React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
|
||||
// 'level': React.PropTypes.oneOf(['hide', 'info', 'warning', 'error']).isRequired
|
||||
},
|
||||
/*
|
||||
jsonExport: function () {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export', 'json');
|
||||
},
|
||||
|
||||
htmlExport: function () {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export', 'html');
|
||||
},
|
||||
*/
|
||||
|
||||
isFeatureEnabled: function (aValue) {
|
||||
return (this.props['features'].indexOf(aValue) > -1);
|
||||
},
|
||||
|
||||
handleDownloadOfflineCopyLink: function (anEvent) {
|
||||
if (this.isFeatureEnabled('OFFLINE_COPY')) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'downloadOfflineCopy');
|
||||
}
|
||||
},
|
||||
|
||||
handleExportLink: function () {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'downloadExport');
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature devicePIN'}, [
|
||||
React.DOM.h1({}, "Export"),
|
||||
React.DOM.p({'className': 'link', 'onClick': MochiKit.Base.method(this, function(){
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export','json');
|
||||
})}, "JSON"),
|
||||
React.DOM.p({'className': 'link', 'onClick': MochiKit.Base.method(this, function(){
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export','printable');
|
||||
})}, "Printable version")
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.ul({}, [
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "Offline copy"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "Download a read-only portable version of Clipperz. Very convenient when no Internet connection is available."),
|
||||
React.DOM.p({}, "An offline copy is just a single HTML file that contains both the whole Clipperz web application and your encrypted data."),
|
||||
React.DOM.p({}, "It is as secure as the hosted Clipperz service since they both share the same code and security architecture.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.handleDownloadOfflineCopyLink}, "download offline copy")
|
||||
]),
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "HTML + JSON"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "Download a printer-friendly HTML file that lists the content of all your cards."),
|
||||
React.DOM.p({}, "This same file also contains all your data in JSON format."),
|
||||
React.DOM.p({}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.handleExportLink}, "download HTML+JSON")
|
||||
]),
|
||||
/*
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "Printing"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "Click on the button below to open a new window displaying all your cards in a printable format."),
|
||||
React.DOM.p({}, "If you are going to print for backup purposes, please consider the safer option provided by the “offline copy”.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.htmlExport}, "HTML")
|
||||
]),
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "Exporting to JSON"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "JSON enables a “lossless” export of your cards. All the information will be preserved, including direct login configurations."),
|
||||
React.DOM.p({}, "This custom format it’s quite convenient if you need to move some of all of your cards to a different Clipperz account. Or if you want to restore a card that has been accidentally deleted."),
|
||||
React.DOM.p({}, "Click on the button below to start the export process.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.jsonExport}, "JSON"),
|
||||
])
|
||||
*/
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@@ -78,19 +78,21 @@ Clipperz.PM.UI.Components.ExtraFeatures.DeleteAccountClass = React.createClass({
|
||||
|
||||
return React.DOM.div({className:'extraFeature deleteAccount'}, [
|
||||
React.DOM.h1({}, "Delete Account"),
|
||||
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.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.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.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.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableDeleteAccountButton(), 'className':'button'}, "Delete my account")
|
||||
//~ React.DOM.div({ref: 'errorMessage', className: 'errorMessage', style: {visibility: errorVisibility} }, this.state.error)
|
||||
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableDeleteAccountButton(), 'className':'button'}, "Delete my account")
|
||||
//~ React.DOM.div({ref: 'errorMessage', className: 'errorMessage', style: {visibility: errorVisibility} }, this.state.error)
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
@@ -36,7 +36,9 @@ Clipperz.PM.UI.Components.ExtraFeatures.DevicePINClass = React.createClass({
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature devicePIN'}, [
|
||||
React.DOM.h1({}, "Device PIN"),
|
||||
React.DOM.h3({}, this.props['PIN'])
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.h3({}, this.props['PIN'])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@@ -101,27 +101,29 @@ Clipperz.PM.UI.Components.ExtraFeatures.PassphraseClass = React.createClass({
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature passphrase'}, [
|
||||
React.DOM.h1({}, "Change Passphrase"),
|
||||
React.DOM.form({'key':'form', 'className':'changePassphraseForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleChangePassphrase}, [
|
||||
React.DOM.div({'key':'fields'},[
|
||||
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
|
||||
React.DOM.input({'key':'username', 'className':this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.form({'key':'form', 'className':'changePassphraseForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleChangePassphrase}, [
|
||||
React.DOM.div({'key':'fields'},[
|
||||
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
|
||||
React.DOM.input({'key':'username', 'className':this.state['username'], 'type':'text', 'name':'name', 'ref':'username', 'placeholder':"username", 'autoCapitalize':'none'}),
|
||||
|
||||
React.DOM.label({'key':'old-passphrase-label', 'htmlFor' :'old-passphrase'}, "old passphrase"),
|
||||
React.DOM.input({'key':'old-passphrase', 'className':this.state['old-passphrase'], 'type':'password', 'name':'old-passphrase', 'ref':'old-passphrase', 'placeholder':"old passphrase"}),
|
||||
React.DOM.label({'key':'old-passphrase-label', 'htmlFor' :'old-passphrase'}, "old passphrase"),
|
||||
React.DOM.input({'key':'old-passphrase', 'className':this.state['old-passphrase'], 'type':'password', 'name':'old-passphrase', 'ref':'old-passphrase', 'placeholder':"old passphrase"}),
|
||||
|
||||
React.DOM.label({'key':'new-passphrase-label', 'autoFocus': 'true', 'htmlFor' :'new-passphrase'}, "new passphrase"),
|
||||
React.DOM.input({'key':'new-passphrase', 'className':this.state['new-passphrase'], 'type':'password', 'name':'new-passphrase', 'ref':'new-passphrase', 'placeholder':"new passphrase"}),
|
||||
React.DOM.label({'key':'new-passphrase-label', 'autoFocus': 'true', 'htmlFor' :'new-passphrase'}, "new passphrase"),
|
||||
React.DOM.input({'key':'new-passphrase', 'className':this.state['new-passphrase'], 'type':'password', 'name':'new-passphrase', 'ref':'new-passphrase', 'placeholder':"new passphrase"}),
|
||||
|
||||
React.DOM.label({'key':'confirm-new-passphrase-label', 'htmlFor' :'confirm-new-passphrase'}, "confirm new passphrase"),
|
||||
React.DOM.input({'key':'confirm-new-passphrase', 'className':this.state['confirm-new-passphrase'], 'type':'password', 'name':'confirm-new-passphrase', 'ref':'confirm-new-passphrase', 'placeholder':"confirm new passphrase"}),
|
||||
React.DOM.label({'key':'confirm-new-passphrase-label', 'htmlFor' :'confirm-new-passphrase'}, "confirm new passphrase"),
|
||||
React.DOM.input({'key':'confirm-new-passphrase', 'className':this.state['confirm-new-passphrase'], 'type':'password', 'name':'confirm-new-passphrase', 'ref':'confirm-new-passphrase', 'placeholder':"confirm new passphrase"}),
|
||||
|
||||
React.DOM.p({}, [
|
||||
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
|
||||
React.DOM.span({}, "I understand that Clipperz will not be able to recover a lost passphrase.")
|
||||
React.DOM.p({}, [
|
||||
React.DOM.input({'key':'confirm', 'className':'confirmCheckbox', 'type':'checkbox', 'name':'confirm', 'ref':'confirm'}),
|
||||
React.DOM.span({}, "I understand that Clipperz will not be able to recover a lost passphrase.")
|
||||
]),
|
||||
]),
|
||||
]),
|
||||
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableChangePassphraseButton(), 'className':'button'}, "Change passphrase"),
|
||||
]),
|
||||
React.DOM.button({'key':'button', 'type':'submit', 'disabled':!this.shouldEnableChangePassphraseButton(), 'className':'button'}, "Change passphrase"),
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
||||
@@ -53,11 +53,15 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
'show': function (aMessage, showMask) {
|
||||
'show': function (aMessage, showMask, showProgress) {
|
||||
if (showMask === true) {
|
||||
this.showMask();
|
||||
}
|
||||
|
||||
if (showProgress === true) {
|
||||
this.showProgressBar();
|
||||
}
|
||||
|
||||
this.resetStatus();
|
||||
this.setMessage(aMessage);
|
||||
MochiKit.DOM.removeElementClass(this.element(), 'ios-overlay-hide');
|
||||
@@ -66,6 +70,7 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
|
||||
|
||||
'done': function (aMessage, aDelayBeforeHiding) {
|
||||
this.hideMask();
|
||||
this.hideProgressBar();
|
||||
this.completed(this.showDoneIcon, aMessage, aDelayBeforeHiding);
|
||||
},
|
||||
|
||||
@@ -114,6 +119,7 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
|
||||
|
||||
'hide': function () {
|
||||
var element = this.element();
|
||||
this.hideProgressBar();
|
||||
MochiKit.DOM.removeElementClass(element, 'ios-overlay-show');
|
||||
MochiKit.DOM.addElementClass(element, 'ios-overlay-hide');
|
||||
return MochiKit.Async.callLater(1, MochiKit.Style.hideElement, element);
|
||||
@@ -133,6 +139,21 @@ Clipperz.Base.extend(Clipperz.PM.UI.Components.Overlay, Object, {
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
'showProgressBar': function () {
|
||||
MochiKit.Style.showElement(this.getElement('progressBar'));
|
||||
},
|
||||
|
||||
'hideProgressBar': function () {
|
||||
MochiKit.Style.hideElement(this.getElement('progressBar'));
|
||||
},
|
||||
|
||||
'updateProgress': function (aProgressPercentage) {
|
||||
MochiKit.Style.setElementDimensions(this.getElement('progress'), {'w': aProgressPercentage}, '%');
|
||||
//console.log("OVERLAY - updating progress: " + aProgressPercentage + "%");
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
'defaultDelay': function () {
|
||||
return this._defaultDelay;
|
||||
},
|
||||
|
||||
@@ -70,9 +70,13 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
|
||||
//=========================================================================
|
||||
|
||||
showExtraFeatureComponent: function (aComponentName) {
|
||||
toggleExtraFeatureComponent: function (aComponentName) {
|
||||
return MochiKit.Base.bind(function () {
|
||||
this.showExtraFeatureContent(Clipperz.PM.UI.Components.ExtraFeatures[aComponentName], aComponentName);
|
||||
if (this.state['extraFeatureComponentName'] != aComponentName) {
|
||||
this.showExtraFeatureContent(Clipperz.PM.UI.Components.ExtraFeatures[aComponentName], aComponentName);
|
||||
} else {
|
||||
this.hideExtraFeatureContent();
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
@@ -118,7 +122,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.li({'key':'account', 'className':this.state['index']['account'] ? 'open' : 'closed'}, [
|
||||
React.DOM.h1({'key':'accountH1', 'onClick':this.toggleIndexState('account')}, "Account"),
|
||||
React.DOM.ul({'key':'accountUL'}, [
|
||||
React.DOM.li({'key':'account_1', 'onClick':this.showExtraFeatureComponent('Passphrase'), 'className':(this.state['extraFeatureComponentName'] == 'Passphrase') ? 'selected' : ''}, [
|
||||
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.")
|
||||
@@ -130,7 +134,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.p({}, "")
|
||||
])
|
||||
]),
|
||||
React.DOM.li({'key':'account_3', 'onClick':this.showExtraFeatureComponent('DevicePIN')}, [
|
||||
React.DOM.li({'key':'account_3', 'onClick':this.toggleExtraFeatureComponent('DevicePIN')}, [
|
||||
React.DOM.h2({}, "Device PIN"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "Configure a PIN that will allow to get access to your cards, but only on this device.")
|
||||
@@ -142,7 +146,7 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.p({}, "")
|
||||
])
|
||||
]),
|
||||
React.DOM.li({'key':'account_5', 'onClick':this.showExtraFeatureComponent('DeleteAccount'), 'className':(this.state['extraFeatureComponentName'] == 'DeleteAccount') ? 'selected' : ''}, [
|
||||
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.")
|
||||
@@ -182,29 +186,29 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
React.DOM.li({'key':'data', 'className':this.state['index']['data'] ? 'open' : 'closed'}, [
|
||||
React.DOM.h1({'onClick':this.toggleIndexState('data')}, "Data"),
|
||||
React.DOM.ul({'key':'data'}, [
|
||||
React.DOM.li({'key':'data_1'}, [
|
||||
React.DOM.h2({}, "Offline copy"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "With just one click you can dump all your encrypted data from Clipperz servers to your hard disk and create a read-only offline version of Clipperz to be used when you are not connected to the Internet."),
|
||||
React.DOM.a({'className':Clipperz.PM.UI.Components.classNames(offlineCopyButtonClasses), 'onClick':this.handleDownloadOfflineCopyLink}, "Download")
|
||||
])
|
||||
]),
|
||||
// React.DOM.li({'key':'data_1', 'className':'offlineCopy'}, [
|
||||
// React.DOM.h2({}, "Offline copy"),
|
||||
// React.DOM.div({}, [
|
||||
// React.DOM.p({}, "With just one click you can dump all your encrypted data from Clipperz servers to your hard disk and create a read-only offline version of Clipperz to be used when you are not connected to the Internet."),
|
||||
// React.DOM.a({'className':Clipperz.PM.UI.Components.classNames(offlineCopyButtonClasses), 'onClick':this.handleDownloadOfflineCopyLink}, "Download")
|
||||
// ])
|
||||
// ]),
|
||||
React.DOM.li({'key':'data_2'}, [
|
||||
React.DOM.h2({}, "Import"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "")
|
||||
React.DOM.p({}, "CSV, JSON, …")
|
||||
])
|
||||
]),
|
||||
React.DOM.li({'key':'data_3', 'onClick':this.showExtraFeatureComponent('DataExport')}, [
|
||||
React.DOM.li({'key':'data_3', 'onClick':this.toggleExtraFeatureComponent('DataExport'), 'className':(this.state['extraFeatureComponentName'] == 'DataExport') ? 'selected' : ''}, [
|
||||
React.DOM.h2({}, "Export"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "")
|
||||
React.DOM.p({}, "Offline copy, printable version, JSON, …")
|
||||
])
|
||||
]),
|
||||
React.DOM.li({'key':'data_4'}, [
|
||||
React.DOM.h2({}, "Sharing"),
|
||||
React.DOM.div({}, [
|
||||
React.DOM.p({}, "")
|
||||
React.DOM.p({}, "Securely share cards with other users")
|
||||
])
|
||||
])
|
||||
])
|
||||
@@ -229,17 +233,20 @@ Clipperz.PM.UI.Components.Panels.ExtraFeaturesPanelClass = React.createClass({
|
||||
|
||||
render: function () {
|
||||
//console.log("ExtraFeaturesPanel props", this.props);
|
||||
var isOpen = (this.props['settingsPanelStatus'] == 'OPEN');
|
||||
var isFullyOpen = isOpen && this.state['isFullyOpen'];
|
||||
|
||||
var classes = {
|
||||
'panel': true,
|
||||
'right': true,
|
||||
'open': this.props['settingsPanelStatus'] == 'OPEN',
|
||||
'fullOpen': this.state['isFullyOpen']
|
||||
'open': isOpen,
|
||||
'fullOpen': isFullyOpen
|
||||
}
|
||||
|
||||
|
||||
return React.DOM.div({'key':'extraFeaturesPanel', 'id':'extraFeaturesPanel', 'className':Clipperz.PM.UI.Components.classNames(classes)}, [
|
||||
this.renderIndex(),
|
||||
this.renderContent(),
|
||||
// (this.props['settingsPanelStatus'] == 'OPEN') ? this.renderContent() : null,
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,87 +21,111 @@ refer to http://www.clipperz.com.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI');
|
||||
|
||||
// https://github.com/eligrey/FileSaver.js
|
||||
// https://github.com/eligrey/Blob.js
|
||||
|
||||
Clipperz.PM.UI.ExportController = function(args) {
|
||||
this._type = args['type'] || Clipperz.Base.exception.raise('MandatoryParameter');
|
||||
this._recordsInfo = args['recordsInfo'] || Clipperz.Base.exception.raise('MandatoryParameter');
|
||||
this._target = Clipperz.PM.Crypto.randomKey();
|
||||
this._processedRecords = 0;
|
||||
|
||||
this._style = "body {"+
|
||||
" margin: 0;"+
|
||||
" padding: 0;"+
|
||||
" font-family: monospace;"+
|
||||
"}"+
|
||||
""+
|
||||
"p {"+
|
||||
" padding-left: 1em;"+
|
||||
"}"+
|
||||
""+
|
||||
"h1 {"+
|
||||
" color: #ff9900;"+
|
||||
" background: black;"+
|
||||
" box-shadow: 0px 5px 6px 0 rgba(0, 0, 0, 0.15);"+
|
||||
" margin: 0;"+
|
||||
" padding:1em;"+
|
||||
"}"+
|
||||
""+
|
||||
".progressBar {"+
|
||||
" position: absolute;"+
|
||||
" width: 100%;"+
|
||||
" margin-top: 0px;"+
|
||||
" "+
|
||||
"}"+
|
||||
""+
|
||||
"#completed {"+
|
||||
" background: #ff9900;"+
|
||||
" color: white;"+
|
||||
" width: 0;"+
|
||||
" overflow: hidden;"+
|
||||
" font-size: 0.8em;"+
|
||||
" box-shadow: 0px 4px 6px 0 rgba(0, 0, 0, 0.15);"+
|
||||
"}"+
|
||||
""+
|
||||
"#printableUl {"+
|
||||
" width:100%;"+
|
||||
" height:80%;"+
|
||||
" margin: 0;"+
|
||||
" padding: 0;"+
|
||||
" list-style-type: none;"+
|
||||
"}"+
|
||||
""+
|
||||
"#printableUl li {"+
|
||||
" border: 1px solid #1863a1;"+
|
||||
" margin: 1em;"+
|
||||
""+
|
||||
"}"+
|
||||
""+
|
||||
"#printableUl li .label {"+
|
||||
" background: #1863a1;"+
|
||||
" color: white;"+
|
||||
" display: block;"+
|
||||
" padding: 1em;"+
|
||||
"}"+
|
||||
""+
|
||||
"#printableUl li dl {"+
|
||||
" padding: 1em;"+
|
||||
"}"+
|
||||
""+
|
||||
"#printableUl li dl dt {"+
|
||||
" color: darkgray;"+
|
||||
"}"+
|
||||
""+
|
||||
"#printableUl li dl dd {"+
|
||||
" padding: 0;"+
|
||||
" margin: 0 0 .5em 0;"+
|
||||
"}"+
|
||||
""+
|
||||
"#printableUl li .notes {"+
|
||||
" font-style: italic;"+
|
||||
" padding: 1em 0 0 1em;"+
|
||||
" display: block;"+
|
||||
"}"+
|
||||
"";
|
||||
this._style =
|
||||
"body {" +
|
||||
"font-family: 'Dejavu Sans', monospace;" +
|
||||
"margin: 0px;" +
|
||||
"}" +
|
||||
|
||||
"header {" +
|
||||
"padding: 10px;" +
|
||||
"border-bottom: 2px solid black;" +
|
||||
"}" +
|
||||
|
||||
"h1 {" +
|
||||
"margin: 0px;" +
|
||||
"}" +
|
||||
|
||||
"h2 {" +
|
||||
"margin: 0px;" +
|
||||
"padding-top: 10px;" +
|
||||
"}" +
|
||||
|
||||
"h3 {" +
|
||||
"margin: 0px;" +
|
||||
"}" +
|
||||
|
||||
"h5 {" +
|
||||
"margin: 0px;" +
|
||||
"color: gray;" +
|
||||
"}" +
|
||||
|
||||
"ul {" +
|
||||
"margin: 0px;" +
|
||||
"padding: 0px;" +
|
||||
"}" +
|
||||
|
||||
"div > ul > li {" +
|
||||
"border-bottom: 1px solid black;" +
|
||||
"padding: 10px;" +
|
||||
"}" +
|
||||
|
||||
"div > ul > li.archived {" +
|
||||
"background-color: #ddd;" +
|
||||
"}" +
|
||||
|
||||
|
||||
"ul > li > ul > li {" +
|
||||
"font-size: 9pt;" +
|
||||
"display: inline-block;" +
|
||||
"}" +
|
||||
|
||||
"ul > li > ul > li:after {" +
|
||||
"content: \",\";" +
|
||||
"padding-right: 5px;" +
|
||||
"}" +
|
||||
|
||||
"ul > li > ul > li:last-child:after {" +
|
||||
"content: \"\";" +
|
||||
"padding-right: 0px;" +
|
||||
"}" +
|
||||
|
||||
"dl {" +
|
||||
"}" +
|
||||
|
||||
"dt {" +
|
||||
"color: gray;" +
|
||||
"font-size: 9pt;" +
|
||||
"}" +
|
||||
|
||||
"dd {" +
|
||||
"margin: 0px;" +
|
||||
"margin-bottom: 5px;" +
|
||||
"padding-left: 10px;" +
|
||||
"}" +
|
||||
|
||||
"div > div {" +
|
||||
"background-color: black;" +
|
||||
"color: white;" +
|
||||
"padding: 10px;" +
|
||||
"}" +
|
||||
|
||||
"textarea {" +
|
||||
"width: 100%;" +
|
||||
"height: 200px;" +
|
||||
"}" +
|
||||
|
||||
"@media print {" +
|
||||
"div > div, header > div {" +
|
||||
"display: none !important;" +
|
||||
"}" +
|
||||
|
||||
"ul > li {" +
|
||||
"page-break-inside: avoid;" +
|
||||
"} " +
|
||||
"}" +
|
||||
|
||||
"";
|
||||
|
||||
return this;
|
||||
}
|
||||
@@ -114,188 +138,141 @@ MochiKit.Base.update(Clipperz.PM.UI.ExportController.prototype, {
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
'type': function () {
|
||||
return this._type;
|
||||
},
|
||||
|
||||
'recordsInfo': function () {
|
||||
return this._recordsInfo;
|
||||
},
|
||||
|
||||
'target': function () {
|
||||
return this._target;
|
||||
//=============================================================================
|
||||
|
||||
'reportRecordExport': function (aRecordData) {
|
||||
var percentage;
|
||||
var exportedCardsCount;
|
||||
var totalCardsToExport;
|
||||
|
||||
this._processedRecords = this._processedRecords + 1;
|
||||
|
||||
exportedCardsCount = this._processedRecords;
|
||||
totalCardsToExport = this.recordsInfo().length;
|
||||
percentage = Math.round(100 * exportedCardsCount / totalCardsToExport);
|
||||
|
||||
//console.log("PROCESSING " + exportedCardsCount + "/" + totalCardsToExport + " - " + percentage + "%");
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'updateProgress', percentage);
|
||||
|
||||
return MochiKit.Async.succeed(aRecordData);
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'setWindowTitle': function (aWindow, aTitle) {
|
||||
aWindow.document.title = aTitle;
|
||||
},
|
||||
|
||||
'setWindowBody': function (aWindow, anHTML) {
|
||||
aWindow.document.body.innerHTML = anHTML;
|
||||
'renderCardToHtml': function (jsonCardData) {
|
||||
var label = Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(jsonCardData.label);
|
||||
var allTags = MochiKit.Base.keys(Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(jsonCardData.label));
|
||||
var regularTags = MochiKit.Base.filter(Clipperz.PM.DataModel.Record.isRegularTag, allTags);
|
||||
var isArchived = MochiKit.Iter.some(allTags, MochiKit.Base.partial(MochiKit.Base.objEqual, Clipperz.PM.DataModel.Record.archivedTag));
|
||||
|
||||
return MochiKit.DOM.LI({'class': isArchived ? 'archived' : ""},
|
||||
MochiKit.DOM.H2({}, label),
|
||||
(regularTags.length > 0) ? MochiKit.DOM.UL({}, MochiKit.Base.map(function (tag) { return MochiKit.DOM.LI({}, tag);}, regularTags)): null,
|
||||
MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.DL({},
|
||||
MochiKit.Base.map(function(key) {
|
||||
return [
|
||||
MochiKit.DOM.DT(jsonCardData.currentVersion.fields[key].label),
|
||||
MochiKit.DOM.DD(jsonCardData.currentVersion.fields[key].value),
|
||||
];
|
||||
}, MochiKit.Base.keys(jsonCardData.currentVersion.fields))
|
||||
)
|
||||
),
|
||||
jsonCardData.data.notes ? MochiKit.DOM.P({}, jsonCardData.data.notes) : null
|
||||
);
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
'renderToHtml': function (jsonData) {
|
||||
var title;
|
||||
var style;
|
||||
var date;
|
||||
var body;
|
||||
|
||||
'initialWindowSetup': function (aWindow) {
|
||||
var dom = MochiKit.DOM.DIV({'id': 'main'},
|
||||
MochiKit.DOM.H1("Clipperz Exported Data (loading...)"),
|
||||
MochiKit.DOM.DIV({'class': 'progressBar'},
|
||||
MochiKit.DOM.DIV({'id': 'completed'},
|
||||
MochiKit.DOM.P({'style': 'margin:0; padding:0; text-align:center;'}, MochiKit.DOM.SPAN({'id': 'nCompleted'},"0"),"/",MochiKit.DOM.SPAN({'id': 'nTotal'},"") )
|
||||
title = "Clipperz data";
|
||||
style = this._style;
|
||||
date = "dd/mm/yyyy";
|
||||
|
||||
body = MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.HEADER({},
|
||||
MochiKit.DOM.H1({}, "Your data on Clipperz"),
|
||||
MochiKit.DOM.H5({}, "Export date: " + date),
|
||||
MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.P({}, "Security warning - This file lists the content of all your cards in a printer-friendly format. At the very bottom, the same content is also available in JSON format."),
|
||||
MochiKit.DOM.P({}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file. We recommend to delete it as soon as it is no longer needed."),
|
||||
MochiKit.DOM.P({}, "If you are going to print its content on paper, store the printout in a safe and private place!"),
|
||||
MochiKit.DOM.P({}, "And, if you need to access your data when no Internet connection is available, please consider the much safer option of creating an offline copy.")
|
||||
)
|
||||
),
|
||||
|
||||
MochiKit.DOM.UL({}, MochiKit.Base.map(this.renderCardToHtml, jsonData)),
|
||||
MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.H3({}, "JSON content"),
|
||||
MochiKit.DOM.DIV({},
|
||||
MochiKit.DOM.P({}, "Instructions on how to use JSON content"),
|
||||
MochiKit.DOM.P({}, "The JSON version of your data may be useful if you want to move the whole content of your Clipperz account to a new Clipperz account or recover a card that has been accidentally deleted. Just follow these instructions:"),
|
||||
MochiKit.DOM.OL({},
|
||||
MochiKit.DOM.LI({}, "Login to your Clipperz account and go to \"Data > Import\"."),
|
||||
MochiKit.DOM.LI({}, "Select the JSON option."),
|
||||
MochiKit.DOM.LI({}, "Copy and paste the JSON content in the form.")
|
||||
),
|
||||
MochiKit.DOM.P({}, "Of course, the unencrypted JSON content won't be transmitted to the Clipperz server.")
|
||||
),
|
||||
MochiKit.DOM.TEXTAREA({}, Clipperz.Base.serializeJSON(jsonData)),
|
||||
MochiKit.DOM.FOOTER({},
|
||||
MochiKit.DOM.P({},
|
||||
"This file has been downloaded from clipperz.is, a service by Clipperz Srl. - ",
|
||||
MochiKit.DOM.A({'href':'https://clipperz.is/terms_service/'}, "Terms of service"),
|
||||
" - ",
|
||||
MochiKit.DOM.A({'href':'https://clipperz.is/privacy_policy/'}, "Privacy policy")
|
||||
),
|
||||
MochiKit.DOM.H4({}, "Clipperz - keep it to yourself")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
aWindow.document.getElementsByTagName('head')[0].appendChild( MochiKit.DOM.STYLE(this._style) );
|
||||
|
||||
this.setWindowTitle(aWindow, "Clipperz Exported Data (loading...)");
|
||||
this.setWindowBody (aWindow, MochiKit.DOM.toHTML(dom));
|
||||
|
||||
return '<html><head><title>' + title + '</title><style type="text/css">' + style + '</style></head><body>' + MochiKit.DOM.toHTML(body) + '</body></html>';
|
||||
},
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
'updateWindowWithHTMLContent': function (aWindow, anHtml) {
|
||||
this.setWindowBody(aWindow, anHtml);
|
||||
},
|
||||
|
||||
'updateWindowJSON': function (aWindow, exportedJSON) {
|
||||
var dom = MochiKit.DOM.DIV({'id': 'main'},
|
||||
MochiKit.DOM.H1("Clipperz Exported Data"),
|
||||
MochiKit.DOM.P("You can now save the following data and load it at any time using the Clipperz import feature."),
|
||||
MochiKit.DOM.TEXTAREA({'style': 'width:100%; height:80%'}, Clipperz.Base.serializeJSON(exportedJSON))
|
||||
);
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
'saveResult': function (exportedJSON) {
|
||||
var blob;
|
||||
var sortedJSON;
|
||||
|
||||
this.setWindowTitle(aWindow, "Clipperz Exported Data");
|
||||
this.setWindowBody(aWindow, MochiKit.DOM.toHTML(dom));
|
||||
},
|
||||
|
||||
'updateWindowPrintable': function (aWindow, exportedJSON) {
|
||||
var dom = MochiKit.DOM.DIV({'id': 'main'},
|
||||
MochiKit.DOM.H1("Clipperz Exported Data"),
|
||||
MochiKit.DOM.P("You can now print this page and store it in a safe place."),
|
||||
MochiKit.DOM.UL({'id': 'printableUl'},
|
||||
exportedJSON.map(function(card){
|
||||
var label = (card.label.indexOf('')>=0) ? card.label.slice(0,card.label.indexOf('')).trim() : card.label;
|
||||
var notes = (card.data.notes) ? MochiKit.DOM.SPAN({'class': 'notes'}, card.data.notes) : "";
|
||||
|
||||
return MochiKit.DOM.LI({},
|
||||
MochiKit.DOM.SPAN({'class': 'label'}, label),
|
||||
notes,
|
||||
MochiKit.DOM.DL({},
|
||||
Object.keys(card.currentVersion.fields).map(function(key) {
|
||||
return [
|
||||
MochiKit.DOM.DT(card.currentVersion.fields[key].label),
|
||||
MochiKit.DOM.DD(card.currentVersion.fields[key].value),
|
||||
];
|
||||
})
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
this.setWindowTitle(aWindow, "Clipperz Exported Data");
|
||||
this.setWindowBody(aWindow, MochiKit.DOM.toHTML(dom));
|
||||
},
|
||||
|
||||
'updateWindowError': function (aWindow, errorMessage) {
|
||||
this.setWindowBody(aWindow,
|
||||
"<h3>Error</h3>"+
|
||||
"<p>The following error occured while exporting your data:</p>"+
|
||||
"<code>"+errorMessage+"</code>"
|
||||
);
|
||||
sortedJSON = MochiKit.Iter.sorted(exportedJSON, function(a,b) { return a.label.toUpperCase().localeCompare(b.label.toUpperCase()); } );
|
||||
|
||||
blob = new Blob([this.renderToHtml(sortedJSON)], {type: "text/html;charset=utf-8"});
|
||||
saveAs(blob, "clipperz_data.html");
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'runExportJSON': function (aWindow) {
|
||||
|
||||
'run': function () {
|
||||
var deferredResult;
|
||||
var exportedRecords;
|
||||
|
||||
var totalRecords = this.recordsInfo().length;
|
||||
|
||||
exportedRecords = 0;
|
||||
var self = this;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("DirectLoginRunner.exportJSON", {trace:false});
|
||||
deferredResult.addMethod(this, 'initialWindowSetup', aWindow);
|
||||
deferredResult.addCallback(function() { return "Export Data"});
|
||||
deferredResult.addMethod(this, 'setWindowTitle', aWindow);
|
||||
|
||||
deferredResult.addMethod( this, function() { return this.recordsInfo(); });
|
||||
deferredResult.addCallback( MochiKit.Base.map, function(recordIn) {
|
||||
var dr = new Clipperz.Async.Deferred("DirectLoginRunner.exportJSON__exportRecord", {trace:false});
|
||||
dr.addMethod(recordIn._rowObject, 'export');
|
||||
dr.addCallback(MochiKit.Base.method(this, function (exportedRecord) {
|
||||
var percentage = Math.round(100*exportedRecords/totalRecords);
|
||||
|
||||
aWindow.document.getElementById('nCompleted').innerText = ++exportedRecords;
|
||||
aWindow.document.getElementById('nTotal').innerText = totalRecords;
|
||||
aWindow.document.getElementById('completed').style.width = percentage+'%';
|
||||
|
||||
return exportedRecord;
|
||||
}));
|
||||
dr.callback();
|
||||
return dr;
|
||||
deferredResult = new Clipperz.Async.Deferred("ExportController.run", {trace:false});
|
||||
deferredResult.addCallback(MochiKit.Base.map, function(recordIn) {
|
||||
var innerDeferredResult;
|
||||
|
||||
innerDeferredResult = new Clipperz.Async.Deferred("ExportController.run__exportRecord", {trace:false});
|
||||
innerDeferredResult.addMethod(recordIn._rowObject, 'export');
|
||||
innerDeferredResult.addMethod(self, 'reportRecordExport');
|
||||
innerDeferredResult.callback();
|
||||
|
||||
return innerDeferredResult;
|
||||
});
|
||||
|
||||
deferredResult.addCallback(Clipperz.Async.collectAll);
|
||||
deferredResult.addMethod( this, function(exportedJSONIn) {
|
||||
// console.log('return',exportedJSONIn);
|
||||
|
||||
sortedJSON = exportedJSONIn.sort( function(a,b) { return a.label.toUpperCase().localeCompare(b.label.toUpperCase()); } );
|
||||
|
||||
switch (this.type()) {
|
||||
case 'json':
|
||||
this.updateWindowJSON(aWindow,exportedJSONIn);
|
||||
break;
|
||||
case 'printable':
|
||||
this.updateWindowPrintable(aWindow,exportedJSONIn);
|
||||
break;
|
||||
default:
|
||||
this.updateWindowError(aWindow,"ExportController.runExportJSON: invalid value '"+this.type()+"' for parameter 'type'.");
|
||||
}
|
||||
});
|
||||
|
||||
deferredResult.callback();
|
||||
deferredResult.addMethod(this, 'saveResult');
|
||||
deferredResult.callback(this.recordsInfo());
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'run': function () {
|
||||
var newWindow;
|
||||
|
||||
newWindow = window.open("", this.target());
|
||||
|
||||
return this.runExportJSON(newWindow);
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'test': function () {
|
||||
var iFrame;
|
||||
var newWindow;
|
||||
|
||||
iFrame = MochiKit.DOM.createDOM('iframe');
|
||||
MochiKit.DOM.appendChildNodes(MochiKit.DOM.currentDocument().body, iFrame);
|
||||
|
||||
newWindow = iFrame.contentWindow;
|
||||
|
||||
return this.runDirectLogin(newWindow);
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
__syntaxFix__: "syntax fix"
|
||||
});
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
Clipperz.PM.UI.ExportController.exportJSON = function (recordsInfoIn, typeIn) {
|
||||
var runner;
|
||||
|
||||
runner = new Clipperz.PM.UI.ExportController({type:typeIn, recordsInfo: recordsInfoIn});
|
||||
return runner.run();
|
||||
};
|
||||
|
||||
@@ -63,7 +63,9 @@ Clipperz.PM.UI.MainController = function() {
|
||||
this.registerForNotificationCenterEvents([
|
||||
'doLogin', 'registerNewUser', 'showRegistrationForm', 'goBack',
|
||||
'changePassphrase', 'deleteAccount',
|
||||
'export',
|
||||
// 'export',
|
||||
'downloadExport',
|
||||
'updateProgress',
|
||||
'toggleSelectionPanel', 'toggleSettingsPanel',
|
||||
'matchMediaQuery', 'unmatchMediaQuery',
|
||||
'selectAllCards', 'selectRecentCards', 'search', 'tagSelected', 'selectUntaggedCards',
|
||||
@@ -106,6 +108,10 @@ MochiKit.Base.update(Clipperz.PM.UI.MainController.prototype, {
|
||||
return this._overlay;
|
||||
},
|
||||
|
||||
updateProgress_handler: function (aProgressPercentage) {
|
||||
this.overlay().updateProgress(aProgressPercentage);
|
||||
},
|
||||
|
||||
loginForm: function () {
|
||||
return this._loginForm;
|
||||
},
|
||||
@@ -1240,8 +1246,24 @@ console.log("THE BROWSER IS OFFLINE");
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
export_handler: function(exportType) {
|
||||
return Clipperz.PM.UI.ExportController.exportJSON( this.recordsInfo(), exportType );
|
||||
// export_handler: function(exportType) {
|
||||
// return Clipperz.PM.UI.ExportController.exportJSON( this.recordsInfo(), exportType );
|
||||
// },
|
||||
|
||||
downloadExport_handler: function () {
|
||||
var exportController;
|
||||
var deferredResult;
|
||||
|
||||
exportController = new Clipperz.PM.UI.ExportController({'recordsInfo': this.recordsInfo()});
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred("MainController.downloadExport_handler", {trace: false});
|
||||
deferredResult.addMethod(this.overlay(), 'show', "exporting …", true, true);
|
||||
// deferredResult.addCallback(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'toggleSettingsPanel');
|
||||
deferredResult.addMethod(exportController, 'run');
|
||||
deferredResult.addMethod(this.overlay(), 'done', "", 1);
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user