message.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /**
  2. * This file is part of Threema Web.
  3. *
  4. * Threema Web is free software: you can redistribute it and/or modify it
  5. * under the terms of the GNU Affero General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or (at
  7. * your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful, but
  10. * WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero
  12. * General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Affero General Public License
  15. * along with Threema Web. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. // tslint:disable:max-line-length
  18. import {getSenderIdentity} from '../helpers/messages';
  19. import {MessageService} from '../services/message';
  20. import {ReceiverService} from '../services/receiver';
  21. import {WebClientService} from '../services/webclient';
  22. export default [
  23. 'WebClientService',
  24. 'MessageService',
  25. 'ReceiverService',
  26. '$mdDialog',
  27. '$mdToast',
  28. '$translate',
  29. '$rootScope',
  30. '$log',
  31. function(webClientService: WebClientService,
  32. messageService: MessageService,
  33. receiverService: ReceiverService,
  34. $mdDialog: ng.material.IDialogService,
  35. $mdToast: ng.material.IToastService,
  36. $translate: ng.translate.ITranslateService,
  37. $rootScope: ng.IRootScopeService,
  38. $log: ng.ILogService) {
  39. return {
  40. restrict: 'E',
  41. scope: {},
  42. bindToController: {
  43. type: '=eeeType',
  44. receiver: '=eeeReceiver',
  45. message: '=eeeMessage',
  46. resolution: '=?eeeResolution',
  47. },
  48. controllerAs: 'ctrl',
  49. controller: [function() {
  50. this.logTag = '[MessageDirective]';
  51. this.$onInit = function() {
  52. // Defaults and variables
  53. if (this.resolution == null) {
  54. this.resolution = 'low';
  55. }
  56. // Find contact
  57. this.contact = webClientService.contacts.get(
  58. getSenderIdentity(this.message, webClientService.me.id),
  59. );
  60. // Show...
  61. this.isStatusMessage = this.message.isStatus;
  62. this.isContactMessage = !this.message.isStatus
  63. && webClientService.contacts.has(getSenderIdentity(this.message, webClientService.me.id));
  64. this.isGroup = this.type as threema.ReceiverType === 'group';
  65. this.isContact = this.type as threema.ReceiverType === 'contact';
  66. this.isBusinessReceiver = receiverService.isBusinessContact(this.receiver);
  67. this.showName = !this.message.isOutbox && this.isGroup;
  68. // show avatar only if a name is shown
  69. this.showAvatar = this.showName;
  70. this.showText = this.message.type === 'text' || this.message.caption;
  71. this.showMedia = this.message.type !== 'text';
  72. this.showState = messageService.showStatusIcon(this.message as threema.Message, this.receiver);
  73. this.showQuote = this.message.quote !== undefined;
  74. this.showVoipInfo = this.message.type === 'voipStatus';
  75. this.access = messageService.getAccess(this.message, this.receiver);
  76. this.ack = (ack: boolean) => {
  77. webClientService.ackMessage(this.receiver, this.message, ack);
  78. };
  79. this.quote = () => {
  80. // set message as quoted
  81. webClientService.setQuote(this.receiver, this.message);
  82. };
  83. this.delete = (ev) => {
  84. const confirm = $mdDialog.confirm()
  85. .title($translate.instant('messenger.CONFIRM_DELETE_TITLE'))
  86. .textContent($translate.instant('common.ARE_YOU_SURE'))
  87. .targetEvent(ev)
  88. .ok($translate.instant('common.YES'))
  89. .cancel($translate.instant('common.CANCEL'));
  90. $mdDialog.show(confirm).then((result) => {
  91. webClientService.deleteMessage(this.receiver, this.message);
  92. }, () => { /* do nothing */});
  93. };
  94. this.copyToClipboard = (ev: MouseEvent) => {
  95. // Get copyable text
  96. const text = messageService.getQuoteText(this.message);
  97. if (text === null) {
  98. return;
  99. }
  100. // In order to copy the text to the clipboard,
  101. // put it into a temporary textarea element.
  102. const textArea = document.createElement('textarea');
  103. let toastString = 'messenger.COPY_ERROR';
  104. textArea.value = text;
  105. document.body.appendChild(textArea);
  106. textArea.focus();
  107. textArea.select();
  108. try {
  109. const successful = document.execCommand('copy');
  110. if (!successful) {
  111. $log.warn(this.logTag, 'Could not copy text to clipboard');
  112. } else {
  113. toastString = 'messenger.COPIED';
  114. }
  115. } catch (err) {
  116. $log.warn(this.logTag, 'Could not copy text to clipboard:', err);
  117. }
  118. document.body.removeChild(textArea);
  119. // Show toast
  120. const toast = $mdToast.simple()
  121. .textContent($translate.instant(toastString))
  122. .position('bottom center');
  123. $mdToast.show(toast);
  124. };
  125. this.download = (ev) => {
  126. this.downloading = true;
  127. webClientService.requestBlob(this.message.id, this.receiver)
  128. .then((blobInfo: threema.BlobInfo) => {
  129. $rootScope.$apply(() => {
  130. this.downloading = false;
  131. switch (this.message.type) {
  132. case 'image':
  133. case 'video':
  134. case 'file':
  135. case 'audio':
  136. saveAs(new Blob([blobInfo.buffer]), blobInfo.filename);
  137. break;
  138. default:
  139. $log.warn(this.logTag, 'Ignored download request for message type', this.message.type);
  140. }
  141. });
  142. })
  143. .catch((error) => {
  144. $log.error(this.logTag, 'Error downloading blob:', error);
  145. this.downloading = false;
  146. });
  147. };
  148. this.isDownloading = () => {
  149. return this.downloading;
  150. };
  151. this.showHistory = (ev) => {
  152. const getEvents = () => this.message.events;
  153. $mdDialog.show({
  154. controllerAs: 'ctrl',
  155. controller: function() {
  156. this.getEvents = getEvents;
  157. this.close = () => {
  158. $mdDialog.hide();
  159. };
  160. },
  161. template: `
  162. <md-dialog class="message-history-dialog" translate-attr="{'aria-label': 'messenger.MSG_HISTORY'}">
  163. <form ng-cloak>
  164. <md-toolbar>
  165. <div class="md-toolbar-tools">
  166. <h2 translate>messenger.MSG_HISTORY</h2>
  167. </div>
  168. </md-toolbar>
  169. <md-dialog-content>
  170. <p ng-repeat="event in ctrl.getEvents()">
  171. <span class="event-type" ng-if="event.type === 'created'" translate>messenger.MSG_HISTORY_CREATED</span>
  172. <span class="event-type" ng-if="event.type === 'sent'" translate>messenger.MSG_HISTORY_SENT</span>
  173. <span class="event-type" ng-if="event.type === 'delivered'" translate>messenger.MSG_HISTORY_DELIVERED</span>
  174. <span class="event-type" ng-if="event.type === 'read'" translate>messenger.MSG_HISTORY_READ</span>
  175. <span class="event-type" ng-if="event.type === 'acked'" translate>messenger.MSG_HISTORY_ACKED</span>
  176. <span class="event-type" ng-if="event.type === 'modified'" translate>messenger.MSG_HISTORY_MODIFIED</span>
  177. {{ event.date | unixToTimestring:true }}
  178. </p>
  179. </md-dialog-content>
  180. <md-dialog-actions layout="row" >
  181. <md-button ng-click="ctrl.close()">
  182. <span translate>common.OK</span>
  183. </md-button>
  184. </md-dialog-actions>
  185. </form>
  186. </md-dialog>
  187. `,
  188. parent: angular.element(document.body),
  189. targetEvent: ev,
  190. clickOutsideToClose: true,
  191. });
  192. };
  193. };
  194. }],
  195. link: function(scope: any, element: ng.IAugmentedJQuery, attrs) {
  196. // Prevent status messages from being draggable
  197. const domElement: HTMLElement = element[0];
  198. if (scope.ctrl.isStatusMessage) {
  199. domElement.ondragstart = () => false;
  200. }
  201. },
  202. templateUrl: 'directives/message.html',
  203. };
  204. },
  205. ];