From 9de10a8072debd7f421eb9f58d1c17304cbd2609 Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Thu, 30 Jul 2026 13:07:55 +0200 Subject: [PATCH 01/14] Add copy author link modal for guests for key actions forms --- customize.dist/messages.js | 5 +++ www/common/sframe-common-outer.js | 4 +++ www/form/inner.js | 60 ++++++++++++++++++++++++++++--- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index fd8a5f879..205bdd97f 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -157,6 +157,11 @@ define(req, function(AppConfig, Default, Language) { } }; + // XXX + Messages.form_guestAuthorTitle = "Save your author link"; + Messages.form_guestAuthorLink = "This is your author link. Copy and save it before continuing. CryptPad cannot recover it if you lose it."; + Messages.form_guestAuthorBody = "You are not logged in. Preview and Copy public link use the respondents' link, which cannot be used to edit this form or see answers."; + Messages.form_guestAuthorCopied = "Author link copied"; return Messages; }); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index e95389659..0f7bab1e0 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2231,6 +2231,10 @@ define([ }); }); }); + sframeChan.on('Q_GET_EDIT_URL', function (data, cb) { + if (!hashes.editHash) { return void cb(); } + cb(window.location.origin + Utils.Hash.hashToHref(hashes.editHash, 'form')); + }); sframeChan.on('EV_OPEN_VIEW_URL', function () { var url = Utils.Hash.hashToHref(hashes.viewHash, 'form'); var a = window.open(url); diff --git a/www/form/inner.js b/www/form/inner.js index 759f3f10b..096b41ddc 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -4908,13 +4908,65 @@ 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; } + // TODO: guest-drive case, dismiss flag, once-per-session, etc. + return true; + }; + var warnGuestAuthorThen = function (then) { + if (!shouldWarnGuestAuthor()) { return void then(); } + + sframeChan.query('Q_GET_EDIT_URL', null, function (err, editUrl) { + if (err || !editUrl) { return void then(); } + + var linkInput = UI.dialog.selectableArea(editUrl, { + id: 'cp-form-guest-author-link', + rows: 2 + }); + var content = h('div', [ + h('h4', Messages.form_guestAuthorTitle), + h('p', Messages.form_guestAuthorBody), + h('label', { for: 'cp-form-guest-author-link' }, Messages.form_guestAuthorLink), + linkInput + ]); + var modal = UI.dialog.customModal(content, { + buttons: [{ + className: 'secondary', + name: Messages.share_linkCopy, + iconClass: 'copy', + onClick: function () { + Clipboard.copy(editUrl, function (copyErr) { + if (!copyErr) { UI.log(Messages.form_guestAuthorCopied); } + else { UI.warn(Messages.error); } + }); + return true; // keep modal open + }, + keys: [] + }, { + className: 'primary', + name: Messages.continue, + onClick: function () { + then(); + }, + keys: [13] + }] + }); + UI.openCustomModal(modal); + }); + }; + $(previewBtn).click(function () { - sframeChan.event('EV_OPEN_VIEW_URL'); + warnGuestAuthorThen(function () { + sframeChan.event('EV_OPEN_VIEW_URL'); + }); }); $(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); + warnGuestAuthorThen(function () { + sframeChan.query('Q_COPY_VIEW_URL', null, function (err, success) { + if (success) { return void UI.log(Messages.shareSuccess); } + UI.warn(Messages.error); + }); }); }); From d0ce238f42c942e69091b66cbc50794d1810ce03 Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Tue, 4 Aug 2026 13:57:11 +0200 Subject: [PATCH 02/14] Treat all guest cases --- customize.dist/messages.js | 5 ++- www/form/inner.js | 79 +++++++++++++++++++++----------------- 2 files changed, 47 insertions(+), 37 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 205bdd97f..3b8594237 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -159,8 +159,9 @@ define(req, function(AppConfig, Default, Language) { // XXX Messages.form_guestAuthorTitle = "Save your author link"; - Messages.form_guestAuthorLink = "This is your author link. Copy and save it before continuing. CryptPad cannot recover it if you lose it."; - Messages.form_guestAuthorBody = "You are not logged in. Preview and Copy public link use the respondents' link, which cannot be used to edit this form or see answers."; + Messages.form_guestAuthorBody = "You are not logged in. CryptPad cannot recover your author link if you lose it."; + Messages.form_guestAuthorBodyUnstored = "This form is not in your guest drive. Store it there or copy your author link. CryptPad cannot recover it if you lose it."; + Messages.form_guestAuthorBodyStored = "This form is in your guest drive on this browser only. Copy and save your author link before continuing."; Messages.form_guestAuthorCopied = "Author link copied"; return Messages; diff --git a/www/form/inner.js b/www/form/inner.js index 096b41ddc..7921bf2e7 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -4911,48 +4911,57 @@ define([ var shouldWarnGuestAuthor = function () { if (framework._.sfCommon.isLoggedIn()) { return false; } if (!APP.isEditor) { return false; } - // TODO: guest-drive case, dismiss flag, once-per-session, etc. return true; }; var warnGuestAuthorThen = function (then) { if (!shouldWarnGuestAuthor()) { return void then(); } - sframeChan.query('Q_GET_EDIT_URL', null, function (err, editUrl) { - if (err || !editUrl) { return void then(); } + framework._.sfCommon.isPadStored(function (err, stored) { + sframeChan.query('Q_GET_EDIT_URL', null, function (urlErr, editUrl) { + if (urlErr || !editUrl) { return void then(); } - var linkInput = UI.dialog.selectableArea(editUrl, { - id: 'cp-form-guest-author-link', - rows: 2 + var hasGuestDrive = Boolean(metadataMgr.getPrivateData().driveChannel); + var bodyMsg = Messages.form_guestAuthorBody; + if (stored) { + bodyMsg = Messages.form_guestAuthorBodyStored; + } else if (hasGuestDrive) { + // Drive exists but this form was not saved + bodyMsg = Messages.form_guestAuthorBodyUnstored; + } + var linkInput = UI.dialog.selectableArea(editUrl, { + id: 'cp-form-guest-author-link', + rows: 2, + 'aria-label': Messages.form_guestAuthorTitle + }); + var content = h('div', [ + h('h4', Messages.form_guestAuthorTitle), + h('p', bodyMsg), + linkInput + ]); + var modal = UI.dialog.customModal(content, { + buttons: [{ + className: 'secondary', + name: Messages.share_linkCopy, + iconClass: 'copy', + onClick: function () { + Clipboard.copy(editUrl, function (copyErr) { + if (!copyErr) { UI.log(Messages.form_guestAuthorCopied); } + else { UI.warn(Messages.error); } + }); + return true; // keep modal open + }, + keys: [] + }, { + className: 'primary', + name: Messages.continue, + onClick: function () { + then(); + }, + keys: [13] + }] + }); + UI.openCustomModal(modal); }); - var content = h('div', [ - h('h4', Messages.form_guestAuthorTitle), - h('p', Messages.form_guestAuthorBody), - h('label', { for: 'cp-form-guest-author-link' }, Messages.form_guestAuthorLink), - linkInput - ]); - var modal = UI.dialog.customModal(content, { - buttons: [{ - className: 'secondary', - name: Messages.share_linkCopy, - iconClass: 'copy', - onClick: function () { - Clipboard.copy(editUrl, function (copyErr) { - if (!copyErr) { UI.log(Messages.form_guestAuthorCopied); } - else { UI.warn(Messages.error); } - }); - return true; // keep modal open - }, - keys: [] - }, { - className: 'primary', - name: Messages.continue, - onClick: function () { - then(); - }, - keys: [13] - }] - }); - UI.openCustomModal(modal); }); }; From 5e31372940690eb59f71dc4d3c44430263769952 Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Mon, 10 Aug 2026 13:59:02 +0200 Subject: [PATCH 03/14] Add dropdown for creation screen+UI+accessibility and fixes --- .../src/less2/include/creation.less | 79 ++++++------ www/common/common-ui-elements.js | 115 ++++++++++-------- www/common/sframe-common-outer.js | 2 + 3 files changed, 112 insertions(+), 84 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index 6fcf8f276..755a346ce 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,50 @@ } .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; - 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; - } - .cp-creation-team-name { - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; + .cp-dropdown-container { + flex: 1 1 0; + width: 0; + min-width: 0; + display: block; + button.btn { width: 100%; - text-align: center; - line-height: 18px; + max-width: 100%; + box-sizing: border-box; + justify-content: flex-start; + text-align: left; + overflow: hidden; + .cp-dropdown-button-title { + flex: 1 1 0; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-align: left; + } + } + .cp-dropdown-content { + left: 0; + right: 0; + width: auto; + min-width: 0; + max-width: 100%; + overflow-x: hidden; + li[role="menuitem"] > a { + display: block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } - border: 1px solid @cp_creation-button-bg; } } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 68e843b19..ff097723c 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2810,56 +2810,65 @@ define([ )); } - // Team pad - var team; - // FIXME: broken wen cache is enabled - var teamExists = privateData.teams && Object.keys(privateData.teams).length; - var teamValue; + // Store location: CryptDrive, team drives, or nowhere // 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) - ]); + // * none ==> do not store + var teamValue = privateData.storeInTeam != null ? String(privateData.storeInTeam) : '-1'; + var getStoreLabel = function (val) { + if (val === '-1') { return Messages.settings_cat_drive; } + if (!val || val === 'none') { return Messages.autostore_hide; } + var teamData = privateData.teams && privateData.teams[val]; + return (teamData && teamData.name) || val; + }; + var storeOptions = [{ + tag: 'a', + attributes: { 'data-value': '-1' }, + content: [Messages.settings_cat_drive] + }]; + Object.keys(privateData.teams || {}).forEach(function (id) { + var data = privateData.teams[id]; + if (!data) { return; } + storeOptions.push({ + tag: 'a', + attributes: { 'data-value': id }, + content: [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; - } - } + }); + storeOptions.push({ + tag: 'a', + attributes: { 'data-value': 'none' }, + content: [Messages.autostore_hide] + }); + var $storeDropdown = UIElements.createDropdown({ + text: getStoreLabel(teamValue), + options: storeOptions, + isSelect: true, + caretDown: true, + initialValue: teamValue, + buttonTitle: getStoreLabel(teamValue), + common: common + }); + var $storeBtn = $storeDropdown.find('button'); + $storeBtn.addClass('btn'); + $storeDropdown.setValue(teamValue, getStoreLabel(teamValue), true); + var updateStoreTitle = function (label) { + var btn = $storeBtn[0]; + if (btn && btn._tippy) { btn._tippy.destroy(); } + $storeBtn.find('.cp-dropdown-button-title').removeAttr('title'); + $storeBtn.attr('title', label); + }; + updateStoreTitle(getStoreLabel(teamValue)); + $storeDropdown.onChange.reg(function (text, value) { + teamValue = (value === undefined || value === null) ? 'none' : String(value); + updateStoreTitle(text || getStoreLabel(teamValue)); + }); + var team = h('div.cp-creation-teams', [ + h('span.cp-creation-store-label', Messages.team_pcsSelectLabel), + $storeDropdown[0], + createHelper('#', Messages.team_pcsSelectHelp) + ]); // Owned pads @@ -3140,11 +3149,17 @@ define([ var $template = $creation.find('.cp-creation-template-selected'); var templateId = $template.data('id') || undefined; var templateContent = $template.data('content') || undefined; - // Team + // Team / drive storage + // -1 ==> own CryptDrive; team id ==> team drive; none ==> nowhere var team; - if (teamValue) { - team = privateData.teams[teamValue] || {}; - team.id = Number(teamValue); + if (teamValue === '-1') { + team = { id: -1 }; + } else if (teamValue && teamValue !== 'none') { + var selectedTeam = privateData.teams[teamValue] || {}; + team = { + id: Number(teamValue), + edPublic: selectedTeam.edPublic + }; } return { diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 0f7bab1e0..267dca507 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2492,6 +2492,8 @@ define([ 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]; From 64c82c5a782c993391a44085e37ae989e6b81112 Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Tue, 11 Aug 2026 14:08:48 +0200 Subject: [PATCH 04/14] Keep simpler don't store --- www/common/common-ui-elements.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index ff097723c..808cb49a2 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -3149,17 +3149,11 @@ define([ var $template = $creation.find('.cp-creation-template-selected'); var templateId = $template.data('id') || undefined; var templateContent = $template.data('content') || undefined; - // Team / drive storage - // -1 ==> own CryptDrive; team id ==> team drive; none ==> nowhere + // Team var team; - if (teamValue === '-1') { - team = { id: -1 }; - } else if (teamValue && teamValue !== 'none') { - var selectedTeam = privateData.teams[teamValue] || {}; - team = { - id: Number(teamValue), - edPublic: selectedTeam.edPublic - }; + if (teamValue && teamValue !== 'none') { + team = privateData.teams[teamValue] || {}; + team.id = Number(teamValue); } return { From e54c2ed2af9ef0891c85f7d2e9b1cc3258aadc39 Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Tue, 11 Aug 2026 14:14:25 +0200 Subject: [PATCH 05/14] Solve lint --- www/common/common-ui-elements.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 808cb49a2..f7b9449cd 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2815,7 +2815,7 @@ define([ // * 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 // * none ==> do not store - var teamValue = privateData.storeInTeam != null ? String(privateData.storeInTeam) : '-1'; + var teamValue = privateData.storeInTeam !== null ? String(privateData.storeInTeam) : '-1'; var getStoreLabel = function (val) { if (val === '-1') { return Messages.settings_cat_drive; } if (!val || val === 'none') { return Messages.autostore_hide; } From 50b3e5373f4a3f2628f86b93af75ea33e45ffded Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Tue, 11 Aug 2026 14:17:48 +0200 Subject: [PATCH 06/14] Refactoring --- www/common/common-ui-elements.js | 50 +++++++++++++++----------------- 1 file changed, 23 insertions(+), 27 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index f7b9449cd..6081e5b61 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2810,19 +2810,19 @@ define([ )); } - // Store location: CryptDrive, team drives, or nowhere + // Team pad // 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 - // * none ==> do not store - var teamValue = privateData.storeInTeam !== null ? String(privateData.storeInTeam) : '-1'; - var getStoreLabel = function (val) { + // * undefined ==> ask (CryptDrive / team / don't store); default CryptDrive + var team; + var teamValue = privateData.storeInTeam != null ? String(privateData.storeInTeam) : '-1'; + var getTeamLabel = function (val) { if (val === '-1') { return Messages.settings_cat_drive; } if (!val || val === 'none') { return Messages.autostore_hide; } - var teamData = privateData.teams && privateData.teams[val]; - return (teamData && teamData.name) || val; + return privateData.teams?.[val]?.name || val; }; - var storeOptions = [{ + var teamOptions = [{ tag: 'a', attributes: { 'data-value': '-1' }, content: [Messages.settings_cat_drive] @@ -2830,43 +2830,39 @@ define([ Object.keys(privateData.teams || {}).forEach(function (id) { var data = privateData.teams[id]; if (!data) { return; } - storeOptions.push({ + teamOptions.push({ tag: 'a', attributes: { 'data-value': id }, content: [data.name] }); }); - storeOptions.push({ + teamOptions.push({ tag: 'a', attributes: { 'data-value': 'none' }, content: [Messages.autostore_hide] }); - var $storeDropdown = UIElements.createDropdown({ - text: getStoreLabel(teamValue), - options: storeOptions, + var $teamSelect = UIElements.createDropdown({ + text: getTeamLabel(teamValue), + options: teamOptions, isSelect: true, caretDown: true, initialValue: teamValue, - buttonTitle: getStoreLabel(teamValue), + buttonTitle: getTeamLabel(teamValue), common: common }); - var $storeBtn = $storeDropdown.find('button'); - $storeBtn.addClass('btn'); - $storeDropdown.setValue(teamValue, getStoreLabel(teamValue), true); - var updateStoreTitle = function (label) { - var btn = $storeBtn[0]; - if (btn && btn._tippy) { btn._tippy.destroy(); } - $storeBtn.find('.cp-dropdown-button-title').removeAttr('title'); - $storeBtn.attr('title', label); + var $teamBtn = $teamSelect.find('button').addClass('btn'); + var setTeamTitle = function (label) { + $teamBtn[0]?._tippy?.destroy(); + $teamBtn.attr('title', label).find('.cp-dropdown-button-title').removeAttr('title'); }; - updateStoreTitle(getStoreLabel(teamValue)); - $storeDropdown.onChange.reg(function (text, value) { - teamValue = (value === undefined || value === null) ? 'none' : String(value); - updateStoreTitle(text || getStoreLabel(teamValue)); + setTeamTitle(getTeamLabel(teamValue)); + $teamSelect.onChange.reg(function (text, value) { + teamValue = value == null ? 'none' : String(value); + setTeamTitle(text || getTeamLabel(teamValue)); }); - var team = h('div.cp-creation-teams', [ + team = h('div.cp-creation-teams', [ h('span.cp-creation-store-label', Messages.team_pcsSelectLabel), - $storeDropdown[0], + $teamSelect[0], createHelper('#', Messages.team_pcsSelectHelp) ]); From 7b885881e4ae73ccc2d6366bd913bfb9aa63dd8d Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Thu, 13 Aug 2026 14:13:43 +0200 Subject: [PATCH 07/14] Add second public link modal and change translation keys --- customize.dist/messages.js | 11 +++++--- www/form/inner.js | 53 +++++++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 3b8594237..f3c2ff6d4 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -158,11 +158,14 @@ define(req, function(AppConfig, Default, Language) { }; // XXX - Messages.form_guestAuthorTitle = "Save your author link"; - Messages.form_guestAuthorBody = "You are not logged in. CryptPad cannot recover your author link if you lose it."; - Messages.form_guestAuthorBodyUnstored = "This form is not in your guest drive. Store it there or copy your author link. CryptPad cannot recover it if you lose it."; - Messages.form_guestAuthorBodyStored = "This form is in your guest drive on this browser only. Copy and save your author link before continuing."; + 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_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/www/form/inner.js b/www/form/inner.js index 7921bf2e7..7d8fbf631 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -4913,20 +4913,53 @@ define([ if (!APP.isEditor) { return false; } return true; }; - var warnGuestAuthorThen = function (then) { + var showGuestPublicLinkModal = function () { + sframeChan.query('Q_GET_VIEW_URL', null, function (urlErr, viewUrl) { + if (urlErr || !viewUrl) { return; } + var linkInput = UI.dialog.selectableArea(viewUrl, { + id: 'cp-form-guest-public-link', + rows: 2, + 'aria-label': Messages.form_guestPublicTitle + }); + var content = h('div', [ + h('h4', Messages.form_guestPublicTitle), + h('p', Messages.form_guestPublicBody), + linkInput + ]); + var frame; + var modal = UI.dialog.customModal(content, { + buttons: [{ + className: 'primary', + name: Messages.share_linkCopy, + iconClass: 'copy', + onClick: function () { + Clipboard.copy(viewUrl, function (copyErr) { + if (!copyErr) { + UI.log(Messages.shareSuccess); + frame.closeModal(); + } else { + UI.warn(Messages.error); + } + }); + return true; + }, + keys: [13] + }] + }); + frame = UI.openCustomModal(modal); + }); + }; + var warnGuestAuthorThen = function (then, opt) { + opt = opt || {}; if (!shouldWarnGuestAuthor()) { return void then(); } framework._.sfCommon.isPadStored(function (err, stored) { sframeChan.query('Q_GET_EDIT_URL', null, function (urlErr, editUrl) { if (urlErr || !editUrl) { return void then(); } - var hasGuestDrive = Boolean(metadataMgr.getPrivateData().driveChannel); var bodyMsg = Messages.form_guestAuthorBody; if (stored) { bodyMsg = Messages.form_guestAuthorBodyStored; - } else if (hasGuestDrive) { - // Drive exists but this form was not saved - bodyMsg = Messages.form_guestAuthorBodyUnstored; } var linkInput = UI.dialog.selectableArea(editUrl, { id: 'cp-form-guest-author-link', @@ -4948,12 +4981,12 @@ define([ if (!copyErr) { UI.log(Messages.form_guestAuthorCopied); } else { UI.warn(Messages.error); } }); - return true; // keep modal open + return true; }, keys: [] }, { className: 'primary', - name: Messages.continue, + name: opt.publicLinkFlow ? Messages.form_guestAuthorContinuePublic : Messages.continue, onClick: function () { then(); }, @@ -4971,12 +5004,14 @@ define([ }); }); $(participantBtn).click(function () { - warnGuestAuthorThen(function () { + 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(showGuestPublicLinkModal, { publicLinkFlow: true }); }); // Private / public status From 5f592f529e4efc606cab5d01a60f625481268082 Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Thu, 13 Aug 2026 14:14:22 +0200 Subject: [PATCH 08/14] Public link --- www/common/sframe-common-outer.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 267dca507..8829c82fc 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2235,6 +2235,10 @@ define([ if (!hashes.editHash) { return void cb(); } cb(window.location.origin + Utils.Hash.hashToHref(hashes.editHash, 'form')); }); + sframeChan.on('Q_GET_VIEW_URL', function (data, cb) { + if (!hashes.viewHash) { return void cb(); } + cb(window.location.origin + Utils.Hash.hashToHref(hashes.viewHash, 'form')); + }); sframeChan.on('EV_OPEN_VIEW_URL', function () { var url = Utils.Hash.hashToHref(hashes.viewHash, 'form'); var a = window.open(url); From 814a4670fa1a4cdbca59615176cdaf68ca31b531 Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Thu, 13 Aug 2026 14:17:27 +0200 Subject: [PATCH 09/14] Remove helper and add icons/avatars to dropdown options --- .../src/less2/include/creation.less | 23 ++++++++++++++++--- www/common/common-ui-elements.js | 14 +++++++---- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index 755a346ce..dcb721c88 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -297,10 +297,27 @@ max-width: 100%; overflow-x: hidden; li[role="menuitem"] > a { - display: block; + display: flex; + align-items: center; + gap: 0.5rem; overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + .cp-creation-team-avatar { + .avatar_main(20px); + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + svg, .lucide { + margin: 0; + } + } + .cp-creation-team-name { + flex: 1 1 0; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } } } } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 6081e5b61..ef9f257a4 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2825,21 +2825,23 @@ define([ var teamOptions = [{ tag: 'a', attributes: { 'data-value': '-1' }, - content: [Messages.settings_cat_drive] + content: [h('span.cp-creation-team-avatar', Icons.get('drive')), h('span.cp-creation-team-name', Messages.settings_cat_drive)] }]; Object.keys(privateData.teams || {}).forEach(function (id) { var data = privateData.teams[id]; if (!data) { return; } + var avatar = h('span.cp-creation-team-avatar.cp-avatar'); + common.displayAvatar($(avatar), data.avatar, data.name); teamOptions.push({ tag: 'a', attributes: { 'data-value': id }, - content: [data.name] + content: [avatar, h('span.cp-creation-team-name', data.name)] }); }); teamOptions.push({ tag: 'a', attributes: { 'data-value': 'none' }, - content: [Messages.autostore_hide] + content: [h('span.cp-creation-team-avatar', Icons.get('close')), h('span.cp-creation-team-name', Messages.autostore_hide)] }); var $teamSelect = UIElements.createDropdown({ text: getTeamLabel(teamValue), @@ -2858,12 +2860,14 @@ define([ setTeamTitle(getTeamLabel(teamValue)); $teamSelect.onChange.reg(function (text, value) { teamValue = value == null ? 'none' : String(value); - setTeamTitle(text || getTeamLabel(teamValue)); + var label = getTeamLabel(teamValue); + $teamBtn.find('.cp-dropdown-button-title').text(label); + setTeamTitle(label); }); team = h('div.cp-creation-teams', [ h('span.cp-creation-store-label', Messages.team_pcsSelectLabel), $teamSelect[0], - createHelper('#', Messages.team_pcsSelectHelp) + // createHelper('#', Messages.team_pcsSelectHelp) - commented until the documentation is updated ]); From 8bc8a7c45979fff99b0da4dd41082b66539dab9d Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Wed, 19 Aug 2026 11:44:08 +0200 Subject: [PATCH 10/14] Add author link description --- customize.dist/messages.js | 2 +- www/form/inner.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index f3c2ff6d4..2856350c4 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -160,7 +160,7 @@ 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_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_guestAuthorContinuePublic = "Continue to public link"; diff --git a/www/form/inner.js b/www/form/inner.js index 7d8fbf631..35433c004 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -4968,6 +4968,7 @@ define([ }); var content = h('div', [ h('h4', Messages.form_guestAuthorTitle), + h('p', Messages.form_guestEditLinkDefinition), h('p', bodyMsg), linkInput ]); From c11a0db954a3f71c28dce956e7d9f08ccb356358 Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Thu, 27 Aug 2026 12:37:33 +0200 Subject: [PATCH 11/14] Implement review changes --- customize.dist/messages.js | 1 + .../src/less2/include/creation.less | 57 +++++++++++-------- www/common/common-ui-elements.js | 37 ++++++++---- www/form/app-form.less | 9 +++ www/form/inner.js | 18 ++++-- 5 files changed, 83 insertions(+), 39 deletions(-) diff --git a/customize.dist/messages.js b/customize.dist/messages.js index 2856350c4..aef5e749e 100755 --- a/customize.dist/messages.js +++ b/customize.dist/messages.js @@ -163,6 +163,7 @@ define(req, function(AppConfig, Default, Language) { 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."; diff --git a/customize.dist/src/less2/include/creation.less b/customize.dist/src/less2/include/creation.less index dcb721c88..03e0392ac 100644 --- a/customize.dist/src/less2/include/creation.less +++ b/customize.dist/src/less2/include/creation.less @@ -268,55 +268,64 @@ .cp-creation-help { flex: none; } + .cp-creation-team-avatar { + .avatar_main(20px); + flex: none; + display: inline-flex; + align-items: center; + justify-content: center; + svg, .lucide { + margin: 0; + } + } + .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; - display: block; button.btn { width: 100%; - max-width: 100%; - box-sizing: border-box; justify-content: flex-start; - text-align: left; 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-overflow: ellipsis; - white-space: nowrap; text-align: left; } } .cp-dropdown-content { left: 0; right: 0; - width: auto; + width: 100%; min-width: 0; max-width: 100%; + box-sizing: border-box; overflow-x: hidden; - li[role="menuitem"] > a { - display: flex; - align-items: center; - gap: 0.5rem; + li[role="menuitem"] { overflow: hidden; - .cp-creation-team-avatar { - .avatar_main(20px); - flex: none; - display: inline-flex; + > a { + display: flex; align-items: center; - justify-content: center; - svg, .lucide { - margin: 0; - } - } - .cp-creation-team-name { - flex: 1 1 0; + gap: 0.5rem; min-width: 0; + max-width: 100%; overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; } } } diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index ef9f257a4..ed5a21cbb 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2822,26 +2822,38 @@ define([ 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: [h('span.cp-creation-team-avatar', Icons.get('drive')), h('span.cp-creation-team-name', Messages.settings_cat_drive)] + content: [getTeamAvatar('-1'), h('span.cp-creation-team-name', Messages.settings_cat_drive)] }]; Object.keys(privateData.teams || {}).forEach(function (id) { var data = privateData.teams[id]; if (!data) { return; } - var avatar = h('span.cp-creation-team-avatar.cp-avatar'); - common.displayAvatar($(avatar), data.avatar, data.name); teamOptions.push({ tag: 'a', attributes: { 'data-value': id }, - content: [avatar, h('span.cp-creation-team-name', data.name)] + content: [getTeamAvatar(id), h('span.cp-creation-team-name', data.name)] }); }); teamOptions.push({ tag: 'a', attributes: { 'data-value': 'none' }, - content: [h('span.cp-creation-team-avatar', Icons.get('close')), h('span.cp-creation-team-name', Messages.autostore_hide)] + content: [getTeamAvatar('none'), h('span.cp-creation-team-name', Messages.autostore_hide)] }); var $teamSelect = UIElements.createDropdown({ text: getTeamLabel(teamValue), @@ -2853,16 +2865,19 @@ define([ common: common }); var $teamBtn = $teamSelect.find('button').addClass('btn'); - var setTeamTitle = function (label) { + var setTeamButton = function (val) { + var label = getTeamLabel(val); $teamBtn[0]?._tippy?.destroy(); - $teamBtn.attr('title', label).find('.cp-dropdown-button-title').removeAttr('title'); + $teamBtn.attr('title', label); + $teamBtn.find('.cp-dropdown-button-title').empty().append([ + getTeamAvatar(val), + h('span.cp-creation-team-name', label) + ]).removeAttr('title'); }; - setTeamTitle(getTeamLabel(teamValue)); + setTeamButton(teamValue); $teamSelect.onChange.reg(function (text, value) { teamValue = value == null ? 'none' : String(value); - var label = getTeamLabel(teamValue); - $teamBtn.find('.cp-dropdown-button-title').text(label); - setTeamTitle(label); + setTeamButton(teamValue); }); team = h('div.cp-creation-teams', [ h('span.cp-creation-store-label', Messages.team_pcsSelectLabel), diff --git a/www/form/app-form.less b/www/form/app-form.less index 37cd22686..29c0d1066 100644 --- a/www/form/app-form.less +++ b/www/form/app-form.less @@ -1553,3 +1553,12 @@ .charts_main(); } +.alertify .cp-form-guest-share-modal { + h4, h5, h6, p { + color: @cryptpad_text_col; + } + textarea { + color: @cryptpad_text_col; + } +} + diff --git a/www/form/inner.js b/www/form/inner.js index 35433c004..9527dba95 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -4921,7 +4921,7 @@ define([ rows: 2, 'aria-label': Messages.form_guestPublicTitle }); - var content = h('div', [ + var content = h('div.cp-form-guest-share-modal', [ h('h4', Messages.form_guestPublicTitle), h('p', Messages.form_guestPublicBody), linkInput @@ -4929,8 +4929,13 @@ define([ var frame; var modal = UI.dialog.customModal(content, { buttons: [{ + className: 'cancel', + name: Messages.cancel, + onClick: function () {}, + keys: [27] + }, { className: 'primary', - name: Messages.share_linkCopy, + name: Messages.form_geturl, iconClass: 'copy', onClick: function () { Clipboard.copy(viewUrl, function (copyErr) { @@ -4966,7 +4971,7 @@ define([ rows: 2, 'aria-label': Messages.form_guestAuthorTitle }); - var content = h('div', [ + var content = h('div.cp-form-guest-share-modal', [ h('h4', Messages.form_guestAuthorTitle), h('p', Messages.form_guestEditLinkDefinition), h('p', bodyMsg), @@ -4974,8 +4979,13 @@ define([ ]); var modal = UI.dialog.customModal(content, { buttons: [{ + className: 'cancel', + name: Messages.cancel, + onClick: function () {}, + keys: [27] + }, { className: 'secondary', - name: Messages.share_linkCopy, + name: Messages.form_guestAuthorCopy, iconClass: 'copy', onClick: function () { Clipboard.copy(editUrl, function (copyErr) { From 9117da0e9a020bd1d9f7bbc8586f36d2669948fa Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Thu, 10 Sep 2026 12:47:05 +0200 Subject: [PATCH 12/14] Lint fixes --- www/common/common-ui-elements.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index ed5a21cbb..897ad98ab 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2816,7 +2816,7 @@ define([ // * -1 ==> store in the user drive, and the user will be the owner // * undefined ==> ask (CryptDrive / team / don't store); default CryptDrive var team; - var teamValue = privateData.storeInTeam != null ? String(privateData.storeInTeam) : '-1'; + var teamValue = privateData.storeInTeam !== null ? String(privateData.storeInTeam) : '-1'; var getTeamLabel = function (val) { if (val === '-1') { return Messages.settings_cat_drive; } if (!val || val === 'none') { return Messages.autostore_hide; } @@ -2876,7 +2876,7 @@ define([ }; setTeamButton(teamValue); $teamSelect.onChange.reg(function (text, value) { - teamValue = value == null ? 'none' : String(value); + teamValue = value === null ? 'none' : String(value); setTeamButton(teamValue); }); team = h('div.cp-creation-teams', [ From 0e60e29877814c8b41955c1f5494ea4ba8313369 Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Thu, 10 Sep 2026 13:29:37 +0200 Subject: [PATCH 13/14] Change to secure iframe for displaying keys in the UI --- www/common/sframe-common-outer.js | 15 ++-- www/form/app-form.less | 9 --- www/form/inner.js | 107 +++------------------------ www/secureiframe/app-secure.less | 5 ++ www/secureiframe/inner.js | 117 ++++++++++++++++++++++++++++++ 5 files changed, 140 insertions(+), 113 deletions(-) diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index 8829c82fc..ab8c0f129 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -2231,13 +2231,14 @@ define([ }); }); }); - sframeChan.on('Q_GET_EDIT_URL', function (data, cb) { - if (!hashes.editHash) { return void cb(); } - cb(window.location.origin + Utils.Hash.hashToHref(hashes.editHash, 'form')); - }); - sframeChan.on('Q_GET_VIEW_URL', function (data, cb) { - if (!hashes.viewHash) { return void cb(); } - cb(window.location.origin + Utils.Hash.hashToHref(hashes.viewHash, 'form')); + sframeChan.on('EV_FORM_GUEST_SHARE_OPEN', function (data) { + initSecureModal('formGuestShare', data || {}, function (action) { + if (action && action.action === 'openView') { + var url = Utils.Hash.hashToHref(hashes.viewHash, 'form'); + var a = window.open(url); + if (!a) { sframeChan.event('EV_POPUP_BLOCKED'); } + } + }); }); sframeChan.on('EV_OPEN_VIEW_URL', function () { var url = Utils.Hash.hashToHref(hashes.viewHash, 'form'); diff --git a/www/form/app-form.less b/www/form/app-form.less index 29c0d1066..37cd22686 100644 --- a/www/form/app-form.less +++ b/www/form/app-form.less @@ -1553,12 +1553,3 @@ .charts_main(); } -.alertify .cp-form-guest-share-modal { - h4, h5, h6, p { - color: @cryptpad_text_col; - } - textarea { - color: @cryptpad_text_col; - } -} - diff --git a/www/form/inner.js b/www/form/inner.js index 9527dba95..651b9d222 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, @@ -4913,106 +4911,21 @@ define([ if (!APP.isEditor) { return false; } return true; }; - var showGuestPublicLinkModal = function () { - sframeChan.query('Q_GET_VIEW_URL', null, function (urlErr, viewUrl) { - if (urlErr || !viewUrl) { return; } - var linkInput = UI.dialog.selectableArea(viewUrl, { - id: 'cp-form-guest-public-link', - rows: 2, - 'aria-label': Messages.form_guestPublicTitle - }); - var content = h('div.cp-form-guest-share-modal', [ - h('h4', Messages.form_guestPublicTitle), - h('p', Messages.form_guestPublicBody), - linkInput - ]); - var frame; - var modal = UI.dialog.customModal(content, { - buttons: [{ - className: 'cancel', - name: Messages.cancel, - onClick: function () {}, - keys: [27] - }, { - className: 'primary', - name: Messages.form_geturl, - iconClass: 'copy', - onClick: function () { - Clipboard.copy(viewUrl, function (copyErr) { - if (!copyErr) { - UI.log(Messages.shareSuccess); - frame.closeModal(); - } else { - UI.warn(Messages.error); - } - }); - return true; - }, - keys: [13] - }] - }); - frame = UI.openCustomModal(modal); - }); - }; - var warnGuestAuthorThen = function (then, opt) { - opt = opt || {}; - if (!shouldWarnGuestAuthor()) { return void then(); } - + var warnGuestAuthorThen = function (next) { + if (!shouldWarnGuestAuthor()) { return void next(); } framework._.sfCommon.isPadStored(function (err, stored) { - sframeChan.query('Q_GET_EDIT_URL', null, function (urlErr, editUrl) { - if (urlErr || !editUrl) { return void then(); } - - var bodyMsg = Messages.form_guestAuthorBody; - if (stored) { - bodyMsg = Messages.form_guestAuthorBodyStored; - } - var linkInput = UI.dialog.selectableArea(editUrl, { - id: 'cp-form-guest-author-link', - rows: 2, - 'aria-label': Messages.form_guestAuthorTitle - }); - var content = h('div.cp-form-guest-share-modal', [ - h('h4', Messages.form_guestAuthorTitle), - h('p', Messages.form_guestEditLinkDefinition), - h('p', bodyMsg), - linkInput - ]); - var modal = UI.dialog.customModal(content, { - buttons: [{ - className: 'cancel', - name: Messages.cancel, - onClick: function () {}, - keys: [27] - }, { - className: 'secondary', - name: Messages.form_guestAuthorCopy, - iconClass: 'copy', - onClick: function () { - Clipboard.copy(editUrl, function (copyErr) { - if (!copyErr) { UI.log(Messages.form_guestAuthorCopied); } - else { UI.warn(Messages.error); } - }); - return true; - }, - keys: [] - }, { - className: 'primary', - name: opt.publicLinkFlow ? Messages.form_guestAuthorContinuePublic : Messages.continue, - onClick: function () { - then(); - }, - keys: [13] - }] - }); - UI.openCustomModal(modal); + sframeChan.event('EV_FORM_GUEST_SHARE_OPEN', { + next: next, + stored: Boolean(stored) }); }); }; $(previewBtn).click(function () { - warnGuestAuthorThen(function () { - sframeChan.event('EV_OPEN_VIEW_URL'); - }); + if (!shouldWarnGuestAuthor()) { + return void sframeChan.event('EV_OPEN_VIEW_URL'); + } + warnGuestAuthorThen('preview'); }); $(participantBtn).click(function () { if (!shouldWarnGuestAuthor()) { @@ -5022,7 +4935,7 @@ define([ }); return; } - warnGuestAuthorThen(showGuestPublicLinkModal, { publicLinkFlow: true }); + 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..c19f21cc9 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_SECURE_ACTION', { action: 'openView' }); + } + 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_SECURE_ACTION', { action: 'openView' }); + } + }, + keys: [13] + }] + }); + frame = UI.openCustomModal(modal); + displayed = frame; + }; + showAuthor(); + }); + }; // Share modal create['share'] = function (data) { From b364b4684abdf77a936daa2cf142a76743031aa6 Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Fri, 11 Sep 2026 13:16:17 +0200 Subject: [PATCH 14/14] Implement review changes and fix Don't store functionality --- www/common/common-ui-elements.js | 23 +++++++++++++++-------- www/common/cryptpad-common.js | 7 +++++++ www/common/sframe-common-outer.js | 28 ++++++++++++---------------- www/form/inner.js | 1 - www/secureiframe/inner.js | 4 ++-- 5 files changed, 36 insertions(+), 27 deletions(-) diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 897ad98ab..42c66cd36 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -2816,7 +2816,7 @@ define([ // * -1 ==> store in the user drive, and the user will be the owner // * undefined ==> ask (CryptDrive / team / don't store); default CryptDrive var team; - var teamValue = privateData.storeInTeam !== null ? String(privateData.storeInTeam) : '-1'; + 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; } @@ -2839,7 +2839,7 @@ define([ var teamOptions = [{ tag: 'a', attributes: { 'data-value': '-1' }, - content: [getTeamAvatar('-1'), h('span.cp-creation-team-name', Messages.settings_cat_drive)] + content: [getTeamAvatar('-1'), h('span.cp-creation-team-name', getTeamLabel('-1'))] }]; Object.keys(privateData.teams || {}).forEach(function (id) { var data = privateData.teams[id]; @@ -2853,7 +2853,7 @@ define([ teamOptions.push({ tag: 'a', attributes: { 'data-value': 'none' }, - content: [getTeamAvatar('none'), h('span.cp-creation-team-name', Messages.autostore_hide)] + content: [getTeamAvatar('none'), h('span.cp-creation-team-name', getTeamLabel('none'))] }); var $teamSelect = UIElements.createDropdown({ text: getTeamLabel(teamValue), @@ -2872,11 +2872,11 @@ define([ $teamBtn.find('.cp-dropdown-button-title').empty().append([ getTeamAvatar(val), h('span.cp-creation-team-name', label) - ]).removeAttr('title'); + ]); }; setTeamButton(teamValue); $teamSelect.onChange.reg(function (text, value) { - teamValue = value === null ? 'none' : String(value); + teamValue = typeof(value) === "undefined" ? 'none' : String(value); setTeamButton(teamValue); }); team = h('div.cp-creation-teams', [ @@ -3166,9 +3166,16 @@ define([ var templateContent = $template.data('content') || undefined; // Team var team; - if (teamValue && teamValue !== 'none') { - 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 0611534fa..24c7c67dc 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 ab8c0f129..6bde8344b 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1175,6 +1175,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) { @@ -1681,7 +1686,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); }); @@ -2232,20 +2239,7 @@ define([ }); }); sframeChan.on('EV_FORM_GUEST_SHARE_OPEN', function (data) { - initSecureModal('formGuestShare', data || {}, function (action) { - if (action && action.action === 'openView') { - var url = Utils.Hash.hashToHref(hashes.viewHash, 'form'); - var a = window.open(url); - if (!a) { sframeChan.event('EV_POPUP_BLOCKED'); } - } - }); - }); - 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'); - } + initSecureModal('formGuestShare', data || {}); }); Handler.formCommandHandlers(sframeChan, Utils, nThen, Cryptpad); @@ -2495,7 +2489,9 @@ 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; diff --git a/www/form/inner.js b/www/form/inner.js index 651b9d222..35e7bd8c9 100644 --- a/www/form/inner.js +++ b/www/form/inner.js @@ -4912,7 +4912,6 @@ define([ return true; }; var warnGuestAuthorThen = function (next) { - if (!shouldWarnGuestAuthor()) { return void next(); } framework._.sfCommon.isPadStored(function (err, stored) { sframeChan.event('EV_FORM_GUEST_SHARE_OPEN', { next: next, diff --git a/www/secureiframe/inner.js b/www/secureiframe/inner.js index c19f21cc9..3330fee3d 100644 --- a/www/secureiframe/inner.js +++ b/www/secureiframe/inner.js @@ -99,7 +99,7 @@ define([ if (!editUrl) { if (data.next === 'public') { return void showPublic(); } if (data.next === 'preview') { - sframeChan.event('EV_SECURE_ACTION', { action: 'openView' }); + sframeChan.event('EV_OPEN_VIEW_URL'); } return void hideIframe(); } @@ -151,7 +151,7 @@ define([ return true; } if (data.next === 'preview') { - sframeChan.event('EV_SECURE_ACTION', { action: 'openView' }); + sframeChan.event('EV_OPEN_VIEW_URL'); } }, keys: [13]