feed_view.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. const { div, h2, p, section, button, form, a, span, textarea, br, input, h1 } = require("../server/node_modules/hyperaxe");
  2. const { template, i18n } = require('./main_views');
  3. const { config } = require('../server/SSB_server.js');
  4. const { renderTextWithStyles } = require('../backend/renderTextWithStyles');
  5. const generateFilterButtons = (filters, currentFilter, action) => {
  6. return filters.map(mode =>
  7. form({ method: 'GET', action },
  8. input({ type: 'hidden', name: 'filter', value: mode }),
  9. button({ type: 'submit', class: currentFilter === mode.toLowerCase() ? 'filter-btn active' : 'filter-btn' }, i18n[mode + 'Button'] || mode)
  10. )
  11. );
  12. };
  13. const renderFeedCard = (feed, alreadyRefeeded, alreadyVoted) => {
  14. const content = feed.value.content;
  15. const totalVotes = Object.entries(content.opinions || {});
  16. const totalCount = totalVotes.reduce((sum, [, count]) => sum + count, 0);
  17. const createdAt = feed.value.timestamp ? new Date(feed.value.timestamp).toLocaleString() : '';
  18. return div({ class: 'feed-card' },
  19. div({ class: 'feed-row' },
  20. div({ class: 'refeed-column' },
  21. h1(`${content.refeeds || 0}`),
  22. !alreadyRefeeded
  23. ? form({ method: 'POST', action: `/feed/refeed/${encodeURIComponent(feed.key)}` },
  24. button({ class: 'refeed-btn' }, i18n.refeedButton)
  25. )
  26. : p(i18n.alreadyRefeeded)
  27. ),
  28. div({ class: 'feed-main' },
  29. div({ class: 'feed-text', innerHTML: renderTextWithStyles(content.text) }),
  30. h2(`${i18n.totalOpinions}: ${totalCount}`),
  31. p({ class: 'card-footer' },
  32. span({ class: 'date-link' }, `${createdAt} ${i18n.performed} `),
  33. a({ href: `/author/${encodeURIComponent(feed.value.author)}`, class: 'user-link' }, `${feed.value.author}`)
  34. )
  35. )
  36. ),
  37. div({ class: 'votes-wrapper' },
  38. totalVotes.length > 0
  39. ? div({ class: 'votes' },
  40. totalVotes.map(([category, count]) =>
  41. span({ class: 'vote-category' }, `${category}: ${count}`)
  42. )
  43. )
  44. : null,
  45. !alreadyVoted
  46. ? div({ class: 'voting-buttons' },
  47. ['interesting','necessary','funny','disgusting','sensible','propaganda','adultOnly','boring','confusing','inspiring','spam'].map(cat =>
  48. form({ method: 'POST', action: `/feed/opinions/${encodeURIComponent(feed.key)}/${cat}` },
  49. button({ class: 'vote-btn' }, `${i18n['vote'+cat.charAt(0).toUpperCase()+cat.slice(1)] || cat} [${content.opinions?.[cat]||0}]`)
  50. )
  51. )
  52. )
  53. : p(i18n.alreadyVoted)
  54. )
  55. );
  56. };
  57. exports.feedView = (feeds, filter) => {
  58. const title =
  59. filter === 'MINE' ? i18n.MINEButton :
  60. filter === 'TODAY' ? i18n.TODAYButton :
  61. filter === 'TOP' ? i18n.TOPButton :
  62. filter === 'CREATE' ? i18n.createFeedTitle :
  63. filter === 'tag' ? i18n.filteredByTag :
  64. i18n.feedTitle;
  65. if (filter !== 'TOP') {
  66. feeds = feeds.sort((a, b) => b.value.timestamp - a.value.timestamp);
  67. } else {
  68. feeds = feeds.sort((a, b) => {
  69. const aRefeeds = a.value.content.refeeds || 0;
  70. const bRefeeds = b.value.content.refeeds || 0;
  71. return bRefeeds - aRefeeds;
  72. });
  73. }
  74. const header = div({ class: 'tags-header' },
  75. h2(title),
  76. p(i18n.FeedshareYourOpinions)
  77. );
  78. return template(
  79. title,
  80. section(
  81. header,
  82. div({ class: 'mode-buttons-row' },
  83. generateFilterButtons(['ALL', 'MINE', 'TODAY', 'TOP'], filter, '/feed'),
  84. form({ method: 'GET', action: '/feed/create' },
  85. button({
  86. type: 'submit',
  87. class: 'create-button filter-btn'
  88. }, i18n.createFeedTitle || "Create Feed")
  89. )
  90. ),
  91. section(
  92. filter === 'CREATE'
  93. ? form({ method: 'POST', action: '/feed/create' },
  94. textarea({
  95. name: 'text',
  96. placeholder: i18n.feedPlaceholder,
  97. maxlength: 280,
  98. rows: 4,
  99. cols: 50
  100. }),
  101. br(),
  102. button({ type: 'submit' }, i18n.createFeedButton)
  103. )
  104. : feeds && feeds.length > 0
  105. ? div({ class: 'feed-container' },
  106. feeds.map(feed => {
  107. const content = feed.value.content;
  108. const alreadyRefeeded = content.refeeds_inhabitants?.includes(config.keys.id);
  109. const alreadyVoted = content.opinions_inhabitants?.includes(config.keys.id);
  110. return renderFeedCard(feed, alreadyRefeeded, alreadyVoted);
  111. })
  112. )
  113. : div({ class: 'no-results' }, p(i18n.noFeedsFound))
  114. )
  115. )
  116. );
  117. };
  118. exports.feedCreateView = () => {
  119. return template(
  120. i18n.createFeedTitle,
  121. section(
  122. div({ class: 'tags-header' },
  123. h2(i18n.createFeedTitle),
  124. p(i18n.FeedshareYourOpinions)
  125. ),
  126. div({ class: 'mode-buttons', style: 'display:flex; gap:8px; margin-bottom:24px;' },
  127. generateFilterButtons(['ALL', 'MINE', 'TODAY', 'TOP'], 'CREATE', '/feed')
  128. ),
  129. form({ method: 'POST', action: '/feed/create' },
  130. textarea({
  131. name: 'text',
  132. maxlength: '280',
  133. rows: 5,
  134. cols: 50,
  135. placeholder: i18n.feedPlaceholder
  136. }),
  137. br(),
  138. button({ type: 'submit', class: 'create-button' }, i18n.createFeedButton || 'Send Feed!')
  139. )
  140. )
  141. );
  142. };