You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

407 lines
14 KiB

  1. define([
  2. 'jquery',
  3. '/common/common-util.js',
  4. '/common/common-hash.js',
  5. '/common/common-interface.js',
  6. '/common/hyperscript.js',
  7. '/common/media-tag.js',
  8. '/customize/messages.js',
  9. '/bower_components/tweetnacl/nacl-fast.min.js',
  10. '/bower_components/croppie/croppie.min.js',
  11. '/bower_components/file-saver/FileSaver.min.js',
  12. 'css!/bower_components/croppie/croppie.css',
  13. ], function ($, Util, Hash, UI, h, MediaTag, Messages) {
  14. var MT = {};
  15. var Nacl = window.nacl;
  16. // Configure MediaTags to use our local viewer
  17. if (MediaTag) {
  18. MediaTag.setDefaultConfig('pdf', {
  19. viewer: '/common/pdfjs/web/viewer.html'
  20. });
  21. }
  22. // Cache of the avatars outer html (including <media-tag>)
  23. var avatars = {};
  24. MT.getCursorAvatar = function (cursor) {
  25. var html = '<span class="cp-cursor-avatar">';
  26. html += (cursor.avatar && avatars[cursor.avatar]) || '';
  27. html += Util.fixHTML(cursor.name) + '</span>';
  28. return html;
  29. };
  30. MT.displayMediatagImage = function (Common, $tag, _cb) {
  31. var cb = Util.once(_cb);
  32. if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); }
  33. var observer = new MutationObserver(function(mutations) {
  34. mutations.forEach(function(mutation) {
  35. if (mutation.addedNodes.length) {
  36. if (mutation.addedNodes.length > 1 ||
  37. mutation.addedNodes[0].nodeName !== 'IMG') {
  38. return void cb('NOT_IMAGE');
  39. }
  40. var $image = $tag.find('img');
  41. var onLoad = function () {
  42. cb(null, $image);
  43. };
  44. if ($image[0].complete) { onLoad(); }
  45. $image.on('load', onLoad);
  46. }
  47. });
  48. });
  49. observer.observe($tag[0], {
  50. attributes: false,
  51. childList: true,
  52. characterData: false
  53. });
  54. MediaTag($tag[0]).on('error', function (data) {
  55. console.error(data);
  56. });
  57. };
  58. MT.displayAvatar = function (common, $container, href, name, _cb) {
  59. var cb = Util.once(Util.mkAsync(_cb || function () {}));
  60. var displayDefault = function () {
  61. var text = (href && typeof(href) === "string") ? href : Util.getFirstCharacter(name);
  62. var $avatar = $('<span>', {'class': 'cp-avatar-default'}).text(text);
  63. $container.append($avatar);
  64. if (cb) { cb(); }
  65. };
  66. if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol
  67. if (!href || href.length === 1) { return void displayDefault(); }
  68. if (avatars[href]) {
  69. var nodes = $.parseHTML(avatars[href]);
  70. var $el = $(nodes[0]);
  71. $container.append($el);
  72. return void cb($el);
  73. }
  74. var centerImage = function ($img, $image) {
  75. var img = $image[0];
  76. var w = img.width;
  77. var h = img.height;
  78. if (w>h) {
  79. $image.css('max-height', '100%');
  80. $img.css('flex-direction', 'column');
  81. avatars[href] = $img[0].outerHTML;
  82. if (cb) { cb($img); }
  83. return;
  84. }
  85. $image.css('max-width', '100%');
  86. $img.css('flex-direction', 'row');
  87. avatars[href] = $img[0].outerHTML;
  88. if (cb) { cb($img); }
  89. };
  90. // No password for avatars
  91. var privateData = common.getMetadataMgr().getPrivateData();
  92. var origin = privateData.fileHost || privateData.origin;
  93. var parsed = Hash.parsePadUrl(href);
  94. var secret = Hash.getSecrets('file', parsed.hash);
  95. if (secret.keys && secret.channel) {
  96. var hexFileName = secret.channel;
  97. var cryptKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
  98. var src = origin + Hash.getBlobPathFromHex(hexFileName);
  99. common.getFileSize(hexFileName, function (e, data) {
  100. if (e || !data) { return void displayDefault(); }
  101. if (typeof data !== "number") { return void displayDefault(); }
  102. if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); }
  103. var $img = $('<media-tag>').appendTo($container);
  104. $img.attr('src', src);
  105. $img.attr('data-crypto-key', 'cryptpad:' + cryptKey);
  106. MT.displayMediatagImage(common, $img, function (err, $image) {
  107. if (err) { return void console.error(err); }
  108. centerImage($img, $image);
  109. });
  110. });
  111. }
  112. };
  113. var transformAvatar = function (file, cb) {
  114. if (file.type === 'image/gif') { return void cb(file); }
  115. var $croppie = $('<div>', {
  116. 'class': 'cp-app-profile-resizer'
  117. });
  118. if (typeof ($croppie.croppie) !== "function") {
  119. return void cb(file);
  120. }
  121. var todo = function () {
  122. UI.confirm($croppie[0], function (yes) {
  123. if (!yes) { return; }
  124. $croppie.croppie('result', {
  125. type: 'blob',
  126. size: {width: 300, height: 300}
  127. }).then(function(blob) {
  128. blob.lastModifiedDate = new Date();
  129. blob.name = 'avatar';
  130. cb(blob);
  131. });
  132. });
  133. };
  134. var reader = new FileReader();
  135. reader.onload = function(e) {
  136. $croppie.croppie({
  137. url: e.target.result,
  138. viewport: { width: 100, height: 100 },
  139. boundary: { width: 400, height: 300 },
  140. });
  141. todo();
  142. };
  143. reader.readAsDataURL(file);
  144. };
  145. MT.addAvatar = function (common, cb) {
  146. var AVATAR_SIZE_LIMIT = 0.5;
  147. var allowedMediaTypes = [
  148. 'image/png',
  149. 'image/jpeg',
  150. 'image/jpg',
  151. 'image/gif',
  152. ];
  153. var fmConfig = {
  154. noHandlers: true,
  155. noStore: true,
  156. body: $('body'),
  157. onUploaded: cb
  158. };
  159. var FM = common.createFileManager(fmConfig);
  160. var accepted = ".gif,.jpg,.jpeg,.png";
  161. var data = {
  162. FM: FM,
  163. filter: function (file) {
  164. var sizeMB = Util.bytesToMegabytes(file.size);
  165. var type = file.type;
  166. // We can't resize .gif so we have to display an error if it is too big
  167. if (sizeMB > AVATAR_SIZE_LIMIT && type === 'image/gif') {
  168. UI.log(Messages._getKey('profile_uploadSizeError', [
  169. Messages._getKey('formattedMB', [AVATAR_SIZE_LIMIT])
  170. ]));
  171. return false;
  172. }
  173. // Display an error if the image type is not allowed
  174. if (allowedMediaTypes.indexOf(type) === -1) {
  175. UI.log(Messages._getKey('profile_uploadTypeError', [
  176. accepted.split(',').join(', ')
  177. ]));
  178. return false;
  179. }
  180. return true;
  181. },
  182. transformer: transformAvatar,
  183. accept: accepted
  184. };
  185. return data;
  186. };
  187. MT.getMediaTagPreview = function (common, tags, start) {
  188. if (!Array.isArray(tags) || !tags.length) { return; }
  189. var i = start;
  190. var metadataMgr = common.getMetadataMgr();
  191. var priv = metadataMgr.getPrivateData();
  192. var left, right;
  193. var modal = UI.createModal({
  194. id: 'cp-mediatag-preview-modal',
  195. $body: $('body')
  196. });
  197. modal.show();
  198. var $modal = modal.$modal.focus();
  199. var $container = $modal.find('.cp-modal').append([
  200. h('div.cp-mediatag-control', left = h('span.fa.fa-chevron-left')),
  201. h('div.cp-mediatag-container', [
  202. h('div.cp-loading-spinner-container', h('span.cp-spinner')),
  203. ]),
  204. h('div.cp-mediatag-control', right = h('span.fa.fa-chevron-right')),
  205. ]);
  206. var $left = $(left);
  207. var $right = $(right);
  208. var $inner = $container.find('.cp-mediatag-container');
  209. var $spinner = $container.find('.cp-loading-spinner-container');
  210. var locked = false;
  211. var show = function (_i) {
  212. if (locked) { return; }
  213. locked = true;
  214. if (_i < 0) { i = 0; }
  215. else if (_i > tags.length -1) { i = tags.length - 1; }
  216. else { i = _i; }
  217. // Show/hide controls
  218. $left.css('visibility', '');
  219. $right.css('visibility', '');
  220. if (i === 0) {
  221. $left.css('visibility', 'hidden');
  222. }
  223. if (i === tags.length - 1) {
  224. $right.css('visibility', 'hidden');
  225. }
  226. // Reset modal
  227. $inner.find('media-tag, pre.mermaid').detach();
  228. $spinner.show();
  229. // Check src and cryptkey
  230. var cfg = tags[i];
  231. var tag;
  232. if (cfg.svg) {
  233. $inner.append(cfg.svg);
  234. if (!cfg.render) {
  235. $spinner.hide();
  236. locked = false;
  237. return;
  238. }
  239. setTimeout(cfg.render);
  240. tag = cfg.svg;
  241. } else {
  242. var src = cfg.src;
  243. var key = cfg.key;
  244. if (cfg.href) {
  245. var parsed = Hash.parsePadUrl(cfg.href);
  246. var secret = Hash.getSecrets(parsed.type, parsed.hash, cfg.password);
  247. var host = priv.fileHost || priv.origin || '';
  248. src = host + Hash.getBlobPathFromHex(secret.channel);
  249. var _key = secret.keys && secret.keys.cryptKey;
  250. if (_key) { key = 'cryptpad:' + Nacl.util.encodeBase64(_key); }
  251. }
  252. if (!src || !key) {
  253. locked = false;
  254. $spinner.hide();
  255. return void UI.log(Messages.error);
  256. }
  257. tag = h('media-tag', {
  258. src: src,
  259. 'data-crypto-key': key
  260. });
  261. $inner.append(tag);
  262. setTimeout(function () {
  263. MediaTag(tag).on('error', function () {
  264. locked = false;
  265. $spinner.hide();
  266. UI.log(Messages.error);
  267. });
  268. });
  269. }
  270. var observer = new MutationObserver(function(mutations) {
  271. mutations.forEach(function() {
  272. locked = false;
  273. $spinner.hide();
  274. });
  275. });
  276. observer.observe(tag, {
  277. attributes: false,
  278. childList: true,
  279. characterData: false
  280. });
  281. };
  282. show(i);
  283. var previous = function () {
  284. if (i === 0) { return; }
  285. show(i - 1);
  286. };
  287. var next = function () {
  288. if (i === tags.length - 1) { return; }
  289. show(i + 1);
  290. };
  291. $left.click(previous);
  292. $right.click(next);
  293. $modal.on('keydown', function (e) {
  294. e.stopPropagation();
  295. });
  296. $modal.on('keyup', function (e) {
  297. //if (!Slide.shown) { return; }
  298. e.stopPropagation();
  299. if (e.ctrlKey) { return; }
  300. switch(e.which) {
  301. case 33: // pageup
  302. case 38: // up
  303. case 37: // left
  304. previous();
  305. break;
  306. case 34: // pagedown
  307. case 32: // space
  308. case 40: // down
  309. case 39: // right
  310. next();
  311. break;
  312. case 27: // esc
  313. $modal.hide();
  314. break;
  315. default:
  316. }
  317. });
  318. };
  319. var mediatagContextMenu;
  320. MT.importMediaTagMenu = function (common) {
  321. if (mediatagContextMenu) { return mediatagContextMenu; }
  322. // Create context menu
  323. var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [
  324. h('ul.dropdown-menu', {
  325. 'role': 'menu',
  326. 'aria-labelledBy': 'dropdownMenu',
  327. 'style': 'display:block;position:static;margin-bottom:5px;'
  328. }, [
  329. h('li.cp-svg', h('a.cp-app-code-context-open.dropdown-item', {
  330. 'tabindex': '-1',
  331. 'data-icon': "fa-eye",
  332. }, Messages.pad_mediatagPreview)),
  333. h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', {
  334. 'tabindex': '-1',
  335. 'data-icon': "fa-cloud-upload",
  336. }, Messages.pad_mediatagImport)),
  337. h('li', h('a.cp-app-code-context-download.dropdown-item', {
  338. 'tabindex': '-1',
  339. 'data-icon': "fa-download",
  340. }, Messages.download_mt_button)),
  341. ])
  342. ]);
  343. // create the icon for each contextmenu option
  344. $(menu).find("li a.dropdown-item").each(function (i, el) {
  345. var $icon = $("<span>");
  346. if ($(el).attr('data-icon')) {
  347. var font = $(el).attr('data-icon').indexOf('cptools') === 0 ? 'cptools' : 'fa';
  348. $icon.addClass(font).addClass($(el).attr('data-icon'));
  349. } else {
  350. $icon.text($(el).text());
  351. }
  352. $(el).prepend($icon);
  353. });
  354. var m = UI.createContextMenu(menu);
  355. mediatagContextMenu = m;
  356. var $menu = $(m.menu);
  357. $menu.on('click', 'a', function (e) {
  358. e.stopPropagation();
  359. m.hide();
  360. var $mt = $menu.data('mediatag');
  361. if ($(this).hasClass("cp-app-code-context-saveindrive")) {
  362. common.importMediaTag($mt);
  363. }
  364. else if ($(this).hasClass("cp-app-code-context-download")) {
  365. var media = $mt[0]._mediaObject;
  366. window.saveAs(media._blob.content, media.name);
  367. }
  368. else if ($(this).hasClass("cp-app-code-context-open")) {
  369. $mt.trigger('preview');
  370. }
  371. });
  372. return m;
  373. };
  374. return MT;
  375. });