define([ 'jquery', '/api/config', '/common/common-util.js', '/common/common-hash.js', '/common/common-language.js', '/common/common-interface.js', '/common/common-constants.js', '/common/common-feedback.js', '/common/hyperscript.js', '/common/clipboard.js', '/customize/messages.js', '/customize/application_config.js', '/customize/pages.js', '/bower_components/nthen/index.js', '/common/inner/invitation.js', '/common/visible.js', 'css!/customize/fonts/cptools/style.css', ], function ($, Config, Util, Hash, Language, UI, Constants, Feedback, h, Clipboard, Messages, AppConfig, Pages, NThen, InviteInner, Visible) { var UIElements = {}; UIElements.getSvgLogo = function () { var svg = (function(){/* image/svg+xml */}).toString().slice(14,-3); return svg; }; UIElements.prettySize = function (bytes) { var kB = Util.bytesToKilobytes(bytes); if (kB < 1024) { return kB + Messages.KB; } var mB = Util.bytesToMegabytes(bytes); return mB + Messages.MB; }; UIElements.updateTags = function (common, href) { var existing, tags; NThen(function(waitFor) { common.getSframeChannel().query("Q_GET_ALL_TAGS", null, waitFor(function(err, res) { if (err || res.error) { return void console.error(err || res.error); } existing = Object.keys(res.tags).sort(); })); }).nThen(function (waitFor) { common.getPadAttribute('tags', waitFor(function (err, res) { if (err) { if (err === 'NO_ENTRY') { UI.alert(Messages.tags_noentry); } waitFor.abort(); return void console.error(err); } tags = res || []; }), href); }).nThen(function () { UI.dialog.tagPrompt(tags, existing, function (newTags) { if (!Array.isArray(newTags)) { return; } common.setPadAttribute('tags', newTags, null, href); }); }); }; var dcAlert; UIElements.disconnectAlert = function () { if (dcAlert && $(dcAlert.element).length) { return; } dcAlert = UI.alert(Messages.common_connectionLost, undefined, true); }; UIElements.reconnectAlert = function () { if (!dcAlert) { return; } if (!dcAlert.delete) { dcAlert = undefined; return; } dcAlert.delete(); dcAlert = undefined; }; var importContent = function (type, f, cfg) { return function () { var $files = $('', {type:"file"}); if (cfg && cfg.accept) { $files.attr('accept', cfg.accept); } $files.click(); $files.on('change', function (e) { var file = e.target.files[0]; var reader = new FileReader(); var parsed = file && file.name && /.+\.([^.]+)$/.exec(file.name); var ext = parsed && parsed[1]; reader.onload = function (e) { f(e.target.result, file, ext); }; if (cfg && cfg.binary && cfg.binary.indexOf(ext) !== -1) { reader.readAsArrayBuffer(file, type); } else { reader.readAsText(file, type); } }); }; }; UIElements.getUserGrid = function (label, config, onSelect) { var common = config.common; var users = config.data; if (!users) { return; } var icons = Object.keys(users).map(function (key, i) { var data = users[key]; var name = data.displayName || data.name || Messages.anonymous; var avatar = h('span.cp-usergrid-avatar.cp-avatar'); common.displayAvatar($(avatar), data.avatar, name); var removeBtn, el; if (config.remove) { removeBtn = h('span.fa.fa-times'); $(removeBtn).click(function () { config.remove(el); }); } el = h('div.cp-usergrid-user'+(data.selected?'.cp-selected':'')+(config.large?'.large':''), { 'data-ed': data.edPublic, 'data-teamid': data.teamId, 'data-curve': data.curvePublic || '', 'data-name': name.toLowerCase(), 'data-order': i, style: 'order:'+i+';' },[ avatar, h('span.cp-usergrid-user-name', name), data.notRemovable ? undefined : removeBtn ]); return el; }).filter(function (x) { return x; }); var noOthers = icons.length === 0 ? '.cp-usergrid-empty' : ''; var classes = noOthers + (config.large?'.large':'') + (config.list?'.list':''); var inputFilter = h('input', { placeholder: Messages.share_filterFriend }); var div = h('div.cp-usergrid-container' + classes, [ label ? h('label', label) : undefined, h('div.cp-usergrid-filter', (config.noFilter || config.noSelect) ? undefined : [ inputFilter ]), ]); var $div = $(div); // Hide friends when they are filtered using the text input var redraw = function () { var name = $(inputFilter).val().trim().replace(/"/g, '').toLowerCase(); $div.find('.cp-usergrid-user').show(); if (name) { $div.find('.cp-usergrid-user:not(.cp-selected):not([data-name*="'+name+'"])').hide(); } }; $(inputFilter).on('keydown keyup change', redraw); $(div).append(h('div.cp-usergrid-grid', icons)); if (!config.noSelect) { $div.on('click', '.cp-usergrid-user', function () { var sel = $(this).hasClass('cp-selected'); if (!sel) { $(this).addClass('cp-selected'); } else { var order = $(this).attr('data-order'); order = order ? 'order:'+order : ''; $(this).removeClass('cp-selected').attr('style', order); } onSelect(); }); } return { icons: icons, div: div }; }; var createShareWithFriends = function (config, onShare, linkGetter) { var common = config.common; var sframeChan = common.getSframeChannel(); var title = config.title; var friends = config.friends || {}; var teams = config.teams || {}; var myName = common.getMetadataMgr().getUserData().name; var order = []; var smallCurves = Object.keys(friends).map(function (c) { return friends[c].curvePublic.slice(0,8); }); var div = h('div.contains-nav'); var $div = $(div); // Replace "copy link" by "share with friends" if at least one friend is selected // Also create the "share with friends" button if it doesn't exist var refreshButtons = function () { var $nav = $div.closest('.alertify').find('nav'); var friendMode = $div.find('.cp-usergrid-user.cp-selected').length; if (friendMode) { $nav.find('button.cp-share-with-friends').prop('disabled', ''); } else { $nav.find('button.cp-share-with-friends').prop('disabled', 'disabled'); } }; config.noInclude = true; Object.keys(friends).forEach(function (curve) { var data = friends[curve]; if (curve.length > 40 && data.notifications) { return; } delete friends[curve]; }); var others = []; if (Object.keys(friends).length) { var friendsList = UIElements.getUserGrid(Messages.share_linkFriends, { common: common, data: friends, noFilter: false, large: true }, refreshButtons); var friendDiv = friendsList.div; $div.append(friendDiv); others = friendsList.icons; } if (Object.keys(teams).length) { var teamsList = UIElements.getUserGrid(Messages.share_linkTeam, { common: common, noFilter: true, large: true, data: teams }, refreshButtons); $div.append(teamsList.div); } var shareButton = { className: 'primary cp-share-with-friends', name: Messages.share_withFriends, onClick: function () { var href; NThen(function (waitFor) { var w = waitFor(); // linkGetter can be async if this is a burn after reading URL var res = linkGetter({}, function (url) { if (!url) { waitFor.abort(); return; } href = url; setTimeout(w); }); if (res && /^http/.test(res)) { href = Hash.getRelativeHref(res); setTimeout(w); return; } }).nThen(function () { var $friends = $div.find('.cp-usergrid-user.cp-selected'); $friends.each(function (i, el) { var curve = $(el).attr('data-curve'); // Check if the selected element is a friend or a team if (curve) { // Friend if (!curve || !friends[curve]) { return; } var friend = friends[curve]; if (!friend.notifications || !friend.curvePublic) { return; } common.mailbox.sendTo("SHARE_PAD", { href: href, password: config.password, isTemplate: config.isTemplate, name: myName, title: title }, { channel: friend.notifications, curvePublic: friend.curvePublic }); return; } // Team var ed = $(el).attr('data-ed'); var team = teams[ed]; if (!team) { return; } sframeChan.query('Q_STORE_IN_TEAM', { href: href, password: config.password, path: config.isTemplate ? ['template'] : undefined, title: title, teamId: team.id }, function (err) { if (err) { return void console.error(err); } }); }); UI.findCancelButton().click(); // Update the "recently shared with" array: // Get the selected curves var curves = $friends.toArray().map(function (el) { return ($(el).attr('data-curve') || '').slice(0,8); }).filter(function (x) { return x; }); // Prepend them to the "order" array Array.prototype.unshift.apply(order, curves); order = Util.deduplicateString(order); // Make sure we don't have "old" friends and save order = order.filter(function (curve) { return smallCurves.indexOf(curve) !== -1; }); common.setAttribute(['general', 'share-friends'], order); if (onShare) { onShare.fire(); } }); }, keys: [13] }; common.getAttribute(['general', 'share-friends'], function (err, val) { order = val || []; // Sort friends by "recently shared with" others.sort(function (a, b) { var ca = ($(a).attr('data-curve') || '').slice(0,8); var cb = ($(b).attr('data-curve') || '').slice(0,8); if (!ca && !cb) { return 0; } if (!ca) { return 1; } if (!cb) { return -1; } var ia = order.indexOf(ca); var ib = order.indexOf(cb); if (ia === -1 && ib === -1) { return 0; } if (ia === -1) { return 1; } if (ib === -1) { return -1; } return ia - ib; }); // Reorder the friend icons others.forEach(function (el, i) { $(el).attr('data-order', i).css('order', i); }); // Display them $(friendDiv).find('.cp-usergrid-grid').detach(); $(friendDiv).append(h('div.cp-usergrid-grid', others)); refreshButtons(); }); return { content: div, buttons: [shareButton] }; }; var noContactsMessage = function(common){ var metadataMgr = common.getMetadataMgr(); var data = metadataMgr.getUserData(); var origin = metadataMgr.getPrivateData().origin; if (common.isLoggedIn()) { return { content: h('p', Messages.share_noContactsLoggedIn), buttons: [{ className: 'secondary', name: Messages.share_copyProfileLink, onClick: function () { var profile = data.profile ? (origin + '/profile/#' + data.profile) : ''; var success = Clipboard.copy(profile); if (success) { UI.log(Messages.shareSuccess); } }, keys: [13] }] }; } else { return { content: h('p', Messages.share_noContactsNotLoggedIn), buttons: [{ className: 'secondary', name: Messages.login_register, onClick: function () { common.setLoginRedirect(function () { common.gotoURL('/register/'); }); } }, { className: 'secondary', name: Messages.login_login, onClick: function () { common.setLoginRedirect(function () { common.gotoURL('/login/'); }); } } ] }; } }; var getEditableTeams = function (common, config) { var privateData = common.getMetadataMgr().getPrivateData(); var teamsData = Util.tryParse(JSON.stringify(privateData.teams)) || {}; var teams = {}; Object.keys(teamsData).forEach(function (id) { // config.teamId only exists when we're trying to share a pad from a team drive // In this case, we don't want to share the pad with the current team if (config.teamId && config.teamId === id) { return; } if (!teamsData[id].hasSecondaryKey) { return; } var t = teamsData[id]; teams[t.edPublic] = { notifications: true, displayName: t.name, edPublic: t.edPublic, avatar: t.avatar, id: id }; }); return teams; }; var makeBurnAfterReadingUrl = function (common, href, channel, cb) { var keyPair = Hash.generateSignPair(); var parsed = Hash.parsePadUrl(href); var newHref = parsed.getUrl({ ownerKey: keyPair.safeSignKey }); var sframeChan = common.getSframeChannel(); var rtChannel; NThen(function (waitFor) { if (parsed.type !== "sheet") { return; } common.getPadAttribute('rtChannel', waitFor(function (err, chan) { rtChannel = chan; })); }).nThen(function (waitFor) { sframeChan.query('Q_SET_PAD_METADATA', { channel: channel, command: 'ADD_OWNERS', value: [keyPair.validateKey] }, waitFor(function (err) { if (err) { waitFor.abort(); UI.warn(Messages.error); } })); if (rtChannel) { sframeChan.query('Q_SET_PAD_METADATA', { channel: rtChannel, command: 'ADD_OWNERS', value: [keyPair.validateKey] }, waitFor(function (err) { if (err) { console.error(err); } })); } }).nThen(function () { cb(newHref); }); }; UIElements.createShareModal = function (config) { var origin = config.origin; var pathname = config.pathname; var hashes = config.hashes; var common = config.common; if (!hashes || (!hashes.editHash && !hashes.viewHash)) { return; } // check if the pad is password protected var hash = hashes.editHash || hashes.viewHash; var href = origin + pathname + '#' + hash; var parsedHref = Hash.parsePadUrl(href); var hasPassword = parsedHref.hashData.password; var makeFaqLink = function () { var link = h('span', [ h('i.fa.fa-question-circle'), h('a', {href: '#'}, Messages.passwordFaqLink) ]); $(link).click(function () { common.openURL(config.origin + "/faq.html#security-pad_password"); }); return link; }; var parsed = Hash.parsePadUrl(pathname); var canPresent = ['code', 'slide'].indexOf(parsed.type) !== -1; var canBAR = parsed.type !== 'drive'; var burnAfterReading = (hashes.viewHash && canBAR) ? UI.createRadio('accessRights', 'cp-share-bar', Messages.burnAfterReading_linkBurnAfterReading, false, { mark: {tabindex:1}, label: {style: "display: none;"} }) : undefined; var rights = h('div.msg.cp-inline-radio-group', [ h('label', Messages.share_linkAccess), h('div.radio-group',[ UI.createRadio('accessRights', 'cp-share-editable-false', Messages.share_linkView, true, { mark: {tabindex:1} }), canPresent ? UI.createRadio('accessRights', 'cp-share-present', Messages.share_linkPresent, false, { mark: {tabindex:1} }) : undefined, UI.createRadio('accessRights', 'cp-share-editable-true', Messages.share_linkEdit, false, { mark: {tabindex:1} })]), burnAfterReading ]); // Burn after reading // Check if we are an owner of this pad. If we are, we can show the burn after reading option. // When BAR is selected, display a red message indicating the consequence and add // the options to generate the BAR url var barAlert = h('div.alert.alert-danger.cp-alertify-bar-selected', { style: 'display: none;' }, Messages.burnAfterReading_warningLink); var channel = Hash.getSecrets('pad', hash, config.password).channel; common.getPadMetadata({ channel: channel }, function (obj) { if (!obj || obj.error) { return; } var priv = common.getMetadataMgr().getPrivateData(); // Not an owner: don't display the burn after reading option if (!Array.isArray(obj.owners) || obj.owners.indexOf(priv.edPublic) === -1) { $(burnAfterReading).remove(); return; } // When the burn after reading option is selected, transform the modal buttons $(burnAfterReading).css({ display: 'flex' }); }); var $rights = $(rights); var saveValue = function () { var edit = Util.isChecked($rights.find('#cp-share-editable-true')); var present = Util.isChecked($rights.find('#cp-share-present')); common.setAttribute(['general', 'share'], { edit: edit, present: present }); }; var burnAfterReadingUrl; var getLinkValue = function (initValue, cb) { var val = initValue || {}; var edit = val.edit !== undefined ? val.edit : Util.isChecked($rights.find('#cp-share-editable-true')); var embed = val.embed; var present = val.present !== undefined ? val.present : Util.isChecked($rights.find('#cp-share-present')); var burnAfterReading = Util.isChecked($rights.find('#cp-share-bar')); if (burnAfterReading && !burnAfterReadingUrl) { if (cb) { // Called from the contacts tab, "share" button var barHref = origin + pathname + '#' + (hashes.viewHash || hashes.editHash); return makeBurnAfterReadingUrl(common, barHref, channel, function (url) { cb(url); }); } return Messages.burnAfterReading_generateLink; } var hash = (!hashes.viewHash || (edit && hashes.editHash)) ? hashes.editHash : hashes.viewHash; var href = burnAfterReading ? burnAfterReadingUrl : (origin + pathname + '#' + hash); var parsed = Hash.parsePadUrl(href); return origin + parsed.getUrl({embed: embed, present: present}); }; var makeCancelButton = function() { return { className: 'cancel', name: Messages.cancel, onClick: function () {}, keys: [27] }; }; // Share link tab var linkContent = config.sharedFolder ? [ h('label', Messages.sharedFolders_share), h('br'), ] : [ UI.createCheckbox('cp-share-embed', Messages.share_linkEmbed, false, { mark: {tabindex:1} }), ]; linkContent.push(h('div.cp-spacer')); linkContent.push(UI.dialog.selectableArea('', { id: 'cp-share-link-preview', tabindex: 1, rows:3})); // Show alert if the pad is password protected if (hasPassword) { linkContent.push(h('div.alert.alert-primary', [ h('i.fa.fa-lock'), Messages.share_linkPasswordAlert, h('br'), makeFaqLink() ])); } // warning about sharing links var localStore = window.cryptpadStore; var dismissButton = h('span.fa.fa-times'); var shareLinkWarning = h('div.alert.alert-warning.dismissable', { style: 'display: none;' }, [ h('span.cp-inline-alert-text', Messages.share_linkWarning), dismissButton ]); linkContent.push(shareLinkWarning); localStore.get('hide-alert-shareLinkWarning', function (val) { if (val === '1') { return; } $(shareLinkWarning).show(); $(dismissButton).on('click', function () { localStore.put('hide-alert-shareLinkWarning', '1'); $(shareLinkWarning).remove(); }); }); linkContent.push($(barAlert).clone()[0]); // Burn after reading var link = h('div.cp-share-modal', linkContent); var $link = $(link); var linkButtons = [ makeCancelButton(), !config.sharedFolder && { className: 'secondary cp-nobar', name: Messages.share_linkOpen, onClick: function () { saveValue(); var v = getLinkValue({ embed: Util.isChecked($link.find('#cp-share-embed')) }); window.open(v); return true; }, keys: [[13, 'ctrl']] }, { className: 'primary cp-nobar', name: Messages.share_linkCopy, onClick: function () { saveValue(); var v = getLinkValue({ embed: Util.isChecked($link.find('#cp-share-embed')) }); var success = Clipboard.copy(v); if (success) { UI.log(Messages.shareSuccess); } }, keys: [13] }, { className: 'primary cp-bar', name: 'GENERATE LINK', onClick: function () { var barHref = origin + pathname + '#' + (hashes.viewHash || hashes.editHash); makeBurnAfterReadingUrl(common, barHref, channel, function (url) { burnAfterReadingUrl = url; $rights.find('input[type="radio"]').trigger('change'); }); return true; }, keys: [] } ]; var frameLink = UI.dialog.customModal(link, { buttons: linkButtons, onClose: config.onClose, }); $(frameLink).find('.cp-bar').hide(); // Share with contacts tab var teams = getEditableTeams(common, config); config.teams = teams; var hasFriends = Object.keys(config.friends || {}).length || Object.keys(teams).length; var onFriendShare = Util.mkEvent(); var friendsObject = hasFriends ? createShareWithFriends(config, onFriendShare, getLinkValue) : noContactsMessage(common); var friendsList = friendsObject.content; onFriendShare.reg(saveValue); var contactsContent = h('div.cp-share-modal'); var $contactsContent = $(contactsContent); $contactsContent.append(friendsList); // Show alert if the pad is password protected if (hasPassword) { $contactsContent.append(h('div.alert.alert-primary', [ h('i.fa.fa-unlock'), Messages.share_contactPasswordAlert, h('br'), makeFaqLink() ])); } $(contactsContent).append($(barAlert).clone()); // Burn after reading var contactButtons = friendsObject.buttons; contactButtons.unshift(makeCancelButton()); var onShowContacts = function () { if (!hasFriends) { $rights.hide(); } }; var frameContacts = UI.dialog.customModal(contactsContent, { buttons: contactButtons, onClose: config.onClose, }); // Embed tab var getEmbedValue = function () { var url = getLinkValue({ embed: true }); return ''; }; var embedContent = [ h('p', Messages.viewEmbedTag), UI.dialog.selectableArea(getEmbedValue(), { id: 'cp-embed-link-preview', tabindex: 1, rows: 3}) ]; // Show alert if the pad is password protected if (hasPassword) { embedContent.push(h('div.alert.alert-primary', [ h('i.fa.fa-lock'), ' ', Messages.share_embedPasswordAlert, h('br'), makeFaqLink() ])); } var embedButtons = [ makeCancelButton(), { className: 'primary', name: Messages.share_linkCopy, onClick: function () { var v = getEmbedValue(); var success = Clipboard.copy(v); if (success) { UI.log(Messages.shareSuccess); } }, keys: [13] }]; var onShowEmbed = function () { $rights.find('#cp-share-bar').closest('label').hide(); $rights.find('input[type="radio"]:enabled').first().prop('checked', 'checked'); $rights.find('input[type="radio"]').trigger('change'); }; var embed = h('div.cp-share-modal', embedContent); var $embed = $(embed); var frameEmbed = UI.dialog.customModal(embed, { buttons: embedButtons, onClose: config.onClose, }); // update values for link and embed preview when radio btns change $embed.find('#cp-embed-link-preview').val(getEmbedValue()); $link.find('#cp-share-link-preview').val(getLinkValue()); $rights.find('input[type="radio"]').on('change', function () { $link.find('#cp-share-link-preview').val(getLinkValue({ embed: Util.isChecked($link.find('#cp-share-embed')) })); // Hide or show the burn after reading alert if (Util.isChecked($rights.find('#cp-share-bar')) && !burnAfterReadingUrl) { $('.cp-alertify-bar-selected').show(); // Show burn after reading button $('.alertify').find('.cp-bar').show(); $('.alertify').find('.cp-nobar').hide(); return; } $embed.find('#cp-embed-link-preview').val(getEmbedValue()); // Hide burn after reading button $('.alertify').find('.cp-nobar').show(); $('.alertify').find('.cp-bar').hide(); $('.cp-alertify-bar-selected').hide(); }); $link.find('input[type="checkbox"]').on('change', function () { $link.find('#cp-share-link-preview').val(getLinkValue({ embed: Util.isChecked($link.find('#cp-share-embed')) })); }); // Create modal var resetTab = function () { $rights.show(); $rights.find('label.cp-radio').show(); }; var tabs = [{ title: Messages.share_contactCategory, icon: "fa fa-address-book", content: frameContacts, active: hasFriends, onShow: onShowContacts, onHide: resetTab }, { title: Messages.share_linkCategory, icon: "fa fa-link", content: frameLink, active: !hasFriends }, { title: Messages.share_embedCategory, icon: "fa fa-code", content: frameEmbed, onShow: onShowEmbed, onHide: resetTab }]; if (typeof(AppConfig.customizeShareOptions) === 'function') { AppConfig.customizeShareOptions(hashes, tabs, { type: 'DEFAULT', origin: origin, pathname: pathname }); } var modal = UI.dialog.tabs(tabs); $(modal).find('.alertify-tabs-titles').after(rights); // disable edit share options if you don't have edit rights if (!hashes.editHash) { $rights.find('#cp-share-editable-false').attr('checked', true); $rights.find('#cp-share-editable-true').removeAttr('checked').attr('disabled', true); } else if (!hashes.viewHash) { $rights.find('#cp-share-editable-false').removeAttr('checked').attr('disabled', true); $rights.find('#cp-share-present').removeAttr('checked').attr('disabled', true); $rights.find('#cp-share-editable-true').attr('checked', true); } common.getAttribute(['general', 'share'], function (err, val) { val = val || {}; if (val.present && canPresent) { $rights.find('#cp-share-editable-false').prop('checked', false); $rights.find('#cp-share-editable-true').prop('checked', false); $rights.find('#cp-share-present').prop('checked', true); } else if ((val.edit === false && hashes.viewHash) || !hashes.editHash) { $rights.find('#cp-share-editable-false').prop('checked', true); $rights.find('#cp-share-editable-true').prop('checked', false); $rights.find('#cp-share-present').prop('checked', false); } else { $rights.find('#cp-share-editable-true').prop('checked', true); $rights.find('#cp-share-editable-false').prop('checked', false); $rights.find('#cp-share-present').prop('checked', false); } delete val.embed; if (!canPresent) { delete val.present; } $link.find('#cp-share-link-preview').val(getLinkValue(val)); }); common.getMetadataMgr().onChange(function () { // "hashes" is only available is the secure "share" app var _hashes = common.getMetadataMgr().getPrivateData().hashes; if (!_hashes) { return; } hashes = _hashes; $link.find('#cp-share-link-preview').val(getLinkValue()); }); return modal; }; UIElements.createFileShareModal = function (config) { var origin = config.origin; var pathname = config.pathname; var hashes = config.hashes; var common = config.common; var fileData = config.fileData; if (!hashes.fileHash) { throw new Error("You must provide a file hash"); } var url = origin + pathname + '#' + hashes.fileHash; // check if the file is password protected var parsedHref = Hash.parsePadUrl(url); var hasPassword = parsedHref.hashData.password; var makeFaqLink = function () { var link = h('span', [ h('i.fa.fa-question-circle'), h('a', {href: '#'}, Messages.passwordFaqLink) ]); $(link).click(function () { common.openURL(config.origin + "/faq.html#security-pad_password"); }); return link; }; var getLinkValue = function () { return url; }; var makeCancelButton = function() { return {className: 'cancel', name: Messages.cancel, onClick: function () {}, keys: [27]}; }; // Share link tab var linkContent = [ UI.dialog.selectableArea(getLinkValue(), { id: 'cp-share-link-preview', tabindex: 1, rows:2 }) ]; // Show alert if the pad is password protected if (hasPassword) { linkContent.push(h('div.alert.alert-primary', [ h('i.fa.fa-lock'), Messages.share_linkPasswordAlert, h('br'), makeFaqLink() ])); } // warning about sharing links var localStore = window.cryptpadStore; var dismissButton = h('span.fa.fa-times'); var shareLinkWarning = h('div.alert.alert-warning.dismissable', { style: 'display: none;' }, [ h('span.cp-inline-alert-text', Messages.share_linkWarning), dismissButton ]); linkContent.push(shareLinkWarning); localStore.get('hide-alert-shareLinkWarning', function (val) { if (val === '1') { return; } $(shareLinkWarning).show(); $(dismissButton).on('click', function () { localStore.put('hide-alert-shareLinkWarning', '1'); $(shareLinkWarning).remove(); }); }); var link = h('div.cp-share-modal', linkContent); var linkButtons = [ makeCancelButton(), { className: 'primary', name: Messages.share_linkCopy, onClick: function () { var v = getLinkValue(); var success = Clipboard.copy(v); if (success) { UI.log(Messages.shareSuccess); } }, keys: [13] } ]; var frameLink = UI.dialog.customModal(link, { buttons: linkButtons, onClose: config.onClose, }); // share with contacts tab var teams = getEditableTeams(common, config); config.teams = teams; var hasFriends = Object.keys(config.friends || {}).length || Object.keys(teams).length; var friendsObject = hasFriends ? createShareWithFriends(config, null, getLinkValue) : noContactsMessage(common); var friendsList = friendsObject.content; var contactsContent = h('div.cp-share-modal'); var $contactsContent = $(contactsContent); $contactsContent.append(friendsList); // Show alert if the pad is password protected if (hasPassword) { $contactsContent.append(h('div.alert.alert-primary', [ h('i.fa.fa-unlock'), Messages.share_contactPasswordAlert, h('br'), makeFaqLink() ])); } var contactButtons = friendsObject.buttons; contactButtons.unshift(makeCancelButton()); var frameContacts = UI.dialog.customModal(contactsContent, { buttons: contactButtons, onClose: config.onClose, }); // Embed tab var embed = h('div.cp-share-modal', [ h('p', Messages.fileEmbedScript), UI.dialog.selectable(common.getMediatagScript()), h('p', Messages.fileEmbedTag), UI.dialog.selectable(common.getMediatagFromHref(fileData)), ]); // Show alert if the pad is password protected if (hasPassword) { embed.append(h('div.alert.alert-primary', [ h('i.fa.fa-lock'), ' ', Messages.share_embedPasswordAlert, h('br'), makeFaqLink() ])); } var embedButtons = [{ className: 'cancel', name: Messages.cancel, onClick: function () {}, keys: [27] }, { className: 'primary', name: Messages.share_mediatagCopy, onClick: function () { var v = common.getMediatagFromHref(fileData); var success = Clipboard.copy(v); if (success) { UI.log(Messages.shareSuccess); } }, keys: [13] }]; var frameEmbed = UI.dialog.customModal(embed, { buttons: embedButtons, onClose: config.onClose, }); // Create modal var tabs = [{ title: Messages.share_contactCategory, icon: "fa fa-address-book", content: frameContacts, active: hasFriends, }, { title: Messages.share_linkCategory, icon: "fa fa-link", content: frameLink, active: !hasFriends }, { title: Messages.share_embedCategory, icon: "fa fa-code", content: frameEmbed }]; if (typeof(AppConfig.customizeShareOptions) === 'function') { AppConfig.customizeShareOptions(hashes, tabs, { type: 'FILE', origin: origin, pathname: pathname }); } var modal = UI.dialog.tabs(tabs); return modal; }; UIElements.createInviteTeamModal = function (config) { var common = config.common; var hasFriends = Object.keys(config.friends || {}).length !== 0; var privateData = common.getMetadataMgr().getPrivateData(); var team = privateData.teams[config.teamId]; if (!team) { return void UI.warn(Messages.error); } var origin = privateData.origin; var module = config.module || common.makeUniversal('team'); // Invite contacts var $div; var refreshButton = function () { if (!$div) { return; } var $modal = $div.closest('.alertify'); var $nav = $modal.find('nav'); var $btn = $nav.find('button.primary'); var selected = $div.find('.cp-usergrid-user.cp-selected').length; if (selected) { $btn.prop('disabled', ''); } else { $btn.prop('disabled', 'disabled'); } }; var getContacts = function () { var list = UIElements.getUserGrid(Messages.team_pickFriends, { common: common, data: config.friends, large: true }, refreshButton); var div = h('div.contains-nav'); var $div = $(div); $div.append(list.div); var contactsButtons = [{ className: 'primary', name: Messages.team_inviteModalButton, onClick: function () { var $sel = $div.find('.cp-usergrid-user.cp-selected'); var sel = $sel.toArray(); if (!sel.length) { return; } sel.forEach(function (el) { var curve = $(el).attr('data-curve'); module.execCommand('INVITE_TO_TEAM', { teamId: config.teamId, user: config.friends[curve] }, function (obj) { if (obj && obj.error) { console.error(obj.error); return UI.warn(Messages.error); } }); }); }, keys: [13] }]; return { content: div, buttons: contactsButtons }; }; var friendsObject = hasFriends ? getContacts() : noContactsMessage(common); var friendsList = friendsObject.content; var contactsButtons = friendsObject.buttons; contactsButtons.unshift({ className: 'cancel', name: Messages.cancel, onClick: function () {}, keys: [27] }); var contactsContent = h('div.cp-share-modal', [ friendsList ]); var frameContacts = UI.dialog.customModal(contactsContent, { buttons: contactsButtons, }); var linkName, linkPassword, linkMessage, linkError, linkSpinText; var linkForm, linkSpin, linkResult; var linkWarning; // Invite from link var dismissButton = h('span.fa.fa-times'); var linkContent = h('div.cp-share-modal', [ h('p', Messages.team_inviteLinkTitle ), linkError = h('div.alert.alert-danger.cp-teams-invite-alert', {style : 'display: none;'}), linkForm = h('div.cp-teams-invite-form', [ // autofill: 'off' was insufficient // adding these two fake inputs confuses firefox and prevents unwanted form autofill h('input', { type: 'text', style: 'display: none'}), h('input', { type: 'password', style: 'display: none'}), linkName = h('input', { placeholder: Messages.team_inviteLinkTempName }), h('br'), h('div.cp-teams-invite-block', [ h('span', Messages.team_inviteLinkSetPassword), h('a.cp-teams-help.fa.fa-question-circle', { href: origin + '/faq.html#security-pad_password', target: "_blank", 'data-tippy-placement': "right" }) ]), linkPassword = UI.passwordInput({ id: 'cp-teams-invite-password', placeholder: Messages.login_password }), h('div.cp-teams-invite-block', h('span', Messages.team_inviteLinkNote) ), linkMessage = h('textarea.cp-teams-invite-message', { placeholder: Messages.team_inviteLinkNoteMsg, rows: 3 }) ]), linkSpin = h('div.cp-teams-invite-spinner', { style: 'display: none;' }, [ h('i.fa.fa-spinner.fa-spin'), linkSpinText = h('span', Messages.team_inviteLinkLoading) ]), linkResult = h('div', { style: 'display: none;' }, h('textarea', { readonly: 'readonly' })), linkWarning = h('div.cp-teams-invite-alert.alert.alert-warning.dismissable', { style: "display: none;" }, [ h('span.cp-inline-alert-text', Messages.team_inviteLinkWarning), dismissButton ]) ]); $(linkMessage).keydown(function (e) { if (e.which === 13) { e.stopPropagation(); } }); var localStore = window.cryptpadStore; localStore.get('hide-alert-teamInvite', function (val) { if (val === '1') { return; } $(linkWarning).show(); $(dismissButton).on('click', function () { localStore.put('hide-alert-teamInvite', '1'); $(linkWarning).remove(); }); }); var $linkContent = $(linkContent); var href; var process = function () { var $nav = $linkContent.closest('.alertify').find('nav'); $(linkError).text('').hide(); var name = $(linkName).val(); var pw = $(linkPassword).find('input').val(); var msg = $(linkMessage).val(); var hash = Hash.createRandomHash('invite', pw); var hashData = Hash.parseTypeHash('invite', hash); href = origin + '/teams/#' + hash; if (!name || !name.trim()) { $(linkError).text(Messages.team_inviteLinkErrorName).show(); return true; } var seeds = InviteInner.deriveSeeds(hashData.key); var salt = InviteInner.deriveSalt(pw, AppConfig.loginSalt); var bytes64; NThen(function (waitFor) { $(linkForm).hide(); $(linkSpin).show(); $nav.find('button.cp-teams-invite-create').hide(); $nav.find('button.cp-teams-invite-copy').show(); setTimeout(waitFor(), 150); }).nThen(function (waitFor) { InviteInner.deriveBytes(seeds.scrypt, salt, waitFor(function (_bytes) { bytes64 = _bytes; })); }).nThen(function (waitFor) { module.execCommand('CREATE_INVITE_LINK', { name: name, password: pw, message: msg, bytes64: bytes64, hash: hash, teamId: config.teamId, seeds: seeds, }, waitFor(function (obj) { if (obj && obj.error) { waitFor.abort(); $(linkSpin).hide(); $(linkForm).show(); $nav.find('button.cp-teams-invite-create').show(); $nav.find('button.cp-teams-invite-copy').hide(); return void $(linkError).text(Messages.team_inviteLinkError).show(); } // Display result here $(linkSpin).hide(); $(linkResult).show().find('textarea').text(href); $nav.find('button.cp-teams-invite-copy').prop('disabled', ''); })); }); return true; }; var linkButtons = [{ className: 'cancel', name: Messages.cancel, onClick: function () {}, keys: [27] }, { className: 'primary cp-teams-invite-create', name: Messages.team_inviteLinkCreate, onClick: function () { return process(); }, keys: [] }, { className: 'primary cp-teams-invite-copy', name: Messages.team_inviteLinkCopy, onClick: function () { if (!href) { return; } var success = Clipboard.copy(href); if (success) { UI.log(Messages.shareSuccess); } }, keys: [] }]; var frameLink = UI.dialog.customModal(linkContent, { buttons: linkButtons, }); $(frameLink).find('.cp-teams-invite-copy').prop('disabled', 'disabled').hide(); // Create modal var tabs = [{ title: Messages.share_contactCategory, icon: "fa fa-address-book", content: frameContacts, active: hasFriends }, { title: Messages.share_linkCategory, icon: "fa fa-link", content: frameLink, active: !hasFriends }]; var modal = UI.dialog.tabs(tabs); UI.openCustomModal(modal); }; UIElements.createButton = function (common, type, rightside, data, callback) { var AppConfig = common.getAppConfig(); var button; var sframeChan = common.getSframeChannel(); var appType = (common.getMetadataMgr().getMetadata().type || 'pad').toUpperCase(); switch (type) { case 'export': button = $('