From 6c8070bdeeda84533f3281ecf5f348d868596b7a Mon Sep 17 00:00:00 2001 From: DianaXWiki Date: Tue, 14 Apr 2026 17:51:38 +0300 Subject: [PATCH] Add the logic separation between logged in and guests metric handling + not count form readonly + dedupe for duplicate increments --- www/common/common-icons.js | 5 +- www/common/common-ui-elements.js | 12 ++- www/common/sframe-common-outer.js | 168 +++++++++++++++++++++--------- www/common/sframe-common.js | 22 +++- 4 files changed, 145 insertions(+), 62 deletions(-) diff --git a/www/common/common-icons.js b/www/common/common-icons.js index c43cc70a8..74865b4f9 100644 --- a/www/common/common-icons.js +++ b/www/common/common-icons.js @@ -251,8 +251,9 @@ define([ // Other "maintenance": "construction", "release-notes": "notepad-text", - "donate": "hand-heart", - "snooze": "timer", + "crowdfunding-donate": "hand-heart", + "crowdfunding-snooze": "timer", + "crowdfunding-donate2": "ticket" }; Icons.add = (newIcons) => { diff --git a/www/common/common-ui-elements.js b/www/common/common-ui-elements.js index 4b3f99f21..d3d784c32 100644 --- a/www/common/common-ui-elements.js +++ b/www/common/common-ui-elements.js @@ -944,7 +944,6 @@ define([ return void UI.warn(Messages.autostore_error); } $(document).trigger('cpPadStored'); - common.getSframeChannel().query('Q_INCREMENT_CROWDFUNDING_ACTION', null, function () {}); UI.log(Messages.autostore_saved); }); }); @@ -3389,7 +3388,11 @@ define([ UIElements.displayCrowdfunding = function (common, force) { if (crowdfundingState) { return; } var priv = common.getMetadataMgr().getPrivateData(); - if (priv.app === 'form' && !priv.canEdit && !priv.form_auditorKey) { return; } + var inDrive = /^\/drive/; + var isDriveContext = inDrive.test(priv.pathname || ''); + if (isDriveContext) { return; } + if (!priv.channel) { return; } + if (priv.app === 'form' && priv.readOnly && !priv.form_auditorHash && !priv.form_auditorKey) { return; } var todo = function (actionCount) { crowdfundingState = true; @@ -3416,7 +3419,7 @@ define([ buttons.push({ name: Messages.features_f_subscribe, className: 'primary', - iconClass: 'subscribe', + iconClass: 'crowdfunding-donate2', onClick: function () { common.openURL('/accounts/'); Feedback.send('CROWDFUNDING_SUBSCRIBE'); @@ -3487,7 +3490,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; @@ -3537,7 +3540,6 @@ define([ return void UI.warn(Messages.autostore_error); } $(document).trigger('cpPadStored'); - common.getSframeChannel().query('Q_INCREMENT_CROWDFUNDING_ACTION', null, function () {}); delete autoStoreModal[priv.channel]; modal.delete(); UI.log(Messages.autostore_saved); diff --git a/www/common/sframe-common-outer.js b/www/common/sframe-common-outer.js index be91b17c5..a7cbd6e4c 100644 --- a/www/common/sframe-common-outer.js +++ b/www/common/sframe-common-outer.js @@ -1018,65 +1018,135 @@ define([ }); }); var CROWDFUNDING_PREFIX = 'cp_crowdfunding_'; - var CROWDFUNDING_LEGACY = { visitCount: 'cp_crowdfundingVisitCount', firstSeen: 'cp_crowdfundingFirstSeen', lastShownAtCount: 'cp_crowdfundingLastShownAtCount', lastShownAtTime: 'cp_crowdfundingLastShownAtTime' }; - var crowdfundingKey = function (suffix) { - return CROWDFUNDING_PREFIX + suffix; - }; - var crowdfundingGet = function (suffix) { - var k = crowdfundingKey(suffix); - var val = localStorage.getItem(k); - if (val !== null && val !== '') return val; - var legacy = CROWDFUNDING_LEGACY[suffix]; - if (legacy) { - val = localStorage.getItem(legacy); - if (val !== null && val !== '') { - try { localStorage.setItem(k, val); localStorage.removeItem(legacy); } catch (e) {} - return val; - } - } - return null; - }; - var CROWDFUNDING_MIN_ACTIONS = 5; - var CROWDFUNDING_ACTIONS_INTERVAL = 10; + var CROWDFUNDING_DRIVE_KEY = ['general', 'crowdfunding_metrics']; + var CROWDFUNDING_MIN_ACTIONS = 10; + var CROWDFUNDING_ACTIONS_INTERVAL = 15; var CROWDFUNDING_MIN_QUOTA_MB = 10; var CROWDFUNDING_ONE_DAY_MS = 24 * 60 * 60 * 1000; + var CROWDFUNDING_OPEN_DEDUPE_MS = 15000; + var crowdfundingLastOpenScope = null; + var crowdfundingLastOpenAt = 0; + 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(crowdfundingGetLS()); + } + 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 (e) { + if (!e) { + return cb && cb(); + } + 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) {} + 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) { - var check = function () { - var actionCount = Number(crowdfundingGet('visitCount')) || 0; - var lastShownAtCount = Number(crowdfundingGet('lastShownAtCount')) || 0; - var lastShownAtTime = Number(crowdfundingGet('lastShownAtTime')) || 0; + 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_ONE_DAY_MS); - cb({ - show: actionCount >= nextThreshold && enoughTimePassed, - actionCount: actionCount - }); - }; - if (CROWDFUNDING_MIN_QUOTA_MB <= 0) { return check(); } - Cryptpad.getPinnedUsage({}, function (e, used) { - if (e || typeof used !== 'number' || (used / (1024 * 1024)) < CROWDFUNDING_MIN_QUOTA_MB) { - return cb({ show: false }); + var showFromActions = actionCount >= nextThreshold && enoughTimePassed; + if (showFromActions) { + return cb({ + show: true, + actionCount: actionCount + }); } - check(); + 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) { - try { - if (data && typeof data.count === 'number') localStorage.setItem(crowdfundingKey('lastShownAtCount'), String(data.count)); - localStorage.setItem(crowdfundingKey('lastShownAtTime'), String(Date.now())); - } catch (e) { console.warn('crowdfunding write failed', e); } - cb(); + crowdfundingReadMetrics(function (metrics) { + if (data && typeof data.count === 'number') { metrics.lastShownAtCount = data.count; } + metrics.lastShownAtTime = Date.now(); + crowdfundingWriteMetrics(metrics, cb); + }); }); - sframeChan.on('Q_INCREMENT_CROWDFUNDING_ACTION', function (data, cb) { + sframeChan.on('Q_CROWDFUNDING_INCREMENT_OPEN', function (data, cb) { + if (readOnly) { return cb && cb(); } + var requestedScope = data && data.scope; + var scope = (secret && secret.channel) || requestedScope; + if (!scope) { return cb && cb(); } + var now = Date.now(); + if (crowdfundingLastOpenScope === scope && + (now - crowdfundingLastOpenAt) < CROWDFUNDING_OPEN_DEDUPE_MS) { + return cb && cb(); + } try { - var k = crowdfundingKey('visitCount'); - var count = (Number(localStorage.getItem(k)) || 0) + 1; - localStorage.setItem(k, String(count)); - if (count === 1) localStorage.setItem(crowdfundingKey('firstSeen'), String(Date.now())); - } catch (e) { console.warn('crowdfunding write failed', e); } - cb(); + var dedupeRaw = localStorage.getItem(CROWDFUNDING_PREFIX + 'open_dedupe'); + var dedupe = dedupeRaw ? JSON.parse(dedupeRaw) : null; + if (dedupe && dedupe.scope === scope && + typeof dedupe.ts === 'number' && + (now - dedupe.ts) < CROWDFUNDING_OPEN_DEDUPE_MS) { + return cb && cb(); + } + localStorage.setItem(CROWDFUNDING_PREFIX + 'open_dedupe', JSON.stringify({ + scope: scope, + ts: now + })); + } catch (e) {} + crowdfundingLastOpenScope = scope; + crowdfundingLastOpenAt = now; + crowdfundingIncrementAction(cb); }); Cryptpad.mailbox.onEvent.reg(function (data, cb) { @@ -2381,12 +2451,6 @@ define([ sframeChan.on('Q_CREATE_PAD', function (data, cb) { if (!isNewFile || rtStarted) { return; } - try { - var k = 'cp_crowdfunding_visitCount'; - var count = (Number(localStorage.getItem(k)) || 0) + 1; - localStorage.setItem(k, String(count)); - if (count === 1) localStorage.setItem('cp_crowdfunding_firstSeen', String(Date.now())); - } catch (e) { console.warn('crowdfunding write failed', e); } let feedbackKey = 'APP_' + parsed.type.toUpperCase() + '_CREATE'; Utils.Feedback.send(feedbackKey); // Create a new hash diff --git a/www/common/sframe-common.js b/www/common/sframe-common.js index 3a33e576b..212d87fb6 100644 --- a/www/common/sframe-common.js +++ b/www/common/sframe-common.js @@ -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,9 +1063,25 @@ define([ document.title = title; }); - ctx.sframeChan.query('Q_INCREMENT_CROWDFUNDING_ACTION', null, function () { + var showCrowdfunding = function () { UIElements.displayCrowdfunding(funcs); - }); + }; + var privateData = ctx.metadataMgr.getPrivateData(); + var inDrive = /^\/drive/; + var isDriveContext = inDrive.test(privateData.pathname || ''); + var isReadOnlyContext = Boolean(privateData.readOnly); + var isReadOnlyFormResponse = privateData.app === 'form' && + !privateData.form_auditorKey && + ((!privateData.canEdit) || + (isReadOnlyContext && !privateData.form_auditorHash)); + var openScope = privateData.channel; + if (isDriveContext || isReadOnlyFormResponse || !openScope) { + showCrowdfunding(); + } else { + ctx.sframeChan.query('Q_CROWDFUNDING_INCREMENT_OPEN', { + scope: openScope + }, showCrowdfunding); + } ctx.sframeChan.ready();