Changeset 0403187 in Klonkt for src/routes/posts.js
- Timestamp:
- 07/01/2026 12:53:32 PM (2 months ago)
- Branches:
- main
- Children:
- bb3f67c
- Parents:
- 731e431
- File:
-
- 1 edited
-
src/routes/posts.js (modified) (11 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/routes/posts.js
r731e431 r0403187 116 116 if (!Number.isFinite(n) || n < 0) return 0; 117 117 return n; 118 } 119 120 // Poll durations offered in the editor (seconds) — the Mastodon set (5m … 7d). 121 const POLL_DURATIONS = new Set([300, 1800, 3600, 21600, 43200, 86400, 259200, 604800]); 122 // Parse the editor's poll fields into the poll_json we store on the post (which 123 // buildNote federates as an AS2 Question). Returns null when no valid poll (< 2 124 // options or the poll checkbox is off). endTime is set from the chosen duration 125 // (default 1 day) so the Scheduler can close it. 126 function parsePollForm(body) { 127 if (!body || !body.poll_enabled) return null; 128 const raw = body.poll_option == null ? [] : (Array.isArray(body.poll_option) ? body.poll_option : [body.poll_option]); 129 const options = []; 130 const seen = new Set(); 131 for (const o of raw) { 132 const name = String(o == null ? '' : o).trim().slice(0, 100); 133 if (!name) continue; 134 const key = name.toLowerCase(); 135 if (seen.has(key)) continue; seen.add(key); 136 options.push({ name }); 137 if (options.length >= 8) break; 138 } 139 if (options.length < 2) return null; 140 const dur = parseInt(body.poll_duration, 10); 141 const secs = POLL_DURATIONS.has(dur) ? dur : 86400; 142 return JSON.stringify({ multiple: !!body.poll_multiple, options, endTime: new Date(Date.now() + secs * 1000).toISOString(), closed: false }); 118 143 } 119 144 … … 241 266 const validTypes = new Set(['post', 'foto', 'video', 'audio']); 242 267 const finalType = validTypes.has(type) ? type : 'post'; 268 const pollJson = parsePollForm(req.body); // AS2 Question definition, or null 243 269 const postId = uuid(); 244 270 const now = new Date().toISOString(); … … 258 284 INSERT INTO posts ( 259 285 id, site_id, slug, author_id, title, content, excerpt, 260 status, cover_image_url, cover_video_url, pinned, tags, type, noindex, fan_only, nsfw, content_warning, p ublish_at,286 status, cover_image_url, cover_video_url, pinned, tags, type, noindex, fan_only, nsfw, content_warning, poll_json, publish_at, 261 287 created_at, updated_at, published_at 262 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )288 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) 263 289 `).run( 264 290 postId, site.id, finalSlug, req.session.user.id, … … 266 292 finalStatus, cover_image_url || null, (req.body.cover_video_url || null), parsePinnedRank(pinned), 267 293 JSON.stringify((tags || '').split(',').map(t => t.trim()).filter(Boolean)), 268 finalType, noindex ? 1 : 0, fanOnly, nsfw, cw, p ublishAt,294 finalType, noindex ? 1 : 0, fanOnly, nsfw, cw, pollJson, publishAt, 269 295 now, now, publishedAt 270 296 ); … … 287 313 id: postId, slug: finalSlug, title: title || finalSlug, 288 314 content: cleanContent, cover_image_url: cover_image_url || null, cover_video_url: req.body.cover_video_url || null, 289 published_at: publishedAt, created_at: now, fan_only: fanOnly, nsfw, content_warning: cw, 315 published_at: publishedAt, created_at: now, fan_only: fanOnly, nsfw, content_warning: cw, poll_json: pollJson, 290 316 }).catch(() => { /* best-effort */ }); 291 317 } … … 321 347 } 322 348 349 // A poll with votes is frozen (options can't change) — flag it so the editor disables the poll fields. 350 let pollLocked = false; 351 try { pollLocked = !!(post.poll_json && db.prepare('SELECT 1 FROM poll_votes WHERE post_id = ? LIMIT 1').get(post.id)); } catch { /* ignore */ } 352 323 353 renderPage(req, res, 'pages/post-edit', { 324 354 post, 325 355 isNew: false, 356 pollLocked, 326 357 fediOpenAudio: postAudioFediOpen(site.id, post.content), 327 358 pageTitle: 'Edit: ' + (post.title || 'Untitled'), … … 353 384 const finalType = validTypes.has(type) ? type : (post.type || 'post'); 354 385 386 // A poll that has already received votes is frozen (you can still edit the surrounding 387 // post, but not the options) — changing options after votes would scramble the tally and 388 // is disallowed on the fediverse too. Otherwise re-parse the poll form (add/remove/disable). 389 const hasVotes = !!(post.poll_json && (() => { try { return db.prepare('SELECT 1 FROM poll_votes WHERE post_id = ? LIMIT 1').get(post.id); } catch { return false; } })()); 390 const pollJson = hasVotes ? post.poll_json : parsePollForm(req.body); 391 355 392 // Sanitize before storage — same pipeline as create. 356 393 const cleanContent = HtmlSanitizerService.sanitize(content || ''); … … 386 423 title = ?, content = ?, excerpt = ?, status = ?, 387 424 cover_image_url = ?, cover_video_url = ?, pinned = ?, tags = ?, 388 type = ?, noindex = ?, fan_only = ?, nsfw = ?, content_warning = ?, p ublish_at = ?,425 type = ?, noindex = ?, fan_only = ?, nsfw = ?, content_warning = ?, poll_json = ?, publish_at = ?, 389 426 slug = ?, published_at = ?, updated_at = ? 390 427 WHERE id = ? … … 393 430 cover_image_url || null, (req.body.cover_video_url || null), parsePinnedRank(pinned), 394 431 JSON.stringify((tags || '').split(',').map(t => t.trim()).filter(Boolean)), 395 finalType, noindex ? 1 : 0, fanOnly, nsfw, cw, p ublishAt,432 finalType, noindex ? 1 : 0, fanOnly, nsfw, cw, pollJson, publishAt, 396 433 finalSlug, publishedAt, now, post.id 397 434 ); … … 418 455 id: post.id, slug: finalSlug, title: title || finalSlug, 419 456 content: cleanContent, cover_image_url: cover_image_url || null, cover_video_url: req.body.cover_video_url || null, 420 published_at: publishedAt, created_at: post.created_at, fan_only: fanOnly, nsfw, content_warning: cw, 457 published_at: publishedAt, created_at: post.created_at, fan_only: fanOnly, nsfw, content_warning: cw, poll_json: pollJson, 421 458 }; 422 459 if (post.status !== 'published') ActivityPubService.deliverCreate(site, apPost).catch(() => { /* best-effort */ }); … … 1079 1116 renderPage(req, res, 'pages/post', { 1080 1117 post, 1118 poll: ActivityPubService.ownPollView(post), 1081 1119 newerPost, 1082 1120 olderPost,
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)