diff --git a/CHANGELOG.md b/CHANGELOG.md index 890f8328d..b80b5d0d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,29 @@ +# WoollyMammoth (3.22.0) + +## Goals + +We've been working on some long-term projects that we hope to deliver over the course of the next few releases. In the meantime, this release includes a number of minor improvements. + +## Update notes + +To upgrade from 3.21.0 to 3.22.0: + +1. Stop your server +2. Get the latest platform code with git +3. Install client-side dependencies with `bower update` +4. Restart the CryptPad API server + +## Features + +* Contributors have helped by translating more of CryptPad into Finnish and traditional Chinese via [our weblate instance](https://weblate.cryptpad.fr/projects/cryptpad/app/) + +## Bug fixes + +* Some of the special behaviour implemented for Org-mode in our code editor sometimes failed when the document was first changed into Org-mode. +* We now clear some minor personal preferences like whether certain tooltips had been dismissed when you log out. +* We identified and addressed a number of issues with teams that caused valid teams to not be displayed and team member rights to fail to upgrade until a full session reload. +* We now display the number of days before an unregistered user's documents are considered inactive in their drive instead of hardcoding "3 months". + # VietnameseRhinoceros (3.21.0) ## Goals diff --git a/customize.dist/pages.js b/customize.dist/pages.js index bd5627139..ab86aa7a8 100644 --- a/customize.dist/pages.js +++ b/customize.dist/pages.js @@ -62,7 +62,7 @@ define([ var imprintUrl = AppConfig.imprint && (typeof(AppConfig.imprint) === "boolean" ? '/imprint.html' : AppConfig.imprint); - Pages.versionString = "CryptPad v3.21.0 (VietnameseRhinoceros)"; + Pages.versionString = "CryptPad v3.22.0 (WoollyMammoth)"; // used for the about menu Pages.imprintLink = AppConfig.imprint ? footLink(imprintUrl, 'imprint') : undefined; diff --git a/package-lock.json b/package-lock.json index cb59a4dac..fb230790b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "cryptpad", - "version": "3.21.0", + "version": "3.22.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 866058611..7f00bc651 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "cryptpad", "description": "realtime collaborative visual editor with zero knowlege server", - "version": "3.21.0", + "version": "3.22.0", "license": "AGPL-3.0+", "repository": { "type": "git", diff --git a/www/code/orgmode.js b/www/code/orgmode.js index c2af1bd5e..942d141f2 100644 --- a/www/code/orgmode.js +++ b/www/code/orgmode.js @@ -123,7 +123,10 @@ define([ }; }); + var init = false; CodeMirror.registerHelper("orgmode", "init", function (editor) { + if (init) { return; } + editor.setOption("extraKeys", { "Tab": function(cm) { org_cycle(cm); }, "Shift-Tab": function(cm){ org_shifttab(cm); }, @@ -139,6 +142,7 @@ define([ "Shift-Right": function(cm){ org_shiftright(cm); } }); + init = true; editor.on('mousedown', toggleHandler); editor.on('touchstart', toggleHandler); editor.on('gutterClick', foldLine); @@ -155,6 +159,9 @@ define([ }); CodeMirror.registerHelper("orgmode", "destroy", function (editor) { + if (!init) { return; } + + init = false; editor.off('mousedown', toggleHandler); editor.off('touchstart', toggleHandler); editor.off('gutterClick', foldLine); diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index a431bf5e7..7f9e94423 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -229,181 +229,7 @@ define([ }; }; - - 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'); - var ed = $(el).attr('data-ed'); - var friend = curve && friends[curve]; - var team = teams[ed]; - // If the selected element is a friend or a team without edit right, - // send a notification - var mailbox = friend || ((team && team.viewer) ? team : undefined); - if (mailbox) { // Friend - if (friends[curve] && !mailbox.notifications) { return; } - if (mailbox.notifications && mailbox.curvePublic) { - common.mailbox.sendTo("SHARE_PAD", { - href: href, - password: config.password, - isTemplate: config.isTemplate, - name: myName, - title: title - }, { - viewed: team && team.id, - channel: mailbox.notifications, - curvePublic: mailbox.curvePublic - }); - return; - } - } - // If it's a team with edit right, add the pad directly - 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){ + UIElements.noContactsMessage = function (common) { var metadataMgr = common.getMetadataMgr(); var data = metadataMgr.getUserData(); var origin = metadataMgr.getPrivateData().origin; @@ -446,655 +272,6 @@ define([ } }; - 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; } - var t = teamsData[id]; - teams[t.edPublic] = { - viewer: !teamsData[id].hasSecondaryKey, - notifications: t.notifications, - curvePublic: t.curvePublic, - 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; @@ -1159,7 +336,7 @@ define([ buttons: contactsButtons }; }; - var friendsObject = hasFriends ? getContacts() : noContactsMessage(common); + var friendsObject = hasFriends ? getContacts() : UIElements.noContactsMessage(common); var friendsList = friendsObject.content; var contactsButtons = friendsObject.buttons; contactsButtons.unshift({ @@ -2254,7 +1431,12 @@ define([ }); $container.keydown(function (e) { var $value = $innerblock.find('[data-value].cp-dropdown-element-active:visible'); + if (!$value.length) { + $value = $innerblock.find('[data-value]').first(); + } if (e.which === 38) { // Up + e.preventDefault(); + e.stopPropagation(); if ($value.length) { $value.mouseleave(); var $prev = $value.prev(); @@ -2263,6 +1445,8 @@ define([ } } if (e.which === 40) { // Down + e.preventDefault(); + e.stopPropagation(); if ($value.length) { $value.mouseleave(); var $next = $value.next(); @@ -2271,12 +1455,16 @@ define([ } } if (e.which === 13) { //Enter + e.preventDefault(); + e.stopPropagation(); if ($value.length) { $value.click(); hide(); } } if (e.which === 27) { // Esc + e.preventDefault(); + e.stopPropagation(); $value.mouseleave(); hide(); } diff --git a/www/common/drive-ui.js b/www/common/drive-ui.js index 3447f5056..8f10fe3bc 100644 --- a/www/common/drive-ui.js +++ b/www/common/drive-ui.js @@ -1,5 +1,6 @@ define([ 'jquery', + '/api/config/', '/common/toolbar.js', 'json.sortify', '/common/common-util.js', @@ -9,6 +10,7 @@ define([ '/common/common-constants.js', '/common/common-feedback.js', + '/common/inner/share.js', '/common/inner/access.js', '/common/inner/properties.js', @@ -19,6 +21,7 @@ define([ '/customize/messages.js', ], function ( $, + ApiConfig, Toolbar, JSONSortify, Util, @@ -27,6 +30,7 @@ define([ UI, Constants, Feedback, + Share, Access, Properties, nThen, @@ -2007,26 +2011,25 @@ define([ if (!parsed.hash && !roParsed.hash) { return void console.error("Invalid href: "+(data.href || data.roHref)); } var friends = common.getFriends(); var ro = folders[id] && folders[id].version >= 2; - var modal = UIElements.createShareModal({ - teamId: APP.team, - origin: APP.origin, - pathname: "/drive/", - friends: friends, - title: data.title, - password: data.password, - sharedFolder: true, - common: common, - hashes: { - editHash: parsed.hash, - viewHash: ro && roParsed.hash, - } - }); // If we're a viewer and this is an old shared folder (no read-only mode), we // can't share the read-only URL and we don't have access to the edit one. // We should hide the share button. - if (!modal) { return; } + if (!data.href && !ro) { return; } $shareBlock.click(function () { - UI.openCustomModal(modal); + Share.getShareModal(common, { + teamId: APP.team, + origin: APP.origin, + pathname: "/drive/", + friends: friends, + title: data.title, + password: data.password, + sharedFolder: true, + common: common, + hashes: { + editHash: parsed.hash, + viewHash: ro && roParsed.hash, + } + }); }); $container.append($shareBlock); return $shareBlock; @@ -2340,7 +2343,7 @@ define([ return $(common.fixLinks($box.html(msg))); } if (!APP.loggedIn) { - msg = APP.newSharedFolder ? Messages.fm_info_sharedFolder : Messages.fm_info_anonymous; + msg = APP.newSharedFolder ? Messages.fm_info_sharedFolder : Messages._getKey('fm_info_anonymous', [ApiConfig.inactiveTime || 90]); return $(common.fixLinks($box.html(msg))); } if (!msg || APP.store['hide-info-' + path[0]] === '1') { @@ -3660,14 +3663,6 @@ define([ if (!readOnlyFolder) { createNewButton(isInRoot, APP.toolbar.$bottomL); } - /* - // The share button is not displayed anymore in the toolbar: users can't know - // if they're going to share the current shared folder or the selected pad - if (sfId) { - createShareButton(sfId, APP.toolbar.$bottomL); - } - */ - if (APP.mobile()) { var $context = $('