diff --git a/customize.dist/messages.js b/customize.dist/messages.js index fd8a5f879..aef5e749e 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -157,6 +157,16 @@ define(req, function(AppConfig, Default, Language) { } }; + // XXX + Messages.form_guestAuthorTitle = "Save your author link before sharing"; + Messages.form_guestEditLinkDefinition = "This form's author link is the private link to modify questions and read responses."; + Messages.form_guestAuthorBody = "This author link is currently not stored anywhere since you are not logged in. To avoid loosing access in future please save this link now:"; + Messages.form_guestAuthorBodyStored = "This author link is currently only stored on this device, please save it to avoid loosing access to your form."; + Messages.form_guestAuthorCopied = "Author link copied"; + Messages.form_guestAuthorCopy = "Copy author link"; + Messages.form_guestAuthorContinuePublic = "Continue to public link"; + Messages.form_guestPublicTitle = "Copy your public link"; + Messages.form_guestPublicBody = "Share this link with participants so they can fill out your form."; return Messages; }); diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index 6fcf8f276..03e0392ac 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -119,15 +119,16 @@ .cp-creation-checkboxes { min-width: 300px; flex-flow: column; - align-items: baseline !important; - max-height: 150px; - min-height: 120px !important; + align-items: stretch !important; + max-height: 180px; + min-height: 140px !important; justify-content: space-evenly; flex: 1; & > div { display: flex; width: 100%; max-width: 100%; + min-width: 0; margin: auto; text-align: left; } @@ -258,40 +259,76 @@ } .cp-creation-teams { - display: none !important; - .cp-creation-teams-grid { - display: flex; - flex-wrap: wrap; - padding: 0 2px; - flex: 1; + display: flex; + align-items: center; + gap: 0.5rem; + min-width: 0; + width: 100%; + .cp-creation-store-label, + .cp-creation-help { + flex: none; } - .cp-creation-team { - .avatar_main(25px); - width: 140px; - height: 35px; - display: flex; - justify-content: center; + .cp-creation-team-avatar { + .avatar_main(20px); + flex: none; + display: inline-flex; align-items: center; - padding: 5px; - cursor: default; - font: @colortheme_app-font; - margin: 0 1px; - - .tools_unselectable(); - - &.cp-selected { - background-color: @cp_creation-button-bg; - color: @cp_creation-button-fg; + justify-content: center; + svg, .lucide { + margin: 0; } - .cp-creation-team-name { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; + } + .cp-creation-team-name { + flex: 1 1 0; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; + } + .cp-dropdown-container { + flex: 1 1 0; + width: 0; + min-width: 0; + button.btn { width: 100%; - text-align: center; - line-height: 18px; + justify-content: flex-start; + overflow: hidden; + color: @cp_creation-fg; + background: @cp_forms-bg; + border: 1px solid @cp_forms-border; + text-transform: none; + .cp-dropdown-button-title { + flex: 1 1 0; + min-width: 0; + display: flex; + align-items: center; + justify-content: flex-start; + gap: 0.5rem; + overflow: hidden; + text-align: left; + } + } + .cp-dropdown-content { + left: 0; + right: 0; + width: 100%; + min-width: 0; + max-width: 100%; + box-sizing: border-box; + overflow-x: hidden; + li[role="menuitem"] { + overflow: hidden; + > a { + display: flex; + align-items: center; + gap: 0.5rem; + min-width: 0; + max-width: 100%; + overflow: hidden; + } + } } - border: 1px solid @cp_creation-button-bg; } } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 56380a975..2f270df54 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2814,55 +2814,79 @@ define([ } // Team pad - var team; - // FIXME: broken wen cache is enabled - var teamExists = privateData.teams && Object.keys(privateData.teams).length; - var teamValue; // storeInTeam can be // * a team ID ==> store in the team drive, and the team will be the owner // * -1 ==> store in the user drive, and the user will be the owner - // * undefined ==> ask - if (teamExists) { - var teams = Object.keys(privateData.teams).map(function (id) { - var data = privateData.teams[id]; - var avatar = h('span.cp-creation-team-avatar.cp-avatar'); - // We assume that teams always have a non-empty name, so we don't need a UID - common.displayAvatar($(avatar), data.avatar, data.name); - return h('div.cp-creation-team', { - 'data-id': id, - title: data.name, - },[ - avatar, - h('span.cp-creation-team-name', data.name) - ]); - }); - teams.unshift(h('div.cp-creation-team', { - 'data-id': '-1', - title: Messages.settings_cat_drive - }, [ - h('span.cp-creation-team-avatar', Icons.get('drive')), - h('span.cp-creation-team-name', Messages.settings_cat_drive) - ])); - team = h('div.cp-creation-teams', [ - Messages.team_pcsSelectLabel, - h('div.cp-creation-teams-grid', teams), - createHelper('#', Messages.team_pcsSelectHelp) - ]); - var $team = $(team); - $team.find('.cp-creation-team').click(function () { - if ($(this).hasClass('cp-selected')) { - teamValue = undefined; - return void $(this).removeClass('cp-selected'); - } - $team.find('.cp-creation-team').removeClass('cp-selected'); - $(this).addClass('cp-selected'); - teamValue = $(this).attr('data-id'); - }); - if (privateData.storeInTeam) { - $team.find('[data-id="'+privateData.storeInTeam+'"]').addClass('cp-selected'); - teamValue = privateData.storeInTeam; + // * undefined ==> ask (CryptDrive / team / don't store); default CryptDrive + var team; + var teamValue = typeof(privateData.storeInTeam) === "undefined" ? '-1' : String(privateData.storeInTeam); + var getTeamLabel = function (val) { + if (val === '-1') { return Messages.settings_cat_drive; } + if (!val || val === 'none') { return Messages.autostore_hide; } + return privateData.teams?.[val]?.name || val; + }; + var getTeamAvatar = function (val) { + if (val === '-1') { + return h('span.cp-creation-team-avatar', Icons.get('drive')); } - } + if (!val || val === 'none') { + return h('span.cp-creation-team-avatar', Icons.get('close')); + } + var data = privateData.teams?.[val]; + var avatar = h('span.cp-creation-team-avatar.cp-avatar'); + if (data) { + common.displayAvatar($(avatar), data.avatar, data.name); + } + return avatar; + }; + var teamOptions = [{ + tag: 'a', + attributes: { 'data-value': '-1' }, + content: [getTeamAvatar('-1'), h('span.cp-creation-team-name', getTeamLabel('-1'))] + }]; + Object.keys(privateData.teams || {}).forEach(function (id) { + var data = privateData.teams[id]; + if (!data) { return; } + teamOptions.push({ + tag: 'a', + attributes: { 'data-value': id }, + content: [getTeamAvatar(id), h('span.cp-creation-team-name', data.name)] + }); + }); + teamOptions.push({ + tag: 'a', + attributes: { 'data-value': 'none' }, + content: [getTeamAvatar('none'), h('span.cp-creation-team-name', getTeamLabel('none'))] + }); + var $teamSelect = UIElements.createDropdown({ + text: getTeamLabel(teamValue), + options: teamOptions, + isSelect: true, + caretDown: true, + initialValue: teamValue, + buttonTitle: getTeamLabel(teamValue), + common: common + }); + var $teamBtn = $teamSelect.find('button').addClass('btn'); + var setTeamButton = function (val) { + var label = getTeamLabel(val); + $teamBtn[0]?._tippy?.destroy(); + $teamBtn.attr('title', label); + $teamBtn.find('.cp-dropdown-button-title').empty().append([ + getTeamAvatar(val), + h('span.cp-creation-team-name', label) + ]); + }; + setTeamButton(teamValue); + $teamSelect.onChange.reg(function (text, value) { + teamValue = typeof(value) === "undefined" ? 'none' : String(value); + setTeamButton(teamValue); + }); + team = h('div.cp-creation-teams', [ + h('span.cp-creation-store-label', Messages.team_pcsSelectLabel), + $teamSelect[0], + // createHelper('#', Messages.team_pcsSelectHelp) - commented until the documentation is updated + ]); // Owned pads @@ -3145,9 +3169,16 @@ define([ var templateContent = $template.data('content') || undefined; // Team var team; - if (teamValue) { - team = privateData.teams[teamValue] || {}; - team.id = Number(teamValue); + if (teamValue === 'none') { + team = false; + } else if (teamValue === '-1') { + team = { id: -1 }; + } else if (teamValue) { + var selectedTeam = privateData.teams[teamValue] || {}; + team = { + id: Number(teamValue), + edPublic: selectedTeam.edPublic + }; } return { diff --git a/www/common/cryptpad-common.js b/www/common/cryptpad-common.js index 42606a35a..c13e3c949 100644 --- a/www/common/cryptpad-common.js +++ b/www/common/cryptpad-common.js @@ -1151,6 +1151,13 @@ define([ data.href = parsed.getUrl({present: parsed.present}); if (typeof (data.title) !== "string") { return cb('Missing title'); } + if (common.initialTeam === false) { + if (!data.forceSave) { + common.autoStore.onStoreRequest.fire({}); + return void cb(null, { notStored: true }); + } + delete common.initialTeam; + } if (common.initialTeam) { // If the value is -1, it means the user drive was selected from the pad creation screen diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 14c54cd99..d7ee09691 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1179,6 +1179,11 @@ define([ openURL(url); }); sframeChan.on('EV_OPEN_URL', openURL); + sframeChan.on('EV_OPEN_VIEW_URL', function () { + if (!hashes || !hashes.viewHash) { return; } + var a = window.open(Utils.Hash.hashToHref(hashes.viewHash, 'form')); + if (!a) { _sframeChan.event('EV_POPUP_BLOCKED'); } + }); sframeChan.on('Q_GET_PAD_METADATA', function (data, cb) { if (!data || !data.channel) { @@ -1765,7 +1770,9 @@ define([ }); sframeChan.on('Q_SAVE_AS_TEMPLATE', function (data, cb) { - data.teamId = Cryptpad.initialTeam; + if (Cryptpad.initialTeam) { + data.teamId = Cryptpad.initialTeam; + } Cryptpad.saveAsTemplate(Cryptget.put, data, cb); }); @@ -2279,12 +2286,8 @@ define([ }); }); }); - sframeChan.on('EV_OPEN_VIEW_URL', function () { - var url = Utils.Hash.hashToHref(hashes.viewHash, 'form'); - var a = window.open(url); - if (!a) { - sframeChan.event('EV_POPUP_BLOCKED'); - } + sframeChan.on('EV_FORM_GUEST_SHARE_OPEN', function (data) { + initSecureModal('formGuestShare', data || {}); }); Handler.formCommandHandlers(sframeChan, Utils, nThen, Cryptpad); @@ -2537,8 +2540,12 @@ define([ if (cfg.integration) { rtConfig.metadata.selfdestruct = true; } - if (data.team) { + if (data.team === false) { + Cryptpad.initialTeam = false; + } else if (data.team) { Cryptpad.initialTeam = data.team.id; + } else { + delete Cryptpad.initialTeam; } if (data.owned && data.team && data.team.edPublic) { rtConfig.metadata.owners = [data.team.edPublic]; diff --git a/www/form/inner.js b/www/form/inner.js index 759f3f10b..35e7bd8c9 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -17,7 +17,6 @@ define([ '/common/common-hash.js', '/common/common-interface.js', '/common/common-ui-elements.js', - '/common/clipboard.js', '/common/inner/common-mediatag.js', '/common/hyperscript.js', '/customize/messages.js', @@ -66,7 +65,6 @@ define([ Hash, UI, UIElements, - Clipboard, MT, h, Messages, @@ -4908,14 +4906,35 @@ define([ Messages.form_geturl ]); var preview = h('div.cp-forms-results-participant', [previewBtn, participantBtn]); + var shouldWarnGuestAuthor = function () { + if (framework._.sfCommon.isLoggedIn()) { return false; } + if (!APP.isEditor) { return false; } + return true; + }; + var warnGuestAuthorThen = function (next) { + framework._.sfCommon.isPadStored(function (err, stored) { + sframeChan.event('EV_FORM_GUEST_SHARE_OPEN', { + next: next, + stored: Boolean(stored) + }); + }); + }; + $(previewBtn).click(function () { - sframeChan.event('EV_OPEN_VIEW_URL'); + if (!shouldWarnGuestAuthor()) { + return void sframeChan.event('EV_OPEN_VIEW_URL'); + } + warnGuestAuthorThen('preview'); }); $(participantBtn).click(function () { - sframeChan.query('Q_COPY_VIEW_URL', null, function (err, success) { - if (success) { return void UI.log(Messages.shareSuccess); } - UI.warn(Messages.error); - }); + if (!shouldWarnGuestAuthor()) { + sframeChan.query('Q_COPY_VIEW_URL', null, function (err, success) { + if (success) { return void UI.log(Messages.shareSuccess); } + UI.warn(Messages.error); + }); + return; + } + warnGuestAuthorThen('public'); }); // Private / public status diff --git a/www/secureiframe/app-secure.less b/www/secureiframe/app-secure.less index 19470b6c8..bcc4b38ec 100644 --- a/www/secureiframe/app-secure.less +++ b/www/secureiframe/app-secure.less @@ -106,4 +106,9 @@ } } } + .alertify .cp-form-guest-share-modal { + h4, h5, h6, p, textarea { + color: @cryptpad_text_col; + } + } } diff --git a/www/secureiframe/inner.js b/www/secureiframe/inner.js index b8f264051..3330fee3d 100644 --- a/www/secureiframe/inner.js +++ b/www/secureiframe/inner.js @@ -46,6 +46,123 @@ define([ }; var create = {}; + create['formGuestShare'] = function (data) { + data = data || {}; + require(['/common/clipboard.js'], function (Clipboard) { + var priv = metadataMgr.getPrivateData(); + var hashes = priv.hashes || {}; + var origin = priv.origin || ''; + var editUrl = hashes.editHash ? + (origin + Hash.hashToHref(hashes.editHash, 'form')) : ''; + var viewUrl = hashes.viewHash ? + (origin + Hash.hashToHref(hashes.viewHash, 'form')) : ''; + + var showPublic = function () { + if (!viewUrl) { return void hideIframe(); } + var frame; + var modal = UI.dialog.customModal(h('div.cp-form-guest-share-modal', [ + h('h4', Messages.form_guestPublicTitle), + h('p', Messages.form_guestPublicBody), + UI.dialog.selectableArea(viewUrl, { + id: 'cp-form-guest-public-link', + rows: 2 + }) + ]), { + onClose: function () { + if (displayed === frame) { hideIframe(); } + }, + buttons: [{ + className: 'cancel', + name: Messages.cancel, + onClick: function () {}, + keys: [27] + }, { + className: 'primary', + name: Messages.form_geturl, + iconClass: 'copy', + onClick: function () { + Clipboard.copy(viewUrl, function (err) { + if (err) { return void UI.warn(Messages.error); } + UI.log(Messages.shareSuccess); + hideIframe(); + }); + return true; + }, + keys: [13] + }] + }); + frame = UI.openCustomModal(modal); + displayed = frame; + }; + + var showAuthor = function () { + if (!editUrl) { + if (data.next === 'public') { return void showPublic(); } + if (data.next === 'preview') { + sframeChan.event('EV_OPEN_VIEW_URL'); + } + return void hideIframe(); + } + var frame; + var stay; + var modal = UI.dialog.customModal(h('div.cp-form-guest-share-modal', [ + h('h4', Messages.form_guestAuthorTitle), + h('p', Messages.form_guestEditLinkDefinition), + h('p', data.stored ? + Messages.form_guestAuthorBodyStored : Messages.form_guestAuthorBody), + UI.dialog.selectableArea(editUrl, { + id: 'cp-form-guest-author-link', + rows: 2 + }) + ]), { + onClose: function () { + if (stay || displayed !== frame) { return; } + hideIframe(); + }, + buttons: [{ + className: 'cancel', + name: Messages.cancel, + onClick: function () {}, + keys: [27] + }, { + className: 'secondary', + name: Messages.form_guestAuthorCopy, + iconClass: 'copy', + onClick: function () { + Clipboard.copy(editUrl, function (err) { + if (err) { return void UI.warn(Messages.error); } + UI.log(Messages.form_guestAuthorCopied); + }); + return true; + }, + keys: [] + }, { + className: 'primary', + name: data.next === 'public' ? + Messages.form_guestAuthorContinuePublic : Messages.continue, + onClick: function () { + if (data.next === 'public') { + stay = true; + if (frame && frame.closeModal) { + frame.closeModal(showPublic); + } else { + showPublic(); + } + return true; + } + if (data.next === 'preview') { + sframeChan.event('EV_OPEN_VIEW_URL'); + } + }, + keys: [13] + }] + }); + frame = UI.openCustomModal(modal); + displayed = frame; + }; + showAuthor(); + }); + }; // Share modal create['share'] = function (data) {