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