Changeset 928d1c7 in Klonkt for src/views


Ignore:
Timestamp:
07/21/2026 01:06:47 AM (7 weeks ago)
Author:
Robin <roboburr@…>
Branches:
main
Children:
9e9e6f9
Parents:
61e3daf
git-author:
Robin <roboburr@…> (07/21/2026 01:06:45 AM)
git-committer:
Robin <roboburr@…> (07/21/2026 01:06:47 AM)
Message:

Feature: paid posts slice 2, post model + teaser gate

A post can be marked paid (klonkt-demo-aki), premium-gated in the
editor with an optional per-post price; additive columns posts.paid +
paid_min_cents. On the web, a paid post shows only a teaser to anyone
who is not the owner/editor (new pages/paid-gate, mirroring the
fan-gate); the owner previews the full post. The passkey unlock arrives
in slices 3-4, so the gate says so for now.

Federation is leak-safe: buildNote federates only a PUBLIC teaser (the
excerpt, else the first paragraph, never later content) plus a
"read the full post (supporters)" link back, and no media attachments.
A short paid post can no longer spill its body: the teaser is the first
paragraph only, pinned by a test.

Changed files:
src/config/database.js

  • additive columns posts.paid, posts.paid_min_cents

src/routes/posts.js

  • create/update read paid + price (premium-gated), store them, pass to deliverCreate/Update; paidTeaser helper; the paid web gate

src/services/ActivityPubService.js

  • buildNote: paid post -> public teaser + link, first paragraph only

src/views/pages/post-edit.ejs

  • paid toggle + price field (in the premium block)

New file:
src/views/pages/paid-gate.ejs

  • teaser + supporters notice

test/paid-federation.test.js

  • teaser + link, no full content, excerpt-as-teaser, non-paid intact

-robo
Co-Authored-By: Claude Opus 4.8 <noreply@…>

Location:
src/views/pages
Files:
1 added
1 edited

Legend:

Unmodified
Added
Removed
  • src/views/pages/post-edit.ejs

    r61e3daf r928d1c7  
    367367            <span>🔒 <%= t('pedit.fan_only_label') %></span>
    368368          </label>
     369          <label class="pe-checkbox" style="margin-top:8px">
     370            <input type="checkbox" name="paid" value="1" id="pe-paid" <%= post.paid ? 'checked' : '' %>>
     371            <span>💶 Betaalde post (supporters ontgrendelen met Patreon)</span>
     372          </label>
     373          <div id="pe-paid-price" style="margin:6px 0 0 26px;<%= post.paid ? '' : 'display:none' %>">
     374            <label style="font-size:.85rem;opacity:.8">Vereist steunbedrag (euro, leeg = standaard)
     375              <input type="text" name="paid_min_eur" inputmode="decimal" style="width:100px"
     376                     value="<%= post.paid_min_cents ? (post.paid_min_cents/100).toFixed(2) : '' %>">
     377            </label>
     378          </div>
     379          <script nonce="<%= cspNonce %>">
     380            (function(){ var p=document.getElementById('pe-paid'), box=document.getElementById('pe-paid-price');
     381              if (p&&box&&!p.__wired){ p.__wired=true; p.addEventListener('change', function(){ box.style.display=p.checked?'':'none'; }); } })();
     382          </script>
    369383          <% var _scheduled = !!(post.publish_at && (post.status === 'scheduled' || Date.parse(String(post.publish_at).replace(' ', 'T')) > Date.now())); %>
    370384          <label class="pe-checkbox" style="margin-top:8px">
Note: See TracChangeset for help on using the changeset viewer.