Merge remote-tracking branch 'origin/crowdfunding-timing' into 2026.4-rc

This commit is contained in:
yflory 2026-05-06 14:18:13 +02:00
commit 550b89fae5
8 changed files with 198 additions and 36 deletions

View File

@ -172,6 +172,12 @@ define(req, function(AppConfig, Default, Language) {
Messages.form_date_time = "Select date and time";
Messages.sent_chatMessage = "{0} has sent you a message"; // XXX
/// XXX
// To be edited for the Spring Release
// Messages.dontShowAgain = "Don't show again";
// Messages.crowdfunding_popup_text = "<h3>We need your help!</h3>To ensure that CryptPad is actively developed, consider supporting the project via the OpenCollective page, where you can see our <b>Roadmap</b> and <b>Funding goals</b>."
Messages.crowdfunding_popup_text2 = "Alternatively, you can subscribe on this instance." // XXX
return Messages;
});

View File

@ -976,6 +976,12 @@
stroke-width: 3.5;
vector-effect: non-scaling-stroke;
}
&.cp-app-drive-content-grid {
li.cp-app-drive-element-row > svg.lucide * {
stroke-width: 3.5;
vector-effect: non-scaling-stroke;
}
}
}
#cp-app-drive-new-ghost-dialog.cp-modal-container {

View File

