mirror of
				http://git.whoc.org.uk/git/password-manager.git
				synced 2025-10-31 19:27:34 +01:00 
			
		
		
		
	Improved IE11 compatibility and fixed attachments on brandNew cards
This commit is contained in:
		| @@ -119,7 +119,6 @@ http://jonibologna.com/flexbox-cheatsheet/ | ||||
|     -ms-transform: rotate(0deg) translate(0, 0); | ||||
|     -o-transform: rotate(0deg) translate(0, 0); | ||||
|     transform: rotate(0deg) translate(0, 0); } | ||||
| 
 | ||||
|   100% { | ||||
|     -webkit-transform: rotate(359deg) translate(0, 0); | ||||
|     -moz-transform: rotate(359deg) translate(0, 0); | ||||
| @@ -133,7 +132,6 @@ http://jonibologna.com/flexbox-cheatsheet/ | ||||
|     -ms-transform: rotate(0deg) translate(0, 0); | ||||
|     -o-transform: rotate(0deg) translate(0, 0); | ||||
|     transform: rotate(0deg) translate(0, 0); } | ||||
| 
 | ||||
|   100% { | ||||
|     -webkit-transform: rotate(359deg) translate(0, 0); | ||||
|     -moz-transform: rotate(359deg) translate(0, 0); | ||||
| @@ -147,7 +145,6 @@ http://jonibologna.com/flexbox-cheatsheet/ | ||||
|     -ms-transform: rotate(0deg) translate(0, 0); | ||||
|     -o-transform: rotate(0deg) translate(0, 0); | ||||
|     transform: rotate(0deg) translate(0, 0); } | ||||
| 
 | ||||
|   100% { | ||||
|     -webkit-transform: rotate(359deg) translate(0, 0); | ||||
|     -moz-transform: rotate(359deg) translate(0, 0); | ||||
| @@ -161,7 +158,6 @@ http://jonibologna.com/flexbox-cheatsheet/ | ||||
|     -ms-transform: rotate(0deg) translate(0, 0); | ||||
|     -o-transform: rotate(0deg) translate(0, 0); | ||||
|     transform: rotate(0deg) translate(0, 0); } | ||||
| 
 | ||||
