diff --git a/frontend/delta/css/clipperz.css b/frontend/delta/css/clipperz.css index 2c6fab1..16b9b6a 100644 --- a/frontend/delta/css/clipperz.css +++ b/frontend/delta/css/clipperz.css @@ -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,65 +2115,88 @@ span.count { #extraFeaturesPanel .extraFeatureContent .extraFeature h1 { font-size: 20pt; padding-bottom: 20px; } - #extraFeaturesPanel .extraFeatureContent form label { - display: none; } - #extraFeaturesPanel .extraFeatureContent form input { - display: block; - font-size: 18pt; - margin-bottom: 8px; - padding: 6px 10px; - border: 0px solid white; - width: 350px; - color: black; } - #extraFeaturesPanel .extraFeatureContent form input.invalid { - border: 0px solid #ff9900; - color: gray; } - #extraFeaturesPanel .extraFeatureContent form p { - display: -webkit-box; - display: -webkit-flex; - display: -moz-flex; - display: -ms-flexbox; - display: flex; - -webkit-box-direction: normal; - -webkit-box-orient: horizontal; - -webkit-flex-direction: row; - -moz-flex-direction: row; - -ms-flex-direction: row; - flex-direction: row; } - #extraFeaturesPanel .extraFeatureContent form p input { - width: 30px; - -webkit-box-flex: auto; - -webkit-flex: auto; - -moz-box-flex: auto; - -moz-flex: auto; - -ms-flex: auto; - flex: auto; } - #extraFeaturesPanel .extraFeatureContent form p span { - -webkit-box-flex: auto; - -webkit-flex: auto; - -moz-box-flex: auto; - -moz-flex: auto; - -ms-flex: auto; - flex: auto; - font-size: 12pt; } - #extraFeaturesPanel .extraFeatureContent form button { - font-family: "clipperz-font"; - color: white; - font-size: 14pt; - border: 0px; - margin-top: 20px; - padding: 6px 10px; - border: 1px solid white; - background-color: #ff9900; - -webkit-transition: background-color font-weight 0.2s linear; - -moz-transition: background-color font-weight 0.2s linear; - -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 { - font-weight: 100; - background-color: #c0c0c0; - cursor: default; } + #extraFeaturesPanel .extraFeatureContent .extraFeature form label { + display: none; } + #extraFeaturesPanel .extraFeatureContent .extraFeature form input { + display: block; + font-size: 18pt; + margin-bottom: 8px; + padding: 6px 10px; + border: 0px solid white; + width: 350px; + color: black; } + #extraFeaturesPanel .extraFeatureContent .extraFeature form input.invalid { + border: 0px solid #ff9900; + color: gray; } + #extraFeaturesPanel .extraFeatureContent .extraFeature form p { + display: -webkit-box; + display: -webkit-flex; + display: -moz-flex; + display: -ms-flexbox; + display: flex; + -webkit-box-direction: normal; + -webkit-box-orient: horizontal; + -webkit-flex-direction: row; + -moz-flex-direction: row; + -ms-flex-direction: row; + flex-direction: row; } + #extraFeaturesPanel .extraFeatureContent .extraFeature form p input { + width: 30px; + -webkit-box-flex: auto; + -webkit-flex: auto; + -moz-box-flex: auto; + -moz-flex: auto; + -ms-flex: auto; + flex: auto; } + #extraFeaturesPanel .extraFeatureContent .extraFeature form p span { + -webkit-box-flex: auto; + -webkit-flex: auto; + -moz-box-flex: auto; + -moz-flex: auto; + -ms-flex: auto; + flex: auto; + font-size: 12pt; } + #extraFeaturesPanel .extraFeatureContent .extraFeature form button { + font-family: "clipperz-font"; + color: white; + font-size: 14pt; + border: 0px; + margin-top: 20px; + padding: 6px 10px; + border: 1px solid white; + background-color: #ff9900; + -webkit-transition: background-color font-weight 0.2s linear; + -moz-transition: background-color font-weight 0.2s linear; + -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 .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; diff --git a/frontend/delta/html/index_template.html b/frontend/delta/html/index_template.html index 6f6c166..4514f08 100644 --- a/frontend/delta/html/index_template.html +++ b/frontend/delta/html/index_template.html @@ -85,6 +85,7 @@ Clipperz_normalizedNewLine = '\x0d\x0a'; loading
diff --git a/frontend/delta/js/Clipperz/PM/DataModel/Record.Version.js b/frontend/delta/js/Clipperz/PM/DataModel/Record.Version.js index da960b9..db91fad 100644 --- a/frontend/delta/js/Clipperz/PM/DataModel/Record.Version.js +++ b/frontend/delta/js/Clipperz/PM/DataModel/Record.Version.js @@ -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(); diff --git a/frontend/delta/js/Clipperz/PM/DataModel/Record.js b/frontend/delta/js/Clipperz/PM/DataModel/Record.js index aa3311b..5abe8c5 100644 --- a/frontend/delta/js/Clipperz/PM/DataModel/Record.js +++ b/frontend/delta/js/Clipperz/PM/DataModel/Record.js @@ -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; +}; diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DataExport.js b/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DataExport.js index 13ebe3f..fb2ceb4 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DataExport.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DataExport.js @@ -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"), + ]) +*/ + ]) + ]) ]); }, diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DeleteAccount.js b/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DeleteAccount.js index 3c71f0d..fc1d976 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DeleteAccount.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DeleteAccount.js @@ -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) + ]) ]) ]); }, diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DevicePIN.js b/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DevicePIN.js index 45c835e..9c8e938 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DevicePIN.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/DevicePIN.js @@ -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']) + ]) ]); }, diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/Passphrase.js b/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/Passphrase.js index a286d3a..49f7aeb 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/Passphrase.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/ExtraFeatures/Passphrase.js @@ -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"), + ]) + ]) ]); }, diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js b/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js index f9f6bcd..01d8a77 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/Overlay.js @@ -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; }, diff --git a/frontend/delta/js/Clipperz/PM/UI/Components/Panels/ExtraFeaturesPanel.js b/frontend/delta/js/Clipperz/PM/UI/Components/Panels/ExtraFeaturesPanel.js index 92609e5..f27ccd7 100644 --- a/frontend/delta/js/Clipperz/PM/UI/Components/Panels/ExtraFeaturesPanel.js +++ b/frontend/delta/js/Clipperz/PM/UI/Components/Panels/ExtraFeaturesPanel.js @@ -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, ]); } diff --git a/frontend/delta/js/Clipperz/PM/UI/ExportController.js b/frontend/delta/js/Clipperz/PM/UI/ExportController.js index 11c5207..a4bd81c 100644 --- a/frontend/delta/js/Clipperz/PM/UI/ExportController.js +++ b/frontend/delta/js/Clipperz/PM/UI/ExportController.js @@ -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 ' +The following error occured while exporting your data:
"+ - ""+errorMessage+"
"
- );
+ 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();
-};
diff --git a/frontend/delta/js/Clipperz/PM/UI/MainController.js b/frontend/delta/js/Clipperz/PM/UI/MainController.js
index 7b8a5cb..b26adbb 100644
--- a/frontend/delta/js/Clipperz/PM/UI/MainController.js
+++ b/frontend/delta/js/Clipperz/PM/UI/MainController.js
@@ -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;
},
//----------------------------------------------------------------------------
diff --git a/frontend/delta/js/FileSaver/Blob.js b/frontend/delta/js/FileSaver/Blob.js
new file mode 100644
index 0000000..2e2d360
--- /dev/null
+++ b/frontend/delta/js/FileSaver/Blob.js
@@ -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));
\ No newline at end of file
diff --git a/frontend/delta/js/FileSaver/FileSaver.js b/frontend/delta/js/FileSaver/FileSaver.js
new file mode 100644
index 0000000..c43c5f6
--- /dev/null
+++ b/frontend/delta/js/FileSaver/FileSaver.js
@@ -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;
+ });
+}
\ No newline at end of file
diff --git a/frontend/delta/properties/delta.properties.json b/frontend/delta/properties/delta.properties.json
index bbfa4a4..b53c099 100644
--- a/frontend/delta/properties/delta.properties.json
+++ b/frontend/delta/properties/delta.properties.json
@@ -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",
diff --git a/frontend/delta/scss/core/layout.scss b/frontend/delta/scss/core/layout.scss
index ce969d3..271cbf0 100644
--- a/frontend/delta/scss/core/layout.scss
+++ b/frontend/delta/scss/core/layout.scss
@@ -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();
+ }
+ }
}
}
diff --git a/frontend/delta/scss/core/overlay.scss b/frontend/delta/scss/core/overlay.scss
index 5ba1c05..ce5ea83 100644
--- a/frontend/delta/scss/core/overlay.scss
+++ b/frontend/delta/scss/core/overlay.scss
@@ -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);
+ }
+ }
}
//========================================================
diff --git a/frontend/delta/scss/style/settingsPanel.scss b/frontend/delta/scss/style/settingsPanel.scss
index a5126e9..8a8ce7a 100644
--- a/frontend/delta/scss/style/settingsPanel.scss
+++ b/frontend/delta/scss/style/settingsPanel.scss
@@ -86,6 +86,10 @@ refer to http://www.clipperz.com.
& > div {
padding: 4px;
}
+
+ &.offlineCopy {
+ cursor: default;
+ }
}
&.open {
@@ -178,6 +182,118 @@ refer to http://www.clipperz.com.
font-size: 20pt;
padding-bottom: 20px;
}
+
+ form {
+
+ label {
+ display: none;
+ }
+
+ input {
+ $border-size: 0px; // 2px;
+
+ display: block;
+ font-size: 18pt;
+ margin-bottom: 8px;
+ padding: (6px - $border-size) (10px - $border-size);
+ border: $border-size solid white;
+ width: 350px;
+ color: black;
+
+ &.invalid {
+ border: $border-size solid $clipperz-orange;
+ color: gray;
+ }
+ }
+
+ p {
+ @include flexbox;
+ @include flex-direction(row);
+
+ input {
+ width: 30px;
+ @include flex(auto);
+ }
+
+ span {
+ @include flex(auto);
+ font-size: 12pt;
+ }
+ }
+
+ button {
+ font-family: "clipperz-font";
+
+ color: white;
+ font-size: 14pt;
+ border: 0px;
+
+ margin-top: 20px;
+ padding: 6px 10px;
+
+ border: 1px solid white;
+ background-color: $main-color;
+ @include transition(background-color font-weight, 0.2s, linear);
+
+ &:hover {
+ };
+
+ &:disabled {
+ font-weight: 100;
+ background-color: #c0c0c0;
+ cursor: default;
+
+ &:hover {
+ };
+ }
+ }
+// input.valid:focus {
+// border: 2px solid $clipperz-blue;
+// }
+ }
+
+ 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 {
+ };
+ }
}
@@ -211,78 +327,6 @@ refer to http://www.clipperz.com.
}
*/
- form {
-
- label {
- display: none;
- }
-
- input {
- $border-size: 0px; // 2px;
-
- display: block;
- font-size: 18pt;
- margin-bottom: 8px;
- padding: (6px - $border-size) (10px - $border-size);
- border: $border-size solid white;
- width: 350px;
- color: black;
-
- &.invalid {
- border: $border-size solid $clipperz-orange;
- color: gray;
- }
- }
-
- p {
- @include flexbox;
- @include flex-direction(row);
-
- input {
- width: 30px;
- @include flex(auto);
- }
-
- span {
- @include flex(auto);
- font-size: 12pt;
- }
- }
-
- 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;
- padding: 6px 10px;
-
- border: 1px solid white;
- background-color: $main-color;
- @include transition(background-color font-weight, 0.2s, linear);
-
- &:hover {
- };
-
- &:disabled {
- font-weight: 100;
- background-color: #c0c0c0;
- cursor: default;
-
- &:hover {
- };
- }
- }
-// input.valid:focus {
-// border: 2px solid $clipperz-blue;
-// }
- }
}
}