| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391 |
- 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
- ? `<script type="module" src="/js/pdf.min.mjs"></script>
- <script src="/js/pdf-viewer.js"></script>`
- : ''}`;
- };
|