Reviewed and improved export feature
This commit is contained in:
parent
b812ea4efb
commit
e329f6926a
@ -469,6 +469,21 @@ div.overlay {
|
||||
-ms-animation-delay: -0.0833s;
|
||||
-o-animation-delay: -0.0833s;
|
||||
animation-delay: -0.0833s; }
|
||||
div.overlay .progressBar {
|
||||
width: 100%;
|
||||
background-color: #222;
|
||||
height: 4px;
|
||||
margin-top: 86px;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px; }
|
||||
div.overlay .progressBar .progress {
|
||||
background-color: #999;
|
||||
height: 4px;
|
||||
display: block;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px; }
|
||||
|
||||
@-webkit-keyframes overlay-spin {
|
||||
from {
|
||||
@ -851,7 +866,8 @@ html {
|
||||
-moz-flex: auto;
|
||||
-ms-flex: auto;
|
||||
flex: auto;
|
||||
overflow: auto; }
|
||||
overflow: scroll;
|
||||
-webkit-overflow-scrolling: touch; }
|
||||
#extraFeaturesPanel .extraFeatureIndex footer {
|
||||
-webkit-box-flex: none;
|
||||
-webkit-flex: none;
|
||||
@ -869,6 +885,12 @@ html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: black; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature {
|
||||
height: 100%; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .content {
|
||||
height: 100%;
|
||||
overflow: scroll;
|
||||
-webkit-overflow-scrolling: touch; }
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
@ -2015,6 +2037,8 @@ span.count {
|
||||
background-color: #333; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div ul li > ul > li > div {
|
||||
padding: 4px; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div ul li > ul > li.offlineCopy {
|
||||
cursor: default; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div ul li h2 {
|
||||
font-weight: 300;
|
||||
font-size: 14pt; }
|
||||
@ -2091,9 +2115,9 @@ span.count {
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature h1 {
|
||||
font-size: 20pt;
|
||||
padding-bottom: 20px; }
|
||||
#extraFeaturesPanel .extraFeatureContent form label {
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form label {
|
||||
display: none; }
|
||||
#extraFeaturesPanel .extraFeatureContent form input {
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form input {
|
||||
display: block;
|
||||
font-size: 18pt;
|
||||
margin-bottom: 8px;
|
||||
@ -2101,10 +2125,10 @@ span.count {
|
||||
border: 0px solid white;
|
||||
width: 350px;
|
||||
color: black; }
|
||||
#extraFeaturesPanel .extraFeatureContent form input.invalid {
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form input.invalid {
|
||||
border: 0px solid #ff9900;
|
||||
color: gray; }
|
||||
#extraFeaturesPanel .extraFeatureContent form p {
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form p {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -moz-flex;
|
||||
@ -2116,7 +2140,7 @@ span.count {
|
||||
-moz-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row; }
|
||||
#extraFeaturesPanel .extraFeatureContent form p input {
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form p input {
|
||||
width: 30px;
|
||||
-webkit-box-flex: auto;
|
||||
-webkit-flex: auto;
|
||||
@ -2124,7 +2148,7 @@ span.count {
|
||||
-moz-flex: auto;
|
||||
-ms-flex: auto;
|
||||
flex: auto; }
|
||||
#extraFeaturesPanel .extraFeatureContent form p span {
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form p span {
|
||||
-webkit-box-flex: auto;
|
||||
-webkit-flex: auto;
|
||||
-moz-box-flex: auto;
|
||||
@ -2132,7 +2156,7 @@ span.count {
|
||||
-ms-flex: auto;
|
||||
flex: auto;
|
||||
font-size: 12pt; }
|
||||
#extraFeaturesPanel .extraFeatureContent form button {
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form button {
|
||||
font-family: "clipperz-font";
|
||||
color: white;
|
||||
font-size: 14pt;
|
||||
@ -2146,10 +2170,33 @@ span.count {
|
||||
-o-transition: background-color font-weight 0.2s linear;
|
||||
-ms-transition: background-color font-weight 0.2s linear;
|
||||
transition: background-color font-weight 0.2s linear; }
|
||||
#extraFeaturesPanel .extraFeatureContent form button:disabled {
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form button:disabled {
|
||||
font-weight: 100;
|
||||
background-color: #c0c0c0;
|
||||
cursor: default; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature ul {
|
||||
color: white; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature ul li {
|
||||
padding-bottom: 40px; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature h3 {
|
||||
font-size: 18pt; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .description {
|
||||
max-width: 500px;
|
||||
padding: 10px 0px 20px 0px; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .description p {
|
||||
font-size: 10pt;
|
||||
margin-bottom: 7px;
|
||||
line-height: 1.4em;
|
||||
color: #bbb; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .description p em {
|
||||
text-decoration: underline; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .button {
|
||||
display: inline;
|
||||
color: white;
|
||||
background-color: #ff9900;
|
||||
font-size: 14pt;
|
||||
border: 1px solid white;
|
||||
padding: 6px 10px; }
|
||||
|
||||
.mainPage.narrow #extraFeaturesPanel .extraFeatureContent header {
|
||||
display: block;
|
||||
|
@ -85,6 +85,7 @@ Clipperz_normalizedNewLine = '\x0d\x0a';
|
||||
</div>
|
||||
<span class="icon done" style="display:none">done</span>
|
||||
<span class="icon failed" style="display:none">failed</span>
|
||||
<span class="progressBar" style="display:none"><span class="progress"></span></span>
|
||||
<span class="title">loading</span>
|
||||
<div class="mask hidden"></div>
|
||||
</div>
|
||||
|
@ -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,6 +78,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DeleteAccountClass = React.createClass({
|
||||
|
||||
return React.DOM.div({className:'extraFeature deleteAccount'}, [
|
||||
React.DOM.h1({}, "Delete Account"),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.form({'key':'form', 'className':'deleteAccountForm', 'onChange': this.handleFormChange, 'onSubmit':this.handleDeleteAccount}, [
|
||||
React.DOM.div({'key':'fields'},[
|
||||
React.DOM.label({'key':'username-label', 'htmlFor' :'name'}, "username"),
|
||||
@ -92,6 +93,7 @@ Clipperz.PM.UI.Components.ExtraFeatures.DeleteAccountClass = React.createClass({
|
||||
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.div({'className': 'content'}, [
|
||||
React.DOM.h3({}, this.props['PIN'])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
|
@ -101,6 +101,7 @@ 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': '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"),
|
||||
@ -121,7 +122,8 @@ Clipperz.PM.UI.Components.ExtraFeatures.PassphraseClass = React.createClass({
|
||||
]),
|
||||
]),
|
||||
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 () {
|
||||
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,86 +21,110 @@ refer to http://www.clipperz.com.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI');
|
||||
|
||||
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();
|
||||
// https://github.com/eligrey/FileSaver.js
|
||||
// https://github.com/eligrey/Blob.js
|
||||
|
||||
Clipperz.PM.UI.ExportController = function(args) {
|
||||
this._recordsInfo = args['recordsInfo'] || Clipperz.Base.exception.raise('MandatoryParameter');
|
||||
this._processedRecords = 0;
|
||||
|
||||
this._style =
|
||||
"body {" +
|
||||
"font-family: 'Dejavu Sans', monospace;" +
|
||||
"margin: 0px;" +
|
||||
"}" +
|
||||
|
||||
"header {" +
|
||||
"padding: 10px;" +
|
||||
"border-bottom: 2px solid black;" +
|
||||
"}" +
|
||||
|
||||
"h1 {" +
|
||||
"margin: 0px;" +
|
||||
"}" +
|
||||
|
||||
"h2 {" +
|
||||
"margin: 0px;" +
|
||||
"padding-top: 10px;" +
|
||||
"}" +
|
||||
|
||||
"h3 {" +
|
||||
"margin: 0px;" +
|
||||
"}" +
|
||||
|
||||
"h5 {" +
|
||||
"margin: 0px;" +
|
||||
"color: gray;" +
|
||||
"}" +
|
||||
|
||||
"ul {" +
|
||||
"margin: 0px;" +
|
||||
"padding: 0px;" +
|
||||
"}" +
|
||||
|
||||
"div > ul > li {" +
|
||||
"border-bottom: 1px solid black;" +
|
||||
"padding: 10px;" +
|
||||
"}" +
|
||||
|
||||
"div > ul > li.archived {" +
|
||||
"background-color: #ddd;" +
|
||||
"}" +
|
||||
|
||||
|
||||
"ul > li > ul > li {" +
|
||||
"font-size: 9pt;" +
|
||||
"display: inline-block;" +
|
||||
"}" +
|
||||
|
||||
"ul > li > ul > li:after {" +
|
||||
"content: \",\";" +
|
||||
"padding-right: 5px;" +
|
||||
"}" +
|
||||
|
||||
"ul > li > ul > li:last-child:after {" +
|
||||
"content: \"\";" +
|
||||
"padding-right: 0px;" +
|
||||
"}" +
|
||||
|
||||
"dl {" +
|
||||
"}" +
|
||||
|
||||
"dt {" +
|
||||
"color: gray;" +
|
||||
"font-size: 9pt;" +
|
||||
"}" +
|
||||
|
||||
"dd {" +
|
||||
"margin: 0px;" +
|
||||
"margin-bottom: 5px;" +
|
||||
"padding-left: 10px;" +
|
||||
"}" +
|
||||
|
||||
"div > div {" +
|
||||
"background-color: black;" +
|
||||
"color: white;" +
|
||||
"padding: 10px;" +
|
||||
"}" +
|
||||
|
||||
"textarea {" +
|
||||
"width: 100%;" +
|
||||
"height: 200px;" +
|
||||
"}" +
|
||||
|
||||
"@media print {" +
|
||||
"div > div, header > div {" +
|
||||
"display: none !important;" +
|
||||
"}" +
|
||||
|
||||
"ul > li {" +
|
||||
"page-break-inside: avoid;" +
|
||||
"} " +
|
||||
"}" +
|
||||
|
||||
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;"+
|
||||
"}"+
|
||||
"";
|
||||
|
||||
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;
|
||||
},
|
||||
'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));
|
||||
|
||||
'setWindowBody': function (aWindow, anHTML) {
|
||||
aWindow.document.body.innerHTML = anHTML;
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'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'},"") )
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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));
|
||||
},
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
'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))
|
||||
);
|
||||
|
||||
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,
|
||||
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({},
|
||||
Object.keys(card.currentVersion.fields).map(function(key) {
|
||||
MochiKit.Base.map(function(key) {
|
||||
return [
|
||||
MochiKit.DOM.DT(card.currentVersion.fields[key].label),
|
||||
MochiKit.DOM.DD(card.currentVersion.fields[key].value),
|
||||
MochiKit.DOM.DT(jsonCardData.currentVersion.fields[key].label),
|
||||
MochiKit.DOM.DD(jsonCardData.currentVersion.fields[key].value),
|
||||
];
|
||||
})
|
||||
}, MochiKit.Base.keys(jsonCardData.currentVersion.fields))
|
||||
)
|
||||
);
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
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>"
|
||||
),
|
||||
jsonCardData.data.notes ? MochiKit.DOM.P({}, jsonCardData.data.notes) : null
|
||||
);
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
'renderToHtml': function (jsonData) {
|
||||
var title;
|
||||
var style;
|
||||
var date;
|
||||
var body;
|
||||
|
||||
'runExportJSON': function (aWindow) {
|
||||
var deferredResult;
|
||||
var exportedRecords;
|
||||
title = "Clipperz data";
|
||||
style = this._style;
|
||||
date = "dd/mm/yyyy";
|
||||
|
||||
var totalRecords = this.recordsInfo().length;
|
||||
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.")
|
||||
)
|
||||
),
|
||||
|
||||
exportedRecords = 0;
|
||||
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")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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);
|
||||
return '<html><head><title>' + title + '</title><style type="text/css">' + style + '</style></head><body>' + MochiKit.DOM.toHTML(body) + '</body></html>';
|
||||
},
|
||||
|
||||
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+'%';
|
||||
'saveResult': function (exportedJSON) {
|
||||
var blob;
|
||||
var sortedJSON;
|
||||
|
||||
return exportedRecord;
|
||||
}));
|
||||
dr.callback();
|
||||
return dr;
|
||||
});
|
||||
sortedJSON = MochiKit.Iter.sorted(exportedJSON, function(a,b) { return a.label.toUpperCase().localeCompare(b.label.toUpperCase()); } );
|
||||
|
||||
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();
|
||||
|
||||
return deferredResult;
|
||||
blob = new Blob([this.renderToHtml(sortedJSON)], {type: "text/html;charset=utf-8"});
|
||||
saveAs(blob, "clipperz_data.html");
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
|
||||
'run': function () {
|
||||
var newWindow;
|
||||
var deferredResult;
|
||||
var self = this;
|
||||
|
||||
newWindow = window.open("", this.target());
|
||||
deferredResult = new Clipperz.Async.Deferred("ExportController.run", {trace:false});
|
||||
deferredResult.addCallback(MochiKit.Base.map, function(recordIn) {
|
||||
var innerDeferredResult;
|
||||
|
||||
return this.runExportJSON(newWindow);
|
||||
},
|
||||
innerDeferredResult = new Clipperz.Async.Deferred("ExportController.run__exportRecord", {trace:false});
|
||||
innerDeferredResult.addMethod(recordIn._rowObject, 'export');
|
||||
innerDeferredResult.addMethod(self, 'reportRecordExport');
|
||||
innerDeferredResult.callback();
|
||||
|
||||
//=============================================================================
|
||||
return innerDeferredResult;
|
||||
});
|
||||
deferredResult.addCallback(Clipperz.Async.collectAll);
|
||||
deferredResult.addMethod(this, 'saveResult');
|
||||
deferredResult.callback(this.recordsInfo());
|
||||
|
||||
'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);
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//=============================================================================
|
||||
__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;
|
||||
},
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
234
frontend/delta/js/FileSaver/Blob.js
Normal file
234
frontend/delta/js/FileSaver/Blob.js
Normal file
@ -0,0 +1,234 @@
|
||||
/*
|
||||
|
||||
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/.
|
||||
|
||||
*/
|
||||
|
||||
/* Blob.js
|
||||
* A Blob implementation.
|
||||
* 2014-07-24
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
* By Devin Samarin, https://github.com/dsamarin
|
||||
* License: X11/MIT
|
||||
* See https://github.com/eligrey/Blob.js/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/*global self, unescape */
|
||||
/*jslint bitwise: true, regexp: true, confusion: true, es5: true, vars: true, white: true,
|
||||
plusplus: true */
|
||||
|
||||
/*! @source http://purl.eligrey.com/github/Blob.js/blob/master/Blob.js */
|
||||
|
||||
(function (view) {
|
||||
"use strict";
|
||||
|
||||
view.URL = view.URL || view.webkitURL;
|
||||
|
||||
if (view.Blob && view.URL) {
|
||||
try {
|
||||
new Blob;
|
||||
return;
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Internally we use a BlobBuilder implementation to base Blob off of
|
||||
// in order to support older browsers that only have BlobBuilder
|
||||
var BlobBuilder = view.BlobBuilder || view.WebKitBlobBuilder || view.MozBlobBuilder || (function(view) {
|
||||
var
|
||||
get_class = function(object) {
|
||||
return Object.prototype.toString.call(object).match(/^\[object\s(.*)\]$/)[1];
|
||||
}
|
||||
, FakeBlobBuilder = function BlobBuilder() {
|
||||
this.data = [];
|
||||
}
|
||||
, FakeBlob = function Blob(data, type, encoding) {
|
||||
this.data = data;
|
||||
this.size = data.length;
|
||||
this.type = type;
|
||||
this.encoding = encoding;
|
||||
}
|
||||
, FBB_proto = FakeBlobBuilder.prototype
|
||||
, FB_proto = FakeBlob.prototype
|
||||
, FileReaderSync = view.FileReaderSync
|
||||
, FileException = function(type) {
|
||||
this.code = this[this.name = type];
|
||||
}
|
||||
, file_ex_codes = (
|
||||
"NOT_FOUND_ERR SECURITY_ERR ABORT_ERR NOT_READABLE_ERR ENCODING_ERR "
|
||||
+ "NO_MODIFICATION_ALLOWED_ERR INVALID_STATE_ERR SYNTAX_ERR"
|
||||
).split(" ")
|
||||
, file_ex_code = file_ex_codes.length
|
||||
, real_URL = view.URL || view.webkitURL || view
|
||||
, real_create_object_URL = real_URL.createObjectURL
|
||||
, real_revoke_object_URL = real_URL.revokeObjectURL
|
||||
, URL = real_URL
|
||||
, btoa = view.btoa
|
||||
, atob = view.atob
|
||||
|
||||
, ArrayBuffer = view.ArrayBuffer
|
||||
, Uint8Array = view.Uint8Array
|
||||
|
||||
, origin = /^[\w-]+:\/*\[?[\w\.:-]+\]?(?::[0-9]+)?/
|
||||
;
|
||||
FakeBlob.fake = FB_proto.fake = true;
|
||||
while (file_ex_code--) {
|
||||
FileException.prototype[file_ex_codes[file_ex_code]] = file_ex_code + 1;
|
||||
}
|
||||
// Polyfill URL
|
||||
if (!real_URL.createObjectURL) {
|
||||
URL = view.URL = function(uri) {
|
||||
var
|
||||
uri_info = document.createElementNS("http://www.w3.org/1999/xhtml", "a")
|
||||
, uri_origin
|
||||
;
|
||||
uri_info.href = uri;
|
||||
if (!("origin" in uri_info)) {
|
||||
if (uri_info.protocol.toLowerCase() === "data:") {
|
||||
uri_info.origin = null;
|
||||
} else {
|
||||
uri_origin = uri.match(origin);
|
||||
uri_info.origin = uri_origin && uri_origin[1];
|
||||
}
|
||||
}
|
||||
return uri_info;
|
||||
};
|
||||
}
|
||||
URL.createObjectURL = function(blob) {
|
||||
var
|
||||
type = blob.type
|
||||
, data_URI_header
|
||||
;
|
||||
if (type === null) {
|
||||
type = "application/octet-stream";
|
||||
}
|
||||
if (blob instanceof FakeBlob) {
|
||||
data_URI_header = "data:" + type;
|
||||
if (blob.encoding === "base64") {
|
||||
return data_URI_header + ";base64," + blob.data;
|
||||
} else if (blob.encoding === "URI") {
|
||||
return data_URI_header + "," + decodeURIComponent(blob.data);
|
||||
} if (btoa) {
|
||||
return data_URI_header + ";base64," + btoa(blob.data);
|
||||
} else {
|
||||
return data_URI_header + "," + encodeURIComponent(blob.data);
|
||||
}
|
||||
} else if (real_create_object_URL) {
|
||||
return real_create_object_URL.call(real_URL, blob);
|
||||
}
|
||||
};
|
||||
URL.revokeObjectURL = function(object_URL) {
|
||||
if (object_URL.substring(0, 5) !== "data:" && real_revoke_object_URL) {
|
||||
real_revoke_object_URL.call(real_URL, object_URL);
|
||||
}
|
||||
};
|
||||
FBB_proto.append = function(data/*, endings*/) {
|
||||
var bb = this.data;
|
||||
// decode data to a binary string
|
||||
if (Uint8Array && (data instanceof ArrayBuffer || data instanceof Uint8Array)) {
|
||||
var
|
||||
str = ""
|
||||
, buf = new Uint8Array(data)
|
||||
, i = 0
|
||||
, buf_len = buf.length
|
||||
;
|
||||
for (; i < buf_len; i++) {
|
||||
str += String.fromCharCode(buf[i]);
|
||||
}
|
||||
bb.push(str);
|
||||
} else if (get_class(data) === "Blob" || get_class(data) === "File") {
|
||||
if (FileReaderSync) {
|
||||
var fr = new FileReaderSync;
|
||||
bb.push(fr.readAsBinaryString(data));
|
||||
} else {
|
||||
// async FileReader won't work as BlobBuilder is sync
|
||||
throw new FileException("NOT_READABLE_ERR");
|
||||
}
|
||||
} else if (data instanceof FakeBlob) {
|
||||
if (data.encoding === "base64" && atob) {
|
||||
bb.push(atob(data.data));
|
||||
} else if (data.encoding === "URI") {
|
||||
bb.push(decodeURIComponent(data.data));
|
||||
} else if (data.encoding === "raw") {
|
||||
bb.push(data.data);
|
||||
}
|
||||
} else {
|
||||
if (typeof data !== "string") {
|
||||
data += ""; // convert unsupported types to strings
|
||||
}
|
||||
// decode UTF-16 to binary string
|
||||
bb.push(unescape(encodeURIComponent(data)));
|
||||
}
|
||||
};
|
||||
FBB_proto.getBlob = function(type) {
|
||||
if (!arguments.length) {
|
||||
type = null;
|
||||
}
|
||||
return new FakeBlob(this.data.join(""), type, "raw");
|
||||
};
|
||||
FBB_proto.toString = function() {
|
||||
return "[object BlobBuilder]";
|
||||
};
|
||||
FB_proto.slice = function(start, end, type) {
|
||||
var args = arguments.length;
|
||||
if (args < 3) {
|
||||
type = null;
|
||||
}
|
||||
return new FakeBlob(
|
||||
this.data.slice(start, args > 1 ? end : this.data.length)
|
||||
, type
|
||||
, this.encoding
|
||||
);
|
||||
};
|
||||
FB_proto.toString = function() {
|
||||
return "[object Blob]";
|
||||
};
|
||||
FB_proto.close = function() {
|
||||
this.size = 0;
|
||||
delete this.data;
|
||||
};
|
||||
return FakeBlobBuilder;
|
||||
}(view));
|
||||
|
||||
view.Blob = function(blobParts, options) {
|
||||
var type = options ? (options.type || "") : "";
|
||||
var builder = new BlobBuilder();
|
||||
if (blobParts) {
|
||||
for (var i = 0, len = blobParts.length; i < len; i++) {
|
||||
if (Uint8Array && blobParts[i] instanceof Uint8Array) {
|
||||
builder.append(blobParts[i].buffer);
|
||||
}
|
||||
else {
|
||||
builder.append(blobParts[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
var blob = builder.getBlob(type);
|
||||
if (!blob.slice && blob.webkitSlice) {
|
||||
blob.slice = blob.webkitSlice;
|
||||
}
|
||||
return blob;
|
||||
};
|
||||
|
||||
var getPrototypeOf = Object.getPrototypeOf || function(object) {
|
||||
return object.__proto__;
|
||||
};
|
||||
view.Blob.prototype = getPrototypeOf(new view.Blob());
|
||||
}(typeof self !== "undefined" && self || typeof window !== "undefined" && window || this.content || this));
|
271
frontend/delta/js/FileSaver/FileSaver.js
Normal file
271
frontend/delta/js/FileSaver/FileSaver.js
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
|
||||
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/.
|
||||
|
||||
*/
|
||||
|
||||
/* FileSaver.js
|
||||
* A saveAs() FileSaver implementation.
|
||||
* 2015-03-04
|
||||
*
|
||||
* By Eli Grey, http://eligrey.com
|
||||
* License: X11/MIT
|
||||
* See https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
|
||||
*/
|
||||
|
||||
/*global self */
|
||||
/*jslint bitwise: true, indent: 4, laxbreak: true, laxcomma: true, smarttabs: true, plusplus: true */
|
||||
|
||||
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
|
||||
|
||||
var saveAs = saveAs
|
||||
// IE 10+ (native saveAs)
|
||||
|| (typeof navigator !== "undefined" &&
|
||||
navigator.msSaveOrOpenBlob && navigator.msSaveOrOpenBlob.bind(navigator))
|
||||
// Everyone else
|
||||
|| (function(view) {
|
||||
"use strict";
|
||||
// IE <10 is explicitly unsupported
|
||||
if (typeof navigator !== "undefined" &&
|
||||
/MSIE [1-9]\./.test(navigator.userAgent)) {
|
||||
return;
|
||||
}
|
||||
var
|
||||
doc = view.document
|
||||
// only get URL when necessary in case Blob.js hasn't overridden it yet
|
||||
, get_URL = function() {
|
||||
return view.URL || view.webkitURL || view;
|
||||
}
|
||||
, save_link = doc.createElementNS("http://www.w3.org/1999/xhtml", "a")
|
||||
, can_use_save_link = "download" in save_link
|
||||
, click = function(node) {
|
||||
var event = doc.createEvent("MouseEvents");
|
||||
event.initMouseEvent(
|
||||
"click", true, false, view, 0, 0, 0, 0, 0
|
||||
, false, false, false, false, 0, null
|
||||
);
|
||||
node.dispatchEvent(event);
|
||||
}
|
||||
, webkit_req_fs = view.webkitRequestFileSystem
|
||||
, req_fs = view.requestFileSystem || webkit_req_fs || view.mozRequestFileSystem
|
||||
, throw_outside = function(ex) {
|
||||
(view.setImmediate || view.setTimeout)(function() {
|
||||
throw ex;
|
||||
}, 0);
|
||||
}
|
||||
, force_saveable_type = "application/octet-stream"
|
||||
, fs_min_size = 0
|
||||
// See https://code.google.com/p/chromium/issues/detail?id=375297#c7 and
|
||||
// https://github.com/eligrey/FileSaver.js/commit/485930a#commitcomment-8768047
|
||||
// for the reasoning behind the timeout and revocation flow
|
||||
, arbitrary_revoke_timeout = 500 // in ms
|
||||
, revoke = function(file) {
|
||||
var revoker = function() {
|
||||
if (typeof file === "string") { // file is an object URL
|
||||
get_URL().revokeObjectURL(file);
|
||||
} else { // file is a File
|
||||
file.remove();
|
||||
}
|
||||
};
|
||||
if (view.chrome) {
|
||||
revoker();
|
||||
} else {
|
||||
setTimeout(revoker, arbitrary_revoke_timeout);
|
||||
}
|
||||
}
|
||||
, dispatch = function(filesaver, event_types, event) {
|
||||
event_types = [].concat(event_types);
|
||||
var i = event_types.length;
|
||||
while (i--) {
|
||||
var listener = filesaver["on" + event_types[i]];
|
||||
if (typeof listener === "function") {
|
||||
try {
|
||||
listener.call(filesaver, event || filesaver);
|
||||
} catch (ex) {
|
||||
throw_outside(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
, FileSaver = function(blob, name) {
|
||||
// First try a.download, then web filesystem, then object URLs
|
||||
var
|
||||
filesaver = this
|
||||
, type = blob.type
|
||||
, blob_changed = false
|
||||
, object_url
|
||||
, target_view
|
||||
, dispatch_all = function() {
|
||||
dispatch(filesaver, "writestart progress write writeend".split(" "));
|
||||
}
|
||||
// on any filesys errors revert to saving with object URLs
|
||||
, fs_error = function() {
|
||||
// don't create more object URLs than needed
|
||||
if (blob_changed || !object_url) {
|
||||
object_url = get_URL().createObjectURL(blob);
|
||||
}
|
||||
if (target_view) {
|
||||
target_view.location.href = object_url;
|
||||
} else {
|
||||
var new_tab = view.open(object_url, "_blank");
|
||||
if (new_tab == undefined && typeof safari !== "undefined") {
|
||||
//Apple do not allow window.open, see http://bit.ly/1kZffRI
|
||||
view.location.href = object_url
|
||||
}
|
||||
}
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch_all();
|
||||
revoke(object_url);
|
||||
}
|
||||
, abortable = function(func) {
|
||||
return function() {
|
||||
if (filesaver.readyState !== filesaver.DONE) {
|
||||
return func.apply(this, arguments);
|
||||
}
|
||||
};
|
||||
}
|
||||
, create_if_not_found = {create: true, exclusive: false}
|
||||
, slice
|
||||
;
|
||||
filesaver.readyState = filesaver.INIT;
|
||||
if (!name) {
|
||||
name = "download";
|
||||
}
|
||||
if (can_use_save_link) {
|
||||
object_url = get_URL().createObjectURL(blob);
|
||||
save_link.href = object_url;
|
||||
save_link.download = name;
|
||||
click(save_link);
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch_all();
|
||||
revoke(object_url);
|
||||
return;
|
||||
}
|
||||
// prepend BOM for UTF-8 XML and text/plain types
|
||||
if (/^\s*(?:text\/(?:plain|xml)|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(blob.type)) {
|
||||
blob = new Blob(["\ufeff", blob], {type: blob.type});
|
||||
}
|
||||
// Object and web filesystem URLs have a problem saving in Google Chrome when
|
||||
// viewed in a tab, so I force save with application/octet-stream
|
||||
// http://code.google.com/p/chromium/issues/detail?id=91158
|
||||
// Update: Google errantly closed 91158, I submitted it again:
|
||||
// https://code.google.com/p/chromium/issues/detail?id=389642
|
||||
if (view.chrome && type && type !== force_saveable_type) {
|
||||
slice = blob.slice || blob.webkitSlice;
|
||||
blob = slice.call(blob, 0, blob.size, force_saveable_type);
|
||||
blob_changed = true;
|
||||
}
|
||||
// Since I can't be sure that the guessed media type will trigger a download
|
||||
// in WebKit, I append .download to the filename.
|
||||
// https://bugs.webkit.org/show_bug.cgi?id=65440
|
||||
if (webkit_req_fs && name !== "download") {
|
||||
name += ".download";
|
||||
}
|
||||
if (type === force_saveable_type || webkit_req_fs) {
|
||||
target_view = view;
|
||||
}
|
||||
if (!req_fs) {
|
||||
fs_error();
|
||||
return;
|
||||
}
|
||||
fs_min_size += blob.size;
|
||||
req_fs(view.TEMPORARY, fs_min_size, abortable(function(fs) {
|
||||
fs.root.getDirectory("saved", create_if_not_found, abortable(function(dir) {
|
||||
var save = function() {
|
||||
dir.getFile(name, create_if_not_found, abortable(function(file) {
|
||||
file.createWriter(abortable(function(writer) {
|
||||
writer.onwriteend = function(event) {
|
||||
target_view.location.href = file.toURL();
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch(filesaver, "writeend", event);
|
||||
revoke(file);
|
||||
};
|
||||
writer.onerror = function() {
|
||||
var error = writer.error;
|
||||
if (error.code !== error.ABORT_ERR) {
|
||||
fs_error();
|
||||
}
|
||||
};
|
||||
"writestart progress write abort".split(" ").forEach(function(event) {
|
||||
writer["on" + event] = filesaver["on" + event];
|
||||
});
|
||||
writer.write(blob);
|
||||
filesaver.abort = function() {
|
||||
writer.abort();
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
};
|
||||
filesaver.readyState = filesaver.WRITING;
|
||||
}), fs_error);
|
||||
}), fs_error);
|
||||
};
|
||||
dir.getFile(name, {create: false}, abortable(function(file) {
|
||||
// delete file if it already exists
|
||||
file.remove();
|
||||
save();
|
||||
}), abortable(function(ex) {
|
||||
if (ex.code === ex.NOT_FOUND_ERR) {
|
||||
save();
|
||||
} else {
|
||||
fs_error();
|
||||
}
|
||||
}));
|
||||
}), fs_error);
|
||||
}), fs_error);
|
||||
}
|
||||
, FS_proto = FileSaver.prototype
|
||||
, saveAs = function(blob, name) {
|
||||
return new FileSaver(blob, name);
|
||||
}
|
||||
;
|
||||
FS_proto.abort = function() {
|
||||
var filesaver = this;
|
||||
filesaver.readyState = filesaver.DONE;
|
||||
dispatch(filesaver, "abort");
|
||||
};
|
||||
FS_proto.readyState = FS_proto.INIT = 0;
|
||||
FS_proto.WRITING = 1;
|
||||
FS_proto.DONE = 2;
|
||||
|
||||
FS_proto.error =
|
||||
FS_proto.onwritestart =
|
||||
FS_proto.onprogress =
|
||||
FS_proto.onwrite =
|
||||
FS_proto.onabort =
|
||||
FS_proto.onerror =
|
||||
FS_proto.onwriteend =
|
||||
null;
|
||||
|
||||
return saveAs;
|
||||
}(
|
||||
typeof self !== "undefined" && self
|
||||
|| typeof window !== "undefined" && window
|
||||
|| this.content
|
||||
));
|
||||
// `self` is undefined in Firefox for Android content script context
|
||||
// while `this` is nsIContentFrameMessageManager
|
||||
// with an attribute `content` that corresponds to the window
|
||||
|
||||
if (typeof module !== "undefined" && module.exports) {
|
||||
module.exports.saveAs = saveAs;
|
||||
} else if ((typeof define !== "undefined" && define !== null) && (define.amd != null)) {
|
||||
define([], function() {
|
||||
return saveAs;
|
||||
});
|
||||
}
|
@ -58,6 +58,9 @@
|
||||
"-- Modernizr/modernizr-2.8.2.js",
|
||||
"OnMediaQuery/onmediaquery-0.2.0.js",
|
||||
|
||||
"FileSaver/Blob.js",
|
||||
"FileSaver/FileSaver.js",
|
||||
|
||||
"-- IT WOULD BE NICE TO BE ABLE TO GET RID OF THESE IMPORTS",
|
||||
"Clipperz/YUI/Utils.js",
|
||||
"Clipperz/YUI/DomHelper.js",
|
||||
|
@ -209,9 +209,8 @@ html {
|
||||
|
||||
& > div {
|
||||
@include flex(auto);
|
||||
|
||||
overflow: auto;
|
||||
|
||||
// overflow: auto;
|
||||
@include overflow-scroll();
|
||||
}
|
||||
|
||||
footer {
|
||||
@ -226,6 +225,20 @@ html {
|
||||
height: 100%;
|
||||
// background-color: rgba( 0, 0, 0, 0.95);
|
||||
background-color: black;
|
||||
|
||||
.extraFeature {
|
||||
// @include flexbox();
|
||||
height: 100%;
|
||||
|
||||
h1 {
|
||||
// @include flex(none);
|
||||
}
|
||||
.content {
|
||||
// @include flex(auto);
|
||||
height: 100%;
|
||||
@include overflow-scroll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -126,6 +126,23 @@ div.overlay {
|
||||
div.bar11 {@include transform(300deg, 0, -142%); @include animation-delay(-0.16670s);}
|
||||
div.bar12 {@include transform(330deg, 0, -142%); @include animation-delay(-0.08330s);}
|
||||
}
|
||||
|
||||
.progressBar {
|
||||
// display: block;
|
||||
width: 100%;
|
||||
background-color: #222;
|
||||
height: 4px;
|
||||
margin-top: 86px;
|
||||
@include border-radius(2px);
|
||||
|
||||
.progress {
|
||||
background-color: #999;
|
||||
// width: 70%;
|
||||
height: 4px;
|
||||
display: block;
|
||||
@include border-radius(2px);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//========================================================
|
||||
|
@ -86,6 +86,10 @@ refer to http://www.clipperz.com.
|
||||
& > div {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
&.offlineCopy {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
&.open {
|
||||
@ -178,38 +182,6 @@ refer to http://www.clipperz.com.
|
||||
font-size: 20pt;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.changePassphraseForm {
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.deleteAccountForm {
|
||||
margin-top: 1em;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.confirmCheckbox {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
form {
|
||||
|
||||
@ -251,13 +223,9 @@ refer to http://www.clipperz.com.
|
||||
|
||||
button {
|
||||
font-family: "clipperz-font";
|
||||
// min-height: 48px;
|
||||
// min-width: 48px;
|
||||
|
||||
color: white;
|
||||
// font-size: 24pt;
|
||||
font-size: 14pt;
|
||||
// font-weight: 500;
|
||||
border: 0px;
|
||||
|
||||
margin-top: 20px;
|
||||
@ -284,6 +252,82 @@ refer to http://www.clipperz.com.
|
||||
// }
|
||||
}
|
||||
|
||||
ul {
|
||||
color: white;
|
||||
|
||||
li {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18pt;
|
||||
}
|
||||
|
||||
.description {
|
||||
max-width: 500px;
|
||||
padding: 10px 0px 20px 0px;
|
||||
|
||||
p {
|
||||
font-size: 10pt;
|
||||
margin-bottom: 7px;
|
||||
line-height: 1.4em;
|
||||
color:#bbb;
|
||||
|
||||
em {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
display: inline;
|
||||
|
||||
color: white;
|
||||
background-color: $main-color;
|
||||
|
||||
font-size: 14pt;
|
||||
|
||||
border: 1px solid white;
|
||||
padding: 6px 10px;
|
||||
|
||||
&:after {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
.changePassphraseForm {
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.deleteAccountForm {
|
||||
margin-top: 1em;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.confirmCheckbox {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user