cryptpad/www/common/inner/common-mediatag.js

568 lines
21 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

define([
'jquery',
'/common/common-util.js',
'/common/common-hash.js',
'/common/common-interface.js',
'/common/hyperscript.js',
'/common/media-tag.js',
'/customize/messages.js',
'/customize/application_config.js',
'/bower_components/tweetnacl/nacl-fast.min.js',
'/bower_components/croppie/croppie.min.js',
'/bower_components/file-saver/FileSaver.min.js',
'css!/bower_components/croppie/croppie.css',
], function ($, Util, Hash, UI, h, MediaTag, Messages, AppConfig) {
var MT = {};
var Nacl = window.nacl;
// Configure MediaTags to use our local viewer
// This file is loaded by sframe-common so the following config is used in all the inner apps
if (MediaTag) {
MediaTag.setDefaultConfig('pdf', {
viewer: '/lib/pdfjs/web/viewer.html'
});
MediaTag.setDefaultConfig('download', {
text: Messages.mediatag_saveButton,
textDl: Messages.mediatag_loadButton,
});
}
MT.MediaTag = MediaTag;
// Cache of the avatars outer html (including <media-tag>)
var avatars = {};
MT.getMediaTag = function (common, data) {
var metadataMgr = common.getMetadataMgr();
var privateDat = metadataMgr.getPrivateData();
var origin = privateDat.fileHost || privateDat.origin;
var src = data.src = data.src.slice(0,1) === '/' ? origin + data.src : data.src;
return h('media-tag', {
src: src,
'data-crypto-key': 'cryptpad:'+data.key
});
};
var animal_avatars = {};
MT.getCursorAvatar = function (cursor) {
var uid = cursor.uid;
// TODO it would be nice to have "{0} is editing" instead of just their name
var html = '<span class="cp-cursor-avatar">';
if (cursor.avatar && avatars[cursor.avatar]) {
html += avatars[cursor.avatar];
} else if (animal_avatars[uid]) {
html += animal_avatars[uid] + ' ';
}
html += Util.fixHTML(cursor.name) + '</span>';
return html;
};
MT.displayMediatagImage = function (Common, $tag, _cb) {
var cb = Util.once(_cb);
if (!$tag.length || !$tag.is('media-tag')) { return void cb('NOT_MEDIATAG'); }
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function(mutation) {
if (mutation.addedNodes.length) {
if (mutation.addedNodes.length > 1 ||
mutation.addedNodes[0].nodeName !== 'IMG') {
return void cb('NOT_IMAGE');
}
var $image = $tag.find('img');
var onLoad = function () {
cb(null, $image);
};
if ($image[0].complete) { onLoad(); }
$image.on('load', onLoad);
}
});
});
observer.observe($tag[0], {
attributes: false,
childList: true,
characterData: false
});
MediaTag($tag[0], {force: true}).on('error', function (data) {
console.error(data);
});
};
// https://emojipedia.org/nature/
var ANIMALS = AppConfig.emojiAvatars || [];
var getPseudorandomAnimal = MT.getPseudorandomAnimal = function (seed) {
if (!ANIMALS.length) { return ''; }
if (typeof(seed) !== 'string') { return; }
seed = seed.replace(/\D/g, '').slice(0, 10); // TODO possible optimization for on-wire uid
seed = parseInt(seed);
if (!seed) { return; }
return ANIMALS[seed % ANIMALS.length] || '';
};
var getPrettyInitials = MT.getPrettyInitials = function (name) {
var parts = name.split(/\s+/);
var text;
if (parts.length > 1) {
text = parts.slice(0, 2).map(Util.getFirstCharacter).join('');
} else {
text = Util.getFirstCharacter(name);
var second = Util.getFirstCharacter(name.replace(text, ''));
if (second && second !== '?') {
text += second;
}
}
return text;
};
MT.displayAvatar = function (common, $container, href, name, _cb, uid) {
var cb = Util.once(Util.mkAsync(_cb || function () {}));
var displayDefault = function () {
var animal_avatar;
if (uid && animal_avatars[uid]) {
animal_avatar = animal_avatars[uid];
}
name = UI.getDisplayName(name);
var text;
if (ANIMALS.length && name === Messages.anonymous && uid) {
if (animal_avatar) {
text = animal_avatar;
} else {
text = animal_avatar = getPseudorandomAnimal(uid);
}
} else {
text = getPrettyInitials(name);
}
var $avatar = $('<span>', {
'class': 'cp-avatar-default' + (animal_avatar? ' animal': ''),
// this prevents screenreaders from trying to describe this
alt: '',
'aria-hidden': true,
}).text(text);
$container.append($avatar);
if (uid && animal_avatar) {
animal_avatars[uid] = animal_avatar;
}
if (cb) { cb(); }
};
if (!window.Symbol) { return void displayDefault(); } // IE doesn't have Symbol
if (!href || href.length === 1) { return void displayDefault(); }
if (avatars[href]) {
var nodes = $.parseHTML(avatars[href]);
var $el = $(nodes[0]);
$container.append($el);
return void cb($el);
}
var centerImage = function ($img, $image) {
var img = $image[0];
var w = img.width;
var h = img.height;
if (w>h) {
$image.css('max-height', '100%');
$img.css('flex-direction', 'column');
avatars[href] = $img[0].outerHTML;
if (cb) { cb($img); }
return;
}
$image.css('max-width', '100%');
$img.css('flex-direction', 'row');
avatars[href] = $img[0].outerHTML;
if (cb) { cb($img); }
};
// No password for avatars
var privateData = common.getMetadataMgr().getPrivateData();
var origin = privateData.fileHost || privateData.origin;
var parsed = Hash.parsePadUrl(href);
var secret = Hash.getSecrets('file', parsed.hash);
if (secret.keys && secret.channel) {
var hexFileName = secret.channel;
var cryptKey = Hash.encodeBase64(secret.keys && secret.keys.cryptKey);
var src = origin + Hash.getBlobPathFromHex(hexFileName);
common.getFileSize(hexFileName, function (e, data) {
if (e || !data) { return void displayDefault(); }
if (typeof data !== "number") { return void displayDefault(); }
if (Util.bytesToMegabytes(data) > 0.5) { return void displayDefault(); }
var mt = UI.mediaTag(src, cryptKey);
var $img = $(mt).appendTo($container);
MT.displayMediatagImage(common, $img, function (err, $image) {
if (err) { return void console.error(err); }
centerImage($img, $image);
});
});
}
};
var transformAvatar = function (file, cb) {
if (file.type === 'image/gif') { return void cb(file); }
var $croppie = $('<div>', {
'class': 'cp-app-profile-resizer'
});
if (typeof ($croppie.croppie) !== "function") {
return void cb(file);
}
var todo = function () {
UI.confirm($croppie[0], function (yes) {
if (!yes) { return; }
$croppie.croppie('result', {
type: 'blob',
size: {width: 300, height: 300}
}).then(function(blob) {
blob.lastModifiedDate = new Date();
blob.name = 'avatar';
cb(blob);
});
});
};
var reader = new FileReader();
reader.onload = function(e) {
$croppie.croppie({
url: e.target.result,
viewport: { width: 100, height: 100 },
boundary: { width: 400, height: 300 },
});
todo();
};
reader.readAsDataURL(file);
};
MT.addAvatar = function (common, cb) {
var AVATAR_SIZE_LIMIT = 0.5;
var allowedMediaTypes = [
'image/png',
'image/jpeg',
'image/jpg',
'image/gif',
];
var fmConfig = {
noHandlers: true,
noStore: true,
body: $('body'),
onUploaded: cb
};
var FM = common.createFileManager(fmConfig);
var accepted = ".gif,.jpg,.jpeg,.png";
var data = {
FM: FM,
filter: function (file) {
var sizeMB = Util.bytesToMegabytes(file.size);
var type = file.type;
// We can't resize .gif so we have to display an error if it is too big
if (sizeMB > AVATAR_SIZE_LIMIT && type === 'image/gif') {
UI.log(Messages._getKey('profile_uploadSizeError', [
Messages._getKey('formattedMB', [AVATAR_SIZE_LIMIT])
]));
return false;
}
// Display an error if the image type is not allowed
if (allowedMediaTypes.indexOf(type) === -1) {
UI.log(Messages._getKey('profile_uploadTypeError', [
accepted.split(',').join(', ')
]));
return false;
}
return true;
},
transformer: transformAvatar,
accept: accepted
};
return data;
};
MT.getMediaTagPreview = function (common, tags, start) {
if (!Array.isArray(tags) || !tags.length) { return; }
var i = start;
var metadataMgr = common.getMetadataMgr();
var priv = metadataMgr.getPrivateData();
var left, right;
var modal = UI.createModal({
id: 'cp-mediatag-preview-modal',
$body: $('body')
});
modal.show();
var $modal = modal.$modal.focus();
var $container = $modal.find('.cp-modal').append([
h('div.cp-mediatag-control', left = h('span.fa.fa-chevron-left')),
h('div.cp-mediatag-container', [
h('div.cp-loading-spinner-container', h('span.cp-spinner')),
]),
h('div.cp-mediatag-control', right = h('span.fa.fa-chevron-right')),
]);
var $close = $modal.find('.cp-modal-close');
var $left = $(left);
var $right = $(right);
var $inner = $container.find('.cp-mediatag-container');
var $spinner = $container.find('.cp-loading-spinner-container');
var locked = false;
var show = function (_i) {
if (locked) { return; }
if (_i < 0) { i = 0; }
else if (_i > tags.length -1) { i = tags.length - 1; }
else { i = _i; }
// Show/hide controls
$left.css('visibility', '');
$right.css('visibility', '');
if (i === 0) {
$left.css('visibility', 'hidden');
}
if (i === tags.length - 1) {
$right.css('visibility', 'hidden');
}
// Reset modal
$inner.find('media-tag, pre[data-plugin]').detach();
$spinner.show();
// Check src and cryptkey
var cfg = tags[i];
var tag;
if (cfg.svg) {
$inner.append(cfg.svg);
if (!cfg.render) {
$spinner.hide();
locked = false;
return;
}
setTimeout(cfg.render);
tag = cfg.svg;
} else {
var src = cfg.src;
var key = cfg.key;
if (cfg.href) {
var parsed = Hash.parsePadUrl(cfg.href);
var secret = Hash.getSecrets(parsed.type, parsed.hash, cfg.password);
var host = priv.fileHost || priv.origin || '';
src = host + Hash.getBlobPathFromHex(secret.channel);
var _key = secret.keys && secret.keys.cryptKey;
if (_key) { key = 'cryptpad:' + Nacl.util.encodeBase64(_key); }
}
if (!src || !key) {
$spinner.hide();
return void UI.log(Messages.error);
}
tag = h('media-tag', {
src: src,
'data-crypto-key': key
});
$inner.append(tag);
setTimeout(function () {
MediaTag(tag).on('error', function () {
locked = false;
$spinner.hide();
UI.log(Messages.error);
}).on('progress', function () {
$spinner.hide();
locked = true;
}).on('complete', function () {
locked = false;
$spinner.hide();
});
});
}
var observer = new MutationObserver(function(mutations) {
mutations.forEach(function() {
$spinner.hide();
});
});
observer.observe(tag, {
attributes: false,
childList: true,
characterData: false
});
};
show(i);
var previous = function () {
if (i === 0) { return; }
show(i - 1);
};
var next = function () {
if (i === tags.length - 1) { return; }
show(i + 1);
};
$left.click(previous);
$right.click(next);
$modal.on('keydown', function (e) {
e.stopPropagation();
});
var close = function () {
$inner.find('audio, video').trigger('pause');
$modal.hide();
};
$close.on('click', close);
$modal.on('keyup', function (e) {
//if (!Slide.shown) { return; }
e.stopPropagation();
if (e.ctrlKey) { return; }
switch(e.which) {
case 33: // pageup
case 38: // up
case 37: // left
previous();
break;
case 34: // pagedown
case 32: // space
case 40: // down
case 39: // right
next();
break;
case 27: // esc
close();
break;
default:
}
});
};
var mediatagContextMenu;
MT.importMediaTagMenu = function (common) {
if (mediatagContextMenu) { return mediatagContextMenu; }
// Create context menu
var menu = h('div.cp-contextmenu.dropdown.cp-unselectable', [
h('ul.dropdown-menu', {
'role': 'menu',
'aria-labelledBy': 'dropdownMenu',
'style': 'display:block;position:static;margin-bottom:5px;'
}, [
h('li.cp-svg', h('a.cp-app-code-context-open.dropdown-item', {
'tabindex': '-1',
'data-icon': "fa-eye",
}, Messages.pad_mediatagPreview)),
h('li', h('a.cp-app-code-context-openin.dropdown-item', {
'tabindex': '-1',
'data-icon': "fa-external-link",
}, Messages.pad_mediatagOpen)),
h('li', h('a.cp-app-code-context-share.dropdown-item', {
'tabindex': '-1',
'data-icon': "fa-shhare-alt",
}, Messages.pad_mediatagShare)),
h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', {
'tabindex': '-1',
'data-icon': "fa-cloud-upload",
}, Messages.pad_mediatagImport)),
h('li.cp-svg', h('a.cp-app-code-context-download.dropdown-item', {
'tabindex': '-1',
'data-icon': "fa-download",
}, Messages.download_mt_button)),
])
]);
// create the icon for each contextmenu option
$(menu).find("li a.dropdown-item").each(function (i, el) {
var $icon = $("<span>");
if ($(el).attr('data-icon')) {
var font = $(el).attr('data-icon').indexOf('cptools') === 0 ? 'cptools' : 'fa';
$icon.addClass(font).addClass($(el).attr('data-icon'));
} else {
$icon.text($(el).text());
}
$(el).prepend($icon);
});
var m = UI.createContextMenu(menu);
mediatagContextMenu = m;
var $menu = $(m.menu);
$menu.on('click', 'a', function (e) {
e.stopPropagation();
m.hide();
var $mt = $menu.data('mediatag');
var $this = $(this);
if ($this.hasClass("cp-app-code-context-saveindrive")) {
common.importMediaTag($mt);
}
else if ($this.hasClass("cp-app-code-context-download")) {
if ($mt.is('pre.mermaid') || $mt.is('pre.markmap')) {
(function () {
var name = Messages.mediatag_defaultImageName + '.svg';
var svg = $mt.find('svg')[0].cloneNode(true);
$(svg).attr('xmlns', 'http://www.w3.org/2000/svg').attr('width', $mt.width()).attr('height', $mt.height());
$(svg).find('foreignObject').each(function (i, el) {
var $el = $(el);
$el.find('br').after('\n');
$el.find('br').remove();
var t = $el[0].innerText || $el[0].textContent;
t.split('\n').forEach(function (text, i) {
var dy = (i+1)+'em';
$el.after(h('text', {y:0, dy:dy, style: ''}, text));
});
$el.remove();
});
var html = svg.outerHTML;
html = html.replace('<br>', '<br/>');
var b = new Blob([html], { type: 'image/svg+xml' });
window.saveAs(b, name);
})();
return;
}
if ($mt.is('pre.mathjax')) {
(function () {
var name = Messages.mediatag_defaultImageName + '.png';
var svg = $mt.find('> span > svg')[0];
var clone = svg.cloneNode(true);
var html = clone.outerHTML;
var b = new Blob([html], { type: 'image/svg+xml' });
var blobURL = URL.createObjectURL(b);
var i = new Image();
i.onload = function () {
var canvas = document.createElement('canvas');
canvas.width = i.width;
canvas.height = i.height;
var context = canvas.getContext('2d');
context.drawImage(i, 0, 0, i.width, i.height);
canvas.toBlob(function (blob) {
window.saveAs(blob, name);
});
};
i.src = blobURL;
})();
return;
}
var media = Util.find($mt, [0, '_mediaObject']);
if (!media) { return void console.error('no media'); }
if (!media.complete) { return void UI.warn(Messages.mediatag_notReady); }
if (!(media && media._blob)) { return void console.error($mt); }
window.saveAs(media._blob.content, media.name);
}
else if ($this.hasClass("cp-app-code-context-open")) {
$mt.trigger('preview');
}
else if ($this.hasClass("cp-app-code-context-openin")) {
var hash = common.getHashFromMediaTag($mt);
common.openURL(Hash.hashToHref(hash, 'file'));
}
else if ($this.hasClass("cp-app-code-context-share")) {
var data = {
file: true,
pathname: '/file/',
hashes: {
fileHash: common.getHashFromMediaTag($mt)
},
title: Util.find($mt[0], ['_mediaObject', 'name']) || ''
};
common.getSframeChannel().event('EV_SHARE_OPEN', data);
}
});
return m;
};
return MT;
});