|   100% { | ||||
|     -webkit-transform: rotate(359deg) translate(0, 0); | ||||
|     -moz-transform: rotate(359deg) translate(0, 0); | ||||
| @@ -480,73 +476,61 @@ div.overlay { | ||||
| @-webkit-keyframes overlay-spin { | ||||
|   from { | ||||
|     opacity: 1; } | ||||
| 
 | ||||
|   to { | ||||
|     opacity: 0.25; } } | ||||
| @-moz-keyframes overlay-spin { | ||||
|   from { | ||||
|     opacity: 1; } | ||||
| 
 | ||||
|   to { | ||||
|     opacity: 0.25; } } | ||||
| @-ms-keyframes overlay-spin { | ||||
|   from { | ||||
|     opacity: 1; } | ||||
| 
 | ||||
|   to { | ||||
|     opacity: 0.25; } } | ||||
| @keyframes overlay-spin { | ||||
|   from { | ||||
|     opacity: 1; } | ||||
| 
 | ||||
|   to { | ||||
|     opacity: 0.25; } } | ||||
| @-webkit-keyframes ios-overlay-show { | ||||
|   0% { | ||||
|     opacity: 0; } | ||||
| 
 | ||||
|   100% { | ||||
|     opacity: 1; } } | ||||
| @-moz-keyframes ios-overlay-show { | ||||
|   0% { | ||||
|     opacity: 0; } | ||||
| 
 | ||||
|   100% { | ||||
|     opacity: 1; } } | ||||
| @-ms-keyframes ios-overlay-show { | ||||
|   0% { | ||||
|     opacity: 0; } | ||||
| 
 | ||||
|   100% { | ||||
|     opacity: 1; } } | ||||
| @keyframes ios-overlay-show { | ||||
|   0% { | ||||
|     opacity: 0; } | ||||
| 
 | ||||
|   100% { | ||||
|     opacity: 1; } } | ||||
| @-webkit-keyframes ios-overlay-hide { | ||||
|   0% { | ||||
|     opacity: 1; } | ||||
| 
 | ||||
|   100% { | ||||
|     opacity: 0; } } | ||||
| @-moz-keyframes ios-overlay-hide { | ||||
|   0% { | ||||
|     opacity: 1; } | ||||
| 
 | ||||
|   100% { | ||||
|     opacity: 0; } } | ||||
| @-ms-keyframes ios-overlay-hide { | ||||
|   0% { | ||||
|     opacity: 1; } | ||||
| 
 | ||||
|   100% { | ||||
|     opacity: 0; } } | ||||
| @keyframes ios-overlay-hide { | ||||
|   0% { | ||||
|     opacity: 1; } | ||||
| 
 | ||||
|   100% { | ||||
|     opacity: 0; } } | ||||
| /* | ||||
| @@ -2495,13 +2479,13 @@ span.count { | ||||
|   #extraFeaturesPanel .extraFeatureIndex footer { | ||||
|     font-size: 8pt; | ||||
|     padding: 5px 5px 5px 5px; | ||||
|     border-top: 1px solid #999999; } | ||||
|     border-top: 1px solid #999; } | ||||
|     #extraFeaturesPanel .extraFeatureIndex footer span { | ||||
|       color: #999999; } | ||||
|       color: #999; } | ||||
|       #extraFeaturesPanel .extraFeatureIndex footer span:after { | ||||
|         content: ":"; } | ||||
|     #extraFeaturesPanel .extraFeatureIndex footer a { | ||||
|       color: #999999; | ||||
|       color: #999; | ||||
|       text-decoration: none; | ||||
|       padding-left: 5px; | ||||
|       font-weight: bold; } | ||||
| @@ -3299,7 +3283,7 @@ div.cardList ul { | ||||
|       padding-right: 0px; | ||||
|       box-shadow: -4px 0px 3px -1px rgba(0, 0, 0, 0.2); } | ||||
|     div.cardList ul li.archived { | ||||
|       background-color: #eeeeee; | ||||
|       background-color: #eee; | ||||
|       color: #999; } | ||||
|     div.cardList ul li .favicon { | ||||
|       width: 48px; | ||||
| @@ -3399,7 +3383,7 @@ div.cardList.narrow { | ||||
|     content: ""; } | ||||
| 
 | ||||
| #cardDetailPage .view.archived, .cardDetail .view.archived { | ||||
|   background-color: #eeeeee; } | ||||
|   background-color: #eee; } | ||||
| #cardDetailPage .view .cardDetailToolbar, .cardDetail .view .cardDetailToolbar { | ||||
|   background-color: #1863a1; | ||||
|   color: white; } | ||||
| @@ -3648,8 +3632,9 @@ div.cardList.narrow { | ||||
|         width: 100px; | ||||
|         text-align: right; } | ||||
|         .cardAttachments .attachmentList li .status .waiting { | ||||
|           white-space: pre; | ||||
|           font-size: 10pt; | ||||
|           color: #aaa; } | ||||
|           color: grey; } | ||||
|       .cardAttachments .attachmentList li .actions { | ||||
|         -moz-user-select: -moz-none; | ||||
|         -khtml-user-select: none; | ||||
| @@ -3848,7 +3833,7 @@ div.cardList.narrow { | ||||
|       cursor: grab; | ||||
|       cursor: -moz-grab; | ||||
|       cursor: -webkit-grab; | ||||
|       background: repeating-linear-gradient(0deg, white, white 2px, #dddddd 2px, #dddddd 3px); | ||||
|       background: repeating-linear-gradient(0deg, white, white 2px, #ddd 2px, #ddd 3px); | ||||
|       width: 28px; | ||||
|       height: 20px; | ||||
|       margin-left: 6px; | ||||
| @@ -4095,7 +4080,7 @@ div.cardList.narrow { | ||||
|     min-width: 220px; | ||||
|     width: 80%; | ||||
|     max-width: 400px; | ||||
|     background-color: #333333; | ||||
|     background-color: #333; | ||||
|     color: #fff; | ||||
|     -webkit-border-radius: 6px; | ||||
|     -moz-border-radius: 6px; | ||||
| @@ -4110,7 +4095,7 @@ div.cardList.narrow { | ||||
|       margin-left: 0px; | ||||
|       width: 0; | ||||
|       height: 0; | ||||
|       border-top: 5px solid #333333; | ||||
|       border-top: 5px solid #333; | ||||
|       border-left: 5px solid transparent; | ||||
|       border-right: 5px solid transparent; } | ||||
|     .passwordGenerator .passwordGeneratorBaloon form span { | ||||
|   | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -43,6 +43,8 @@ Clipperz.PM.UI.AttachmentController = function(someParameters) { | ||||
| 		this.downloadMessageCallback    = someParameters['downloadMessageCallback']; | ||||
| 		this.reloadServerStatusCallback = someParameters['reloadServerStatusCallback']; | ||||
|  | ||||
| 		this.cryptoObject = window.crypto || window.msCrypto; | ||||
|  | ||||
| 		return this; | ||||
| 	} | ||||
|  | ||||
| @@ -345,7 +347,7 @@ MochiKit.Base.update(Clipperz.PM.UI.AttachmentController.prototype, { | ||||
| 			'status': 'ENCRYPTING', | ||||
| 		}); | ||||
|  | ||||
| 		window.crypto.subtle.importKey( | ||||
| 		this.cryptoObject.subtle.importKey( | ||||
| 		    "raw", | ||||
| 		    aKey,								//this is an example jwk key, "raw" would be an ArrayBuffer | ||||
| 		    { name: "AES-CBC" },				//this is the algorithm options | ||||
| @@ -358,7 +360,7 @@ MochiKit.Base.update(Clipperz.PM.UI.AttachmentController.prototype, { | ||||
| 	}, | ||||
|  | ||||
| 	doEncrypt: function(aFileReference, anArrayBuffer, anIV, aWebcryptoKey) { | ||||
| 		window.crypto.subtle.encrypt( | ||||
| 		this.cryptoObject.subtle.encrypt( | ||||
| 			{ | ||||
| 				name: "AES-CBC", | ||||
| 				iv: anIV, | ||||
| @@ -481,7 +483,7 @@ MochiKit.Base.update(Clipperz.PM.UI.AttachmentController.prototype, { | ||||
| 			'status': 'DECRYPTING', | ||||
| 		}); | ||||
| 	 | ||||
| 		window.crypto.subtle.importKey( | ||||
| 		this.cryptoObject.subtle.importKey( | ||||
| 		    "raw", | ||||
| 		    aKey,								//this is an example jwk key, "raw" would be an ArrayBuffer | ||||
| 		    {name: "AES-CBC"},					//this is the algorithm options | ||||
| @@ -493,7 +495,7 @@ MochiKit.Base.update(Clipperz.PM.UI.AttachmentController.prototype, { | ||||
| 	}, | ||||
|  | ||||
| 	doDecrypt: function(aFileReference, anArrayBuffer, anIV, aWebcryptoKey) { | ||||
| 		window.crypto.subtle.decrypt( | ||||
| 		this.cryptoObject.subtle.decrypt( | ||||
| 		    {name: "AES-CBC", iv: anIV}, | ||||
| 		    aWebcryptoKey, | ||||
| 		    anArrayBuffer | ||||
| @@ -532,8 +534,9 @@ MochiKit.Base.update(Clipperz.PM.UI.AttachmentController.prototype, { | ||||
| 	 *  an exception is thrown also when the user manually cancels the file | ||||
| 	 *  processing. In this case the status remains 'CANCELED'. | ||||
| 	 */ | ||||
| 	handleException: function(aFileReference, aMessage) { | ||||
| 	handleException: function(aFileReference, aMessage, anException) { | ||||
| 		var queueElement = this.getQueueElement(aFileReference); | ||||
| 		var messageString = aMessage ? " (" + aMessage + ")" : ""; | ||||
|  | ||||
| 		if (queueElement['status'] != 'CANCELED') { | ||||
| 			this.updateFileInQueue(aFileReference, { | ||||
| @@ -542,7 +545,7 @@ MochiKit.Base.update(Clipperz.PM.UI.AttachmentController.prototype, { | ||||
| 		} | ||||
|  | ||||
| 		if (aMessage) { | ||||
| 			console.log("AttachmentController: caught exception (" + aMessage + ")"); | ||||
| 			console.log("AttachmentController: caught exception" + messageString + ":", anException); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
|   | ||||
| @@ -690,8 +690,7 @@ console.log("DROP");	//, anEvent); | ||||
| 	renderAttachmentProgress: function(aStatus, aServerStatus, aProgress) { | ||||
| 		var result; | ||||
|  | ||||
| 		var broken = (! aServerStatus && (! aStatus || aStatus == 'CANCELED' || aStatus == 'FAILED' || aStatus == 'DONE')); | ||||
| 		var queueOperationsInProgress = (aStatus != 'DONE' && aStatus != 'CANCELED' && aStatus != 'FAILED'); | ||||
| 		var queueOperationsInProgress = (aStatus && aStatus != 'DONE' && aStatus != 'CANCELED' && aStatus != 'FAILED'); | ||||
|  | ||||
| 		result = null; | ||||
| 		if (aStatus == 'UPLOADING' || aStatus == 'DOWNLOADING') { | ||||
| @@ -699,7 +698,7 @@ console.log("DROP");	//, anEvent); | ||||
| 				'progress': aProgress, | ||||
| 				'border': 1 | ||||
| 			}); | ||||
| 		} else if (! broken && aServerStatus != 'AVAILABLE' && queueOperationsInProgress) { | ||||
| 		} else if (queueOperationsInProgress) { | ||||
| 			result = Clipperz.PM.UI.Components.RadialProgressIndicator({ | ||||
| 				'progress': 0, | ||||
| 				'border': 1, | ||||
| @@ -723,7 +722,7 @@ console.log("DROP");	//, anEvent); | ||||
| 		} else if (status == 'UPLOADING' || status == 'DOWNLOADING') { | ||||
| 			var actionSymbol = (status == 'UPLOADING') ? "\u2b06" : "\u2b07"; | ||||
| 			result = React.DOM.span({'className': 'progressStatus'}, actionSymbol + Math.floor(aProgress*100) + '%'); | ||||
| 		} else if (aServerStatus != 'AVAILABLE') { | ||||
| 		} else if (aServerStatus != 'AVAILABLE' && ! this.props['_isBrandNew']) { | ||||
| 			switch(status) { | ||||
| 				case 'CANCELED': | ||||
| 					result = React.DOM.span({'className': 'broken'}, "canceled"); | ||||
| @@ -739,6 +738,8 @@ console.log("DROP");	//, anEvent); | ||||
| 			} | ||||
| 		} else if (queueOperationsInProgress) { | ||||
| 			result = React.DOM.span({'className': 'waiting'}, "\u2b07waiting"); | ||||
| 		} else if (this.props['_isBrandNew']) { | ||||
| 			result = React.DOM.span({'className': 'waiting'}, "waiting save"); | ||||
| 		} | ||||
|  | ||||
| 		return result; | ||||
| @@ -762,9 +763,9 @@ console.log("DROP");	//, anEvent); | ||||
| 		var queueInfo = this.props['attachmentQueueInfo'].elementFetchCallback(anAttachment._reference) || []; | ||||
| 		var queueStatus = queueInfo['status']; | ||||
| 		var serverStatus = this.props['attachmentServerStatus'][anAttachment._reference]; | ||||
| 		var broken = (! serverStatus && (! queueStatus || queueStatus == 'CANCELED' || queueStatus == 'FAILED' || queueStatus == 'DONE')); | ||||
|  | ||||
| // console.log(anAttachment['name'], queueStatus) | ||||
| 		var queueOperationsInProgress = (queueStatus && queueStatus != 'DONE' && queueStatus != 'CANCELED' && queueStatus != 'FAILED'); | ||||
| 		 | ||||
| 		var broken = (! serverStatus && ! queueOperationsInProgress && ! this.props['_isBrandNew']); | ||||
|  | ||||
| 		var status				= this.renderAttachmentStatus(queueStatus, serverStatus, queueInfo['requestProgress']); | ||||
| 		var actions				= this.renderAttachmentActions(queueStatus, serverStatus, anAttachment['_attachment']); | ||||
|   | ||||
| @@ -298,7 +298,7 @@ Clipperz.PM.UI.Components.Cards.ViewClass = React.createClass({ | ||||
| 		} else if (status == 'UPLOADING' || status == 'DOWNLOADING') { | ||||
| 			var actionSymbol = (status == 'UPLOADING') ? "\u2b06" : "\u2b07"; | ||||
| 			result = React.DOM.span({'className': 'progressStatus'}, actionSymbol + Math.floor(aProgress*100) + '%'); | ||||
| 		} else if (aServerStatus != 'AVAILABLE') { | ||||
| 		} else if (aServerStatus != 'AVAILABLE' && ! this.props['_isBrandNew']) { | ||||
| 			switch(status) { | ||||
| 				case 'CANCELED': | ||||
| 					result = React.DOM.span({'className': 'broken'}, "canceled"); | ||||
| @@ -314,11 +314,12 @@ Clipperz.PM.UI.Components.Cards.ViewClass = React.createClass({ | ||||
| 			} | ||||
| 		} else if (queueOperationsInProgress) { | ||||
| 			result = React.DOM.span({'className': 'waiting'}, "\u2b07waiting"); | ||||
| 		} else if (this.props['_isBrandNew']) { | ||||
| 			result = React.DOM.span({'className': 'waiting'}, "waiting save"); | ||||
| 		} | ||||
|  | ||||
| 		return result; | ||||
| 	}, | ||||
|  | ||||
| 	renderAttachmentActions: function(aStatus, aServerStatus, anAttachment) { | ||||
| 		var result; | ||||
|  | ||||
| @@ -349,7 +350,9 @@ Clipperz.PM.UI.Components.Cards.ViewClass = React.createClass({ | ||||
| 			var queueInfo = this.props['attachmentQueueInfo'].elementFetchCallback(anAttachment._reference) || []; | ||||
| 			var queueStatus = queueInfo['status']; | ||||
| 			var serverStatus = this.props['attachmentServerStatus'][anAttachment._reference]; | ||||
| 			var broken = (! serverStatus && (! queueStatus || queueStatus == 'CANCELED')); | ||||
| 			var queueOperationsInProgress = (queueStatus && queueStatus != 'DONE' && queueStatus != 'CANCELED' && queueStatus != 'FAILED'); | ||||
|  | ||||
| 			var broken = (! serverStatus && ! queueOperationsInProgress && ! this.props['_isBrandNew']); | ||||
|  | ||||
| 			var status				= this.renderAttachmentStatus(queueStatus, serverStatus, queueInfo['requestProgress']); | ||||
| 			var actions				= this.renderAttachmentActions(queueStatus, serverStatus, anAttachment['_attachment']); | ||||
| @@ -420,6 +423,8 @@ Clipperz.PM.UI.Components.Cards.ViewClass = React.createClass({ | ||||
| 	render: function () { | ||||
| 		var	result; | ||||
|  | ||||
| console.log(this.props['_isBrandNew']); | ||||
|  | ||||
| 		if (this.props['loading'] == true) { | ||||
| 			result = this.renderLoading(); | ||||
| 		} else if (this.props['_reference']) { | ||||
|   | ||||
| @@ -43,8 +43,8 @@ Clipperz.PM.UI.Components.RadialProgressIndicatorClass = React.createClass({ | ||||
| 		var pi = Math.PI; | ||||
| 		var radiantAngle = 2 * pi * aProgress; | ||||
|  | ||||
| 		var x = Math.sin( radiantAngle ) * aRadius; | ||||
| 		var y = Math.cos( radiantAngle ) * - aRadius; | ||||
| 		var x = Math.sin(radiantAngle) * aRadius; | ||||
| 		var y = Math.cos(radiantAngle) * - aRadius; | ||||
| 		var mid = (aProgress > 0.5) ? 1 : 0; | ||||
|  | ||||
| 		return 'M 0 0 ' +									// Start from origin | ||||
| @@ -61,7 +61,7 @@ Clipperz.PM.UI.Components.RadialProgressIndicatorClass = React.createClass({ | ||||
|  | ||||
| 	//========================================================================= | ||||
|  | ||||
| 	getAdditionalClassesString(aList) { | ||||
| 	getAdditionalClassesString: function(aList) { | ||||
| 		var result; | ||||
|  | ||||
| 		aList = aList || []; | ||||
|   | ||||
| @@ -648,6 +648,8 @@ Clipperz.log("THE BROWSER IS OFFLINE"); | ||||
| 		deferredResult.setValue('_reference'); | ||||
| 		deferredResult.addMethod(aRecord, 'isArchived'); | ||||
| 		deferredResult.setValue('_isArchived'); | ||||
| 		deferredResult.addMethod(aRecord, 'isBrandNew'); | ||||
| 		deferredResult.setValue('_isBrandNew'); | ||||
| //		deferredResult.addMethod(aRecord, 'hasPendingChanges'); | ||||
| 		deferredResult.addMethod(this.user(), 'hasPendingChanges'); | ||||
| 		deferredResult.setValue('hasPendingChanges'); | ||||
| @@ -1784,10 +1786,25 @@ Clipperz.log("THE BROWSER IS OFFLINE"); | ||||
| 	saveCardEdits_handler: function (aRecordReference) { | ||||
| 		var	currentPage = this.pages()[this.currentPage()]; | ||||
| 		var	self = this; | ||||
| 		var record, wasBrandNew; | ||||
| 		 | ||||
| 		return Clipperz.Async.callbacks("MainController.saveCardEdits_handler", [ | ||||
| 			MochiKit.Base.method(currentPage, 'setProps', {'showGlobalMask':true}), | ||||
|  | ||||
| 			MochiKit.Base.method(this.user(), 'getRecord', aRecordReference), | ||||
| 			function(aRecord) { record = aRecord; wasBrandNew = aRecord.isBrandNew(); }, | ||||
|  | ||||
| 			MochiKit.Base.method(this, 'saveChanges'), | ||||
|  | ||||
| 			// When new record has attachments, server status should be updated as soon as it is available | ||||
| 			function() { return wasBrandNew; }, | ||||
| 			Clipperz.Async.deferredIf('WasBrandNew',[ | ||||
| 				function() { return record; }, | ||||
| 				MochiKit.Base.method(this, 'reloadAttachmentServerStatusCallback') | ||||
| 			], [ | ||||
| //				MochiKit.Async.succeed | ||||
| 			]), | ||||
| 						 | ||||
| 			MochiKit.Base.method(currentPage, 'setProps', {'mode':'view', 'showGlobalMask':false}), | ||||
| 			MochiKit.Base.method(this, 'refreshUI', aRecordReference), | ||||
| 			MochiKit.Base.partial(MochiKit.Signal.signal, Clipperz.Signal.NotificationCenter, 'enableLock'), | ||||
| @@ -2310,14 +2327,16 @@ Clipperz.log("THE BROWSER IS OFFLINE"); | ||||
| 	//---------------------------------------------------------------------------- | ||||
|  | ||||
| 	reloadAttachmentServerStatusCallback: function(aRecord) { | ||||
| 		return Clipperz.Async.callbacks("MainController.reloadAttachmentServerStatus_handler", [ | ||||
| 			MochiKit.Base.method(this.user(), 'getRecordDetail', aRecord), | ||||
| 			MochiKit.Base.bind(function () {  | ||||
| 				if (this._selectedCardInfo && this._selectedCardInfo['reference']) { | ||||
| 					return this.refreshUI(this._selectedCardInfo['reference']); | ||||
| 				} | ||||
| 			}, this), | ||||
| 		], {trace:false}); | ||||
| 		if (! aRecord.isBrandNew()) { | ||||
| 			return Clipperz.Async.callbacks("MainController.reloadAttachmentServerStatus_handler", [ | ||||
| 				MochiKit.Base.method(this.user(), 'getRecordDetail', aRecord), | ||||
| 				MochiKit.Base.bind(function () {  | ||||
| 					if (this._selectedCardInfo && this._selectedCardInfo['reference']) { | ||||
| 						this.refreshUI(this._selectedCardInfo['reference']); | ||||
| 					} | ||||
| 				}, this), | ||||
| 			], {trace:false}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	//============================================================================ | ||||
|   | ||||
| @@ -476,8 +476,9 @@ $cardViewBasePadding: 10px; | ||||
| 				text-align: right; | ||||
|  | ||||
| 				.waiting { | ||||
| 					white-space: pre; | ||||
| 					font-size: 10pt; | ||||
| 					color: #aaa; | ||||
| 					color: grey; | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Dario Chiappetta
					Dario Chiappetta