diff --git a/customize.dist/src/less2/include/modals-ui-elements.less b/customize.dist/src/less2/include/modals-ui-elements.less index 855142a28..b8f7cb90f 100644 --- a/customize.dist/src/less2/include/modals-ui-elements.less +++ b/customize.dist/src/less2/include/modals-ui-elements.less @@ -106,4 +106,33 @@ .cp-teams-help { margin-left: 10px; } + + // mediatag preview + #cp-mediatag-preview-modal { + .cp-modal { + display: flex; + align-items: center; + justify-content: center; + .cp-mediatag-container { + height: 100%; + width: 100%; + media-tag { + & > * { + max-width: 100%; + max-height: 100%; + } + & > iframe { + width: 100%; + height: 100%; + } + & > .plain-text-reader { + white-space: pre-wrap; + text-align: left; + word-break: break-word; + color: @cryptpad_text_col; + } + } + } + } + } } diff --git a/www/common/common-interface.js b/www/common/common-interface.js index 4fa409604..5a7cf8754 100644 --- a/www/common/common-interface.js +++ b/www/common/common-interface.js @@ -1283,5 +1283,65 @@ define([ }; }; + UI.createContextMenu = function (menu) { + var $menu = $(menu).appendTo($('body')); + + var display = function (e) { + $menu.css({ display: "block" }); + var h = $menu.outerHeight(); + var w = $menu.outerWidth(); + var wH = window.innerHeight; + var wW = window.innerWidth; + if (h > wH) { + $menu.css({ + top: '0px', + bottom: '' + }); + } else if (e.pageY + h <= wH) { + $menu.css({ + top: e.pageY+'px', + bottom: '' + }); + } else { + $menu.css({ + bottom: '0px', + top: '' + }); + } + if(w > wW) { + $menu.css({ + left: '0px', + right: '' + }); + } else if (e.pageX + w <= wW) { + $menu.css({ + left: e.pageX+'px', + right: '' + }); + } else { + $menu.css({ + left: '', + right: '0px', + }); + } + }; + + var hide = function () { + $menu.hide(); + }; + var remove = function () { + $menu.remove(); + }; + + $('body').click(hide); + + return { + menu: menu, + show: display, + hide: hide, + remove: remove + }; + }; + return UI; }); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 6377e2fb8..9908dcae6 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -3373,122 +3373,6 @@ define([ }; - var createContextMenu = function (menu) { - var $menu = $(menu).appendTo($('body')); - - var display = function (e) { - $menu.css({ display: "block" }); - var h = $menu.outerHeight(); - var w = $menu.outerWidth(); - var wH = window.innerHeight; - var wW = window.innerWidth; - if (h > wH) { - $menu.css({ - top: '0px', - bottom: '' - }); - } else if (e.pageY + h <= wH) { - $menu.css({ - top: e.pageY+'px', - bottom: '' - }); - } else { - $menu.css({ - bottom: '0px', - top: '' - }); - } - if(w > wW) { - $menu.css({ - left: '0px', - right: '' - }); - } else if (e.pageX + w <= wW) { - $menu.css({ - left: e.pageX+'px', - right: '' - }); - } else { - $menu.css({ - left: '', - right: '0px', - }); - } - }; - - var hide = function () { - $menu.hide(); - }; - var remove = function () { - $menu.remove(); - }; - - $('body').click(hide); - - return { - menu: menu, - show: display, - hide: hide, - remove: remove - }; - }; - - var mediatagContextMenu; - UIElements.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', h('a.cp-app-code-context-saveindrive.dropdown-item', { - 'tabindex': '-1', - 'data-icon': "fa-cloud-upload", - }, Messages.pad_mediatagImport)), - h('li', 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 = $(""); - 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 = createContextMenu(menu); - - mediatagContextMenu = m; - - var $menu = $(m.menu); - $menu.on('click', 'a', function (e) { - e.stopPropagation(); - m.hide(); - var $mt = $menu.data('mediatag'); - if ($(this).hasClass("cp-app-code-context-saveindrive")) { - common.importMediaTag($mt); - } - else if ($(this).hasClass("cp-app-code-context-download")) { - var media = $mt[0]._mediaObject; - window.saveAs(media._blob.content, media.name); - } - }); - - return m; - }; - - UIElements.getMediaTagPreview = function () { - }; - UIElements.displayFriendRequestModal = function (common, data) { var msg = data.content.msg; var userData = msg.content.user; diff --git a/www/common/diffMarked.js b/www/common/diffMarked.js index f8e006216..7268e1c87 100644 --- a/www/common/diffMarked.js +++ b/www/common/diffMarked.js @@ -5,13 +5,14 @@ define([ '/common/common-hash.js', '/common/common-util.js', '/common/hyperscript.js', + '/common/inner/common-mediatag.js', '/common/media-tag.js', '/common/highlight/highlight.pack.js', '/customize/messages.js', '/bower_components/diff-dom/diffDOM.js', '/bower_components/tweetnacl/nacl-fast.min.js', 'css!/common/highlight/styles/github.css' -],function ($, ApiConfig, Marked, Hash, Util, h, MediaTag, Highlight, Messages) { +],function ($, ApiConfig, Marked, Hash, Util, h, MT, MediaTag, Highlight, Messages) { var DiffMd = {}; var DiffDOM = window.diffDOM; @@ -355,7 +356,7 @@ define([ DD.apply($content[0], patch); var $mts = $content.find('media-tag:not(:has(*))'); $mts.each(function (i, el) { - $(el).contextmenu(function (e) { + var $mt = $(el).contextmenu(function (e) { e.preventDefault(); $(contextMenu.menu).data('mediatag', $(el)); contextMenu.show(e); @@ -371,6 +372,15 @@ define([ observer.disconnect(); } }); + $mt.off('dblclick'); + if ($mt.find('img').length) { + $mt.on('dblclick', function () { + common.getMediaTagPreview({ + src: $mt.attr('src'), + key: $mt.attr('data-crypto-key') + }); + }); + } }); observer.observe(el, { attributes: false, diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 787caa6b4..aaeda303d 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -80,6 +80,7 @@ define([ var faCollapseAll = 'fa-minus-square-o'; var faShared = 'fa-shhare-alt'; var faReadOnly = 'fa-eye'; + var faPreview = 'fa-eye'; var faOpenInCode = 'cptools-code'; var faRename = 'fa-pencil'; var faColor = 'cptools-palette'; @@ -317,6 +318,10 @@ define([ 'style': 'display:block;position:static;margin-bottom:5px;' }, [ h('span.cp-app-drive-context-noAction.dropdown-item.disabled', Messages.fc_noAction || "No action possible"), + h('li', h('a.cp-app-drive-context-preview.dropdown-item', { + 'tabindex': '-1', + 'data-icon': faPreview, + }, 'PREVIEW')), // XXX h('li', h('a.cp-app-drive-context-open.dropdown-item', { 'tabindex': '-1', 'data-icon': faFolderOpen, @@ -1042,12 +1047,23 @@ define([ return ret; }; - var openFile = function (el, isRo) { + var openFile = function (el, isRo, app) { var data = manager.getFileData(el); if (!data || (!data.href && !data.roHref)) { return void logError("Missing data for the file", el, data); } + var href = isRo ? data.roHref : (data.href || data.roHref); + var parsed = Hash.parsePadUrl(href); + + if (parsed.hashData && parsed.hashData.type === 'file' && !app) { + common.getMediaTagPreview({ + href: data.href, + password: data.password + }); + return; + } + var priv = metadataMgr.getPrivateData(); var useUnsafe = Util.find(priv, ['settings', 'security', 'unsafeLinks']); if (useUnsafe !== false) { // true of undefined: use unsafe links @@ -1055,7 +1071,6 @@ define([ } // Get hidden hash - var parsed = Hash.parsePadUrl(href); var secret = Hash.getSecrets(parsed.type, parsed.hash, data.password); var opts = {}; if (isRo) { opts.view = true; } @@ -1175,6 +1190,7 @@ define([ if (!$element.is('.cp-border-color-file')) { //hide.push('download'); hide.push('openincode'); + hide.push('preview'); } if ($element.is('.cp-border-color-sheet')) { hide.push('download'); @@ -1192,6 +1208,9 @@ define([ if (!metadata || !Util.isPlainTextFile(metadata.fileType, metadata.title)) { hide.push('openincode'); } + if (metadata.channel && metadata.channel.length < 48) { + hide.push('preview'); + } if (!metadata.channel || metadata.channel.length > 32 || metadata.rtChannel) { hide.push('makeacopy'); // Not for blobs } @@ -1260,6 +1279,7 @@ define([ hide.push('savelocal'); hide.push('openincode'); // can't because of race condition hide.push('makeacopy'); + hide.push('preview'); } if (containsFolder && paths.length > 1) { // Cannot open multiple folders @@ -1276,12 +1296,12 @@ define([ show = ['newfolder', 'newsharedfolder', 'uploadfiles', 'uploadfolder', 'newdoc']; break; case 'tree': - show = ['open', 'openro', 'openincode', 'expandall', 'collapseall', + show = ['open', 'openro', 'preview', 'openincode', 'expandall', 'collapseall', 'color', 'download', 'share', 'savelocal', 'rename', 'delete', 'makeacopy', 'deleteowned', 'removesf', 'access', 'properties', 'hashtag']; break; case 'default': - show = ['open', 'openro', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy']; + show = ['open', 'openro', 'preview', 'share', 'openparent', 'delete', 'deleteowned', 'properties', 'access', 'hashtag', 'makeacopy']; break; case 'trashtree': { show = ['empty']; @@ -3980,11 +4000,15 @@ define([ else if ($this.hasClass('cp-app-drive-context-deleteowned')) { deleteOwnedPaths(paths); } + else if ($this.hasClass('cp-app-drive-context-preview')) { + if (paths.length !== 1) { return; } + el = manager.find(paths[0].path); + openFile(el); + } else if ($this.hasClass('cp-app-drive-context-open')) { paths.forEach(function (p) { - var $element = p.element; - $element.click(); - $element.dblclick(); + var el = manager.find(p.path); + openFile(el, false, true); }); } else if ($this.hasClass('cp-app-drive-context-openro')) { @@ -3999,7 +4023,7 @@ define([ } else { if (!el || manager.isFolder(el)) { return; } } - openFile(el, true); + openFile(el, true, true); }); } else if ($this.hasClass('cp-app-drive-context-makeacopy')) { diff --git a/www/common/inner/common-mediatag.js b/www/common/inner/common-mediatag.js index 6a6fbdb49..6349893b6 100644 --- a/www/common/inner/common-mediatag.js +++ b/www/common/inner/common-mediatag.js @@ -3,14 +3,18 @@ define([ '/common/common-util.js', '/common/common-hash.js', '/common/common-interface.js', + '/common/hyperscript.js', '/common/media-tag.js', '/customize/messages.js', '/bower_components/croppie/croppie.min.js', + '/bower_components/file-saver/FileSaver.min.js', 'css!/bower_components/croppie/croppie.css', -], function ($, Util, Hash, UI, MediaTag, Messages) { +], function ($, Util, Hash, UI, h, MediaTag, Messages) { var MT = {}; + var Nacl = window.nacl; + // Configure MediaTags to use our local viewer if (MediaTag) { MediaTag.setDefaultConfig('pdf', { @@ -204,7 +208,7 @@ define([ return data; }; - MT.previewMediaTag = function (common, config) { + MT.getMediaTagPreview = function (common, config) { config = config || {}; var metadataMgr = common.getMetadataMgr(); @@ -217,21 +221,124 @@ define([ var secret = Hash.getSecrets(parsed.type, parsed.hash, config.password); var host = priv.fileHost || priv.origin || ''; src = host + Hash.getBlobPathFromHex(secret.channel); - key = secret.keys && secret.keys.cryptKey; - } - if (!src || !key) { - // XXX - return; + var _key = secret.keys && secret.keys.cryptKey; + if (_key) { key = 'cryptpad:' + Nacl.util.encodeBase64(_key); } } + if (!src || !key) { return void UI.log(Messages.error); } var tag = h('media-tag', { src: src, - 'data-crypto-key': 'cryptpad:' + key + 'data-crypto-key': key + }); + + var $modal = UI.createModal({ + id: 'cp-mediatag-preview-modal', + $body: $('body') + }).show().focus(); + + var $container = $modal.find('.cp-modal').append(h('div.cp-mediatag-container', [ + tag + ])); + + var el; + var checkSize = function () { + if (!el) { return; } + $container.find('.cp-mediatag-container').css('height', ''); + var size = el.naturalHeight || el.videoHeight; + // Center small images and videos + if (size && size < $container.height()) { + $container.find('.cp-mediatag-container').css('height', 'auto'); + } + }; + var observer = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + if (mutation.addedNodes.length === 1) { + el = mutation.addedNodes[0]; + if (el.readyState === 0) { + // Wait for the video to be ready before checking the size + el.onloadedmetadata = checkSize; + return; + } + if (el.complete === false) { + el.onload = checkSize; + return; + } + setTimeout(checkSize); + } + }); + }); + observer.observe(tag, { + attributes: false, + childList: true, + characterData: false + }); + MediaTag(tag).on('error', function () { + UI.log(Messages.error); + $modal.hide(); + }); + }; + + 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', h('a.cp-app-code-context-open.dropdown-item', { + 'tabindex': '-1', + 'data-icon': "fa-eye", + }, Messages.fc_open)), // XXX + h('li', h('a.cp-app-code-context-saveindrive.dropdown-item', { + 'tabindex': '-1', + 'data-icon': "fa-cloud-upload", + }, Messages.pad_mediatagImport)), + h('li', 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 = $(""); + 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'); + if ($(this).hasClass("cp-app-code-context-saveindrive")) { + common.importMediaTag($mt); + } + else if ($(this).hasClass("cp-app-code-context-download")) { + var media = $mt[0]._mediaObject; + window.saveAs(media._blob.content, media.name); + } + else if ($(this).hasClass("cp-app-code-context-open")) { + common.getMediaTagPreview({ + src: $mt.attr('src'), + key: $mt.attr('data-crypto-key') + }); + } }); - $img.attr('src', src); - $img.attr('data-crypto-key', 'cryptpad:' + cryptKey); - + return m; }; return MT; diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index e10a90976..ebacf3b71 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -104,8 +104,8 @@ define([ funcs.getBurnAfterReadingWarning = callWithCommon(UIElements.getBurnAfterReadingWarning); funcs.createNewPadModal = callWithCommon(UIElements.createNewPadModal); funcs.onServerError = callWithCommon(UIElements.onServerError); - funcs.importMediaTagMenu = callWithCommon(UIElements.importMediaTagMenu); - funcs.getMediaTagPreview = callWithCommon(UIElements.getMediaTagPreview); + funcs.importMediaTagMenu = callWithCommon(MT.importMediaTagMenu); + funcs.getMediaTagPreview = callWithCommon(MT.getMediaTagPreview); // Thumb funcs.displayThumbnail = callWithCommon(Thumb.displayThumbnail);