const { div, h2, p, section, button, form, a, img, video: videoHyperaxe, audio: audioHyperaxe, input, table, tr, th, td, br, span } = require("../server/node_modules/hyperaxe"); const { template, i18n } = require('./main_views'); const { config } = require('../server/SSB_server.js'); const { renderTextWithStyles } = require('../backend/renderTextWithStyles'); const { renderUrl } = require('../backend/renderUrl'); const opinionCategories = require('../backend/opinion_categories'); const { sanitizeHtml } = require('../backend/sanitizeHtml'); const seenDocumentTitles = new Set(); const renderContentHtml = (content, key) => { switch (content.type) { case 'bookmark': return div({ class: 'opinion-bookmark' }, div({ class: 'card-section bookmark' }, form({ method: "GET", action: `/bookmarks/${encodeURIComponent(key)}` }, button({ type: "submit", class: "filter-btn" }, i18n.viewDetails) ), br(), h2(content.url ? div({ class: 'card-field' }, span({ class: 'card-label' }, p(a({ href: content.url, target: '_blank', class: "bookmark-url" }, content.url))) ) : ""), content.lastVisit ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.bookmarkLastVisit + ':'), span({ class: 'card-value' }, new Date(content.lastVisit).toLocaleString()) ) : "", content.description ? [ span({ class: 'card-label' }, i18n.bookmarkDescriptionLabel + ":"), p(...renderUrl(content.description)) ] : null ) ); case 'image': return div({ class: 'opinion-image' }, div({ class: 'card-section image' }, form({ method: "GET", action: `/images/${encodeURIComponent(key)}` }, button({ type: "submit", class: "filter-btn" }, i18n.viewDetails) ), br(), content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.imageTitleLabel + ':'), span({ class: 'card-value' }, content.title) ) : "", content.description ? [ span({ class: 'card-label' }, i18n.imageDescriptionLabel + ":"), p(...renderUrl(content.description)) ] : null, content.meme ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.trendingCategory + ':'), span({ class: 'card-value' }, i18n.meme) ) : "", br(), div({ class: 'card-field' }, img({ src: `/blob/${encodeURIComponent(content.url)}`, class: 'feed-image' }) ) ) ); case 'video': return div({ class: 'opinion-video' }, div({ class: 'card-section video' }, form({ method: "GET", action: `/videos/${encodeURIComponent(key)}` }, button({ type: "submit", class: "filter-btn" }, i18n.viewDetails) ), br(), content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.videoTitleLabel + ':'), span({ class: 'card-value' }, content.title) ) : "", content.description ? [ span({ class: 'card-label' }, i18n.videoDescriptionLabel + ":"), p(...renderUrl(content.description)) ] : null, div({ class: 'card-field' }, videoHyperaxe({ controls: true, src: `/blob/${encodeURIComponent(content.url)}`, type: content.mimeType || 'video/mp4', width: '640', height: '360' }) ) ) ); case 'audio': return div({ class: 'opinion-audio' }, div({ class: 'card-section audio' }, form({ method: "GET", action: `/audios/${encodeURIComponent(key)}` }, button({ type: "submit", class: "filter-btn" }, i18n.viewDetails) ), br(), content.title ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.audioTitleLabel + ':'), span({ class: 'card-value' }, content.title) ) : "", content.description ? [ span({ class: 'card-label' }, i18n.audioDescriptionLabel + ":"), p(...renderUrl(content.description)) ] : null, div({ class: 'card-field' }, audioHyperaxe({ controls: true, src: `/blob/${encodeURIComponent(content.url)}`, type: content.mimeType, preload: 'metadata' }) ) ) ); case 'document': { const t = content.title?.trim(); if (t && seenDocumentTitles.has(t)) return null; if (t) seenDocumentTitles.add(t); return div({ class: 'opinion-document' }, div({ class: 'card-section document' }, form({ method: "GET", action: `/documents/${encodeURIComponent(key)}` }, button({ type: "submit", class: "filter-btn" }, i18n.viewDetails) ), br(), t ? div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.documentTitleLabel + ':'), span({ class: 'card-value' }, t) ) : "", content.description ? [ span({ class: 'card-label' }, i18n.documentDescriptionLabel + ":"), p(...renderUrl(content.description)) ] : null, div({ class: 'card-field' }, div({ class: 'pdf-viewer-container', 'data-pdf-url': `/blob/${encodeURIComponent(content.url)}` }) ) ) ); } case 'feed': return div({ class: 'opinion-feed' }, div({ class: 'card-section feed' }, div({ class: 'feed-text', innerHTML: sanitizeHtml(renderTextWithStyles(content.text)) }), h2({ class: 'card-field' }, span({ class: 'card-label' }, `${i18n.tribeFeedRefeeds}: `), span({ class: 'card-value' }, content.refeeds) ) ) ); case 'votes': { const votesList = content.votes && typeof content.votes === 'object' ? Object.entries(content.votes).map(([option, count]) => ({ option, count })) : []; return div({ class: 'opinion-votes' }, div({ class: 'card-section votes' }, form({ method: "GET", action: `/votes/${encodeURIComponent(key)}` }, button({ type: "submit", class: "filter-btn" }, i18n.viewDetails) ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteQuestionLabel + ':'), span({ class: 'card-value' }, content.question) ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteDeadline + ':'), span({ class: 'card-value' }, content.deadline ? new Date(content.deadline).toLocaleString() : '') ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.voteTotalVotes + ':'), span({ class: 'card-value' }, content.totalVotes) ), table( tr(...votesList.map(({ option }) => th(i18n[option] || option))), tr(...votesList.map(({ count }) => td(count))) ) ) ); } case 'transfer': return div({ class: 'opinion-transfer' }, div({ class: 'card-section transfer' }, form({ method: "GET", action: `/transfers/${encodeURIComponent(key)}` }, button({ type: "submit", class: "filter-btn" }, i18n.viewDetails) ), br(), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.concept + ':'), span({ class: 'card-value' }, content.concept) ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.deadline + ':'), span({ class: 'card-value' }, content.deadline ? new Date(content.deadline).toLocaleString() : '') ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.status + ':'), span({ class: 'card-value' }, content.status) ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.amount + ':'), span({ class: 'card-value' }, content.amount) ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.from + ':'), span({ class: 'card-value' }, a({ class: 'user-link', href: `/author/${encodeURIComponent(content.from)}`, target: "_blank" }, content.from)) ), div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.to + ':'), span({ class: 'card-value' }, a({ class: 'user-link', href: `/author/${encodeURIComponent(content.to)}`, target: "_blank" }, content.to)) ), h2({ class: 'card-field' }, span({ class: 'card-label' }, `${i18n.transfersConfirmations}: `), span({ class: 'card-value' }, `${content.confirmedBy.length}/2`) ) ) ); default: return div({ class: 'styled-text' }, div({ class: 'card-section styled-text-content' }, div({ class: 'card-field' }, span({ class: 'card-label' }, i18n.textContentLabel + ':'), span({ class: 'card-value', innerHTML: sanitizeHtml(content.text || content.description || content.title || '[no content]') }) ) ) ); } }; exports.opinionsView = (items, filter) => { seenDocumentTitles.clear(); items = items .filter(item => { const c = item.value?.content || item.content; return c && typeof c === 'object' && c.type !== 'tombstone'; }) .sort((a, b) => { if (filter === 'TOP') { const aVotes = (a.value.content.opinions_inhabitants || []).length; const bVotes = (b.value.content.opinions_inhabitants || []).length; return bVotes !== aVotes ? bVotes - aVotes : b.value.timestamp - a.value.timestamp; } return b.value.timestamp - a.value.timestamp; }); const title = i18n.opinionsTitle; const baseFilters = ['RECENT', 'ALL', 'MINE', 'TOP']; const cards = items .map(item => { const c = item.value.content; const key = item.key; const contentHtml = renderContentHtml(c, key); if (!contentHtml) return null; const voteEntries = Object.entries(c.opinions || {}); const total = voteEntries.reduce((sum, [, v]) => sum + v, 0); const voted = c.opinions_inhabitants?.includes(config.keys.id); const created = new Date(item.value.timestamp).toLocaleString(); const allCats = opinionCategories; return div( contentHtml, p({ class: 'card-footer' }, span({ class: 'date-link' }, `${created} ${i18n.performed} `), a({ href: `/author/${encodeURIComponent(item.value.author)}`, class: 'user-link' }, item.value.author) ), (() => { const entries = voteEntries.filter(([, v]) => v > 0); const dominantPart = (() => { if (!entries.length) return null; const maxVal = Math.max(...entries.map(([, v]) => v)); const dominant = entries.filter(([, v]) => v === maxVal).map(([k]) => i18n['vote' + k.charAt(0).toUpperCase() + k.slice(1)] || k); return [ span({ style: 'margin:0 8px;opacity:0.5;' }, '|'), span({ style: 'font-weight:700;' }, `${i18n.moreVoted || 'More Voted'}: ${dominant.join(' + ')}`) ]; })(); return h2( `${i18n.totalOpinions || i18n.opinionsTotalCount}: `, span({ style: 'font-weight:700;' }, String(total)), ...(dominantPart || []) ); })(), div({ class: 'voting-buttons' }, allCats.map(cat => { const label = `${i18n['vote' + cat.charAt(0).toUpperCase() + cat.slice(1)] || cat} [${c.opinions?.[cat] || 0}]`; if (voted) { return button({ class: 'vote-btn', type: 'button' }, label); } return form({ method: 'POST', action: `/opinions/${encodeURIComponent(key)}/${cat}` }, button({ class: 'vote-btn' }, label) ); }) ) ); }) .filter(Boolean); const hasDocuments = items.some(item => item.value.content?.type === 'document'); const header = div({ class: 'tags-header' }, h2(title), p(i18n.shareYourOpinions) ); const html = template( title, section( header, div({ class: 'mode-buttons' }, div({ class: 'column' }, baseFilters.map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, i18n[mode + 'Button'] || mode) ) ) ), div({ class: 'column' }, opinionCategories.positive.slice(0, 5).map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, i18n[mode + 'Button'] || mode) ) ) ), div({ class: 'column' }, opinionCategories.positive.slice(5, 10).map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, i18n[mode + 'Button'] || mode) ) ) ), div({ class: 'column' }, opinionCategories.positive.slice(10, 15).map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, i18n[mode + 'Button'] || mode) ) ) ) ), div({ class: 'mode-buttons' }, div({ class: 'column' }, opinionCategories.constructive.slice(0, 5).map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, i18n[mode + 'Button'] || mode) ) ) ), div({ class: 'column' }, opinionCategories.constructive.slice(5, 11).map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, i18n[mode + 'Button'] || mode) ) ) ), div({ class: 'column' }, opinionCategories.moderation.slice(0, 5).map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, i18n[mode + 'Button'] || mode) ) ) ), div({ class: 'column' }, opinionCategories.moderation.slice(5, 10).map(mode => form({ method: 'GET', action: '/opinions' }, input({ type: 'hidden', name: 'filter', value: mode }), button({ type: 'submit', class: filter === mode ? 'filter-btn active' : 'filter-btn' }, i18n[mode + 'Button'] || mode) ) ) ) ), section( cards.length ? div({ class: 'opinions-container' }, ...cards) : div({ class: 'no-results' }, p(i18n.noOpinionsFound)) ) ) ); return `${html}${hasDocuments ? ` ` : ''}`; };