@ -85,7 +85,7 @@ define([
"share": "share-2",
"download": "hard-drive-download",
"destroy": "shredder",
"donate": "hand-coins",
"donate": "hand-heart",
"send": "send",
"cloud-upload": "cloud-upload",
"print": "printer",
@ -250,7 +250,10 @@ define([
"badge-error": "circle-alert",
// Other
"maintenance": "construction",
"release-notes": "notepad-text"
"release-notes": "notepad-text",
"crowdfunding-donate": "hand-heart",
"crowdfunding-snooze": "timer",
"crowdfunding-donate2": "ticket"
};
Icons.add = (newIcons) => {

View File

@ -3392,39 +3392,72 @@ define([
UIElements.displayCrowdfunding = function (common, force) {
if (crowdfundingState) { return; }
var priv = common.getMetadataMgr().getPrivateData();
if (priv.app === 'form' && !priv.canEdit && !priv.form_auditorKey) { return; }
if (priv.app === 'drive') { return; }
if (!priv.channel) { return; }
if (priv.app === 'form' && priv.readOnly && !priv.form_auditorHash && !priv.form_auditorKey) { return; }
var todo = function () {
crowdfundingState = true;
// Display the popup
var text = Messages.crowdfunding_popup_text;
var yes = h('button.cp-corner-primary', [
Icons.get('external-link'),
'OpenCollective'
]);
var no = h('button.cp-corner-cancel', Messages.crowdfunding_popup_no);
var actions = h('div', [no, yes]);
var recordShown = function () {
common.getSframeChannel().query('Q_RECORD_CROWDFUNDING_SHOWN', {}, function () {});
};
var dontShowAgain = function () {
common.setAttribute(['general', 'crowdfunding'], false);
Feedback.send('CROWDFUNDING_NEVER');
};
var modal = UI.cornerPopup(text, actions, '', {
big: true,
alt: true,
dontShowAgain: dontShowAgain
var content = Messages.crowdfunding_popup_text;
var buttons = [
{
name: Messages.crowdfunding_popup_no,
className: 'cancel',
iconClass: 'crowdfunding-snooze',
onClick: function () {
recordShown();
Feedback.send('CROWDFUNDING_NO');
}
}
];
if (!Config.removeDonateButton) {
buttons.push({
name: Messages.crowdfunding_button2,
className: 'primary',
iconClass: 'crowdfunding-donate',
onClick: function () {
recordShown();
common.openURL(priv.accounts.donateURL);
Feedback.send('CROWDFUNDING_YES');
}
});
}
if (Config.accounts_api && priv.accountName) {
content += ' ' + Messages.crowdfunding_popup_text2;
buttons.push({
name: Messages.features_f_subscribe,
className: 'primary',
iconClass: 'crowdfunding-donate2',
onClick: function () {
recordShown();
common.openURL('/accounts/');
Feedback.send('CROWDFUNDING_SUBSCRIBE');
}
});
}
buttons.push({
name: Messages.dontShowAgain,
className: 'cancel left',
iconClass: 'close',
onClick: function () {
recordShown();
dontShowAgain();
}
});
$(yes).click(function () {
modal.delete();
common.openURL(priv.accounts.donateURL);
Feedback.send('CROWDFUNDING_YES');
});
$(no).click(function () {
modal.delete();
Feedback.send('CROWDFUNDING_NO');
var modal = UI.dialog.customModal(content, {
force: true,
scrollable: true,
buttons: buttons
});
UI.openCustomModal(modal, { wide: true });
};
if (force) {
@ -3434,13 +3467,16 @@ define([
if (AppConfig.disableCrowdfundingMessages) { return; }
if (priv.plan) { return; }
if (Config.removeDonateButton && !Config.accounts_api) { return; }
crowdfundingState = true;
common.getAttribute(['general', 'crowdfunding'], function (err, val) {
if (err || val === false) { return; }
common.getSframeChannel().query('Q_GET_PINNED_USAGE', null, function (err, obj) {
var quotaMb = obj.quota / (1024 * 1024);
if (quotaMb < 10) { return; }
if (err || val === false) { crowdfundingState = false; return; }
common.getSframeChannel().query('Q_CROWDFUNDING_SHOULD_SHOW', null, function (err, result) {
if (err || !result || !result.show) {
crowdfundingState = false;
return;
}
todo();
});
});
@ -3461,7 +3497,7 @@ define([
// This pad will be deleted automatically, it shouldn't be stored
if (priv.burnAfterReading) { return; }
if (priv.app === 'form' && !priv.canEdit && !priv.form_auditorKey && !common.isLoggedIn()) { return; }
if (priv.app === 'form' && priv.readOnly && !priv.form_auditorHash && !priv.form_auditorKey && !common.isLoggedIn()) { return; }
var typeMsg = priv.pathname.indexOf('/file/') !== -1 ? Messages.autostore_file :
priv.pathname.indexOf('/drive/') !== -1 ? Messages.autostore_sf :
Messages.autostore_pad;

View File

@ -151,8 +151,8 @@ define([
sessionStorage.clear();
try {
Object.keys(localStorage || {}).forEach(function (k) {
// Remvoe everything in localStorage except CACHE and FS_hash
if (/^CRYPTPAD_CACHE/.test(k) || /^LESS_CACHE/.test(k) || k === Constants.fileHashKey || /^CRYPTPAD_STORE|colortheme/.test(k)) { return; }
// Remove everything in localStorage except CACHE and FS_hash
if (/^CRYPTPAD_CACHE/.test(k) || /^LESS_CACHE/.test(k) || k === Constants.fileHashKey || /^CRYPTPAD_STORE|colortheme/.test(k) || (!isDeletion && /^cp_crowdfunding_/.test(k))) { return; }
delete localStorage[k];
});
} catch (e) { console.error(e); }

View File

@ -1028,6 +1028,110 @@ define([
cb({error:e});
});
});
var CROWDFUNDING_PREFIX = 'cp_crowdfunding_';
var CROWDFUNDING_DRIVE_KEY = ['general', 'crowdfunding_metrics'];
// First action (opening or creating a document) count threshold before showing the banner
var CROWDFUNDING_MIN_ACTIONS = 5;
// Additional actions required after each shown banner
var CROWDFUNDING_ACTIONS_INTERVAL = 10;
// Quota usage threshold for quota-based banner display
var CROWDFUNDING_MIN_QUOTA_MB = 50;
// Cooldown between banner displays based on last shown timestamp in milliseconds
var CROWDFUNDING_COOLDOWN_MS = 24 * 60 * 60 * 1000;
var crowdfundingGetLS = function () {
var get = function (suffix) {
var k = CROWDFUNDING_PREFIX + suffix;
var val = localStorage.getItem(k);
if (val !== null && val !== '') { return Number(val) || null; }
return null;
};
return {
visitCount: get('visitCount') || 0,
firstSeen: get('firstSeen') || null,
lastShownAtCount: get('lastShownAtCount') || 0,
lastShownAtTime: get('lastShownAtTime') || 0
};
};
// Read metrics: encrypted drive for logged-in users, localStorage for guests
var crowdfundingReadMetrics = function (cb) {
if (!Utils.LocalStore.isLoggedIn()) { return cb(crowdfundingGetLS()); }
Cryptpad.getAttribute(CROWDFUNDING_DRIVE_KEY, function (e, metrics) {
if (e || !metrics || typeof metrics !== 'object') {
return cb({
visitCount: 0,
firstSeen: null,
lastShownAtCount: 0,
lastShownAtTime: 0
});
}
cb(metrics);
});
};
// Write metrics: encrypted drive for logged-in users, localStorage for guests
var crowdfundingWriteMetrics = function (metrics, cb) {
if (!Utils.LocalStore.isLoggedIn()) {
try {
['visitCount', 'firstSeen', 'lastShownAtCount', 'lastShownAtTime'].forEach(function (k) {
if (metrics[k] !== null && metrics[k] !== undefined) {
localStorage.setItem(CROWDFUNDING_PREFIX + k, String(metrics[k]));
}
});
} catch (e) {}
return cb && cb();
}
Cryptpad.setAttribute(CROWDFUNDING_DRIVE_KEY, metrics, function () {
cb && cb();
});
};
var crowdfundingIncrementAction = function (cb) {
crowdfundingReadMetrics(function (metrics) {
metrics.visitCount = (metrics.visitCount || 0) + 1;
if (!metrics.firstSeen) { metrics.firstSeen = Date.now(); }
crowdfundingWriteMetrics(metrics, cb);
});
};
sframeChan.on('Q_CROWDFUNDING_SHOULD_SHOW', function (data, cb) {
crowdfundingReadMetrics(function (metrics) {
var actionCount = metrics.visitCount || 0;
var lastShownAtCount = metrics.lastShownAtCount || 0;
var lastShownAtTime = metrics.lastShownAtTime || 0;
var now = Date.now();
var nextThreshold = lastShownAtCount === 0 ? CROWDFUNDING_MIN_ACTIONS : lastShownAtCount + CROWDFUNDING_ACTIONS_INTERVAL;
var enoughTimePassed = lastShownAtTime === 0 || (now - lastShownAtTime >= CROWDFUNDING_COOLDOWN_MS);
var showFromActions = actionCount >= nextThreshold && enoughTimePassed;
if (showFromActions) {
return cb({
show: true,
actionCount: actionCount
});
}
if (CROWDFUNDING_MIN_QUOTA_MB <= 0) {
return cb({
show: false,
actionCount: actionCount
});
}
Cryptpad.getPinnedUsage({}, function (e, used) {
var usedMb = (typeof used === 'number') ? (used / (1024 * 1024)) : 0;
cb({
show: !e && usedMb >= CROWDFUNDING_MIN_QUOTA_MB && enoughTimePassed,
actionCount: actionCount
});
});
});
});
sframeChan.on('Q_RECORD_CROWDFUNDING_SHOWN', function (data, cb) {
crowdfundingReadMetrics(function (metrics) {
metrics.lastShownAtCount = (data && typeof data.count === 'number') ? data.count : (metrics.visitCount || 0);
metrics.lastShownAtTime = Date.now();
crowdfundingWriteMetrics(metrics, cb);
});
});
sframeChan.on('Q_CROWDFUNDING_INCREMENT_OPEN', function (data, cb) {
if (readOnly) { return cb && cb(); }
crowdfundingIncrementAction(cb);
});
Cryptpad.mailbox.onEvent.reg(function (data, cb) {
sframeChan.query('EV_MAILBOX_EVENT', data, function (err, obj) {

View File

@ -768,7 +768,7 @@ define([
// Ctrl+E: New pad modal
var priv = ctx.metadataMgr.getPrivateData();
if (e.which === 69 && isApp) {
if (priv.app === 'form' && !priv.canEdit && !priv.form_auditorKey) { return; }
if (priv.app === 'form' && priv.readOnly && !priv.form_auditorHash && !priv.form_auditorKey) { return; }
e.preventDefault();
return void funcs.createNewPadModal();
}
@ -1063,10 +1063,16 @@ define([
document.title = title;
});
funcs.isPadStored(function (err, val) {
if (err || !val) { return; }
var showCrowdfunding = function () {
UIElements.displayCrowdfunding(funcs);
});
};
var privateData = ctx.metadataMgr.getPrivateData();
var isDriveContext = privateData.app === 'drive';
var isReadOnlyFormResponse = privateData.app === 'form' && privateData.readOnly && !privateData.form_auditorHash && !privateData.form_auditorKey;
var skipCrowdfunding = privateData.secureIframe === true || privateData.unsafeIframe === true || isReadOnlyFormResponse || isDriveContext;
if (!skipCrowdfunding) {
ctx.sframeChan.query('Q_CROWDFUNDING_INCREMENT_OPEN', {}, showCrowdfunding);
}
ctx.sframeChan.ready();

View File

@ -106,6 +106,7 @@ define([
origin: window.location.origin,
pathname: window.location.pathname,
feedbackAllowed: Utils.Feedback.state,
unsafeIframe: true,
};
for (var k in additionalPriv) { metaObj.priv[k] = additionalPriv[k]; }