audio_view.js 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. const { form, button, div, h2, p, section, input, label, br, a, audio: audioHyperaxe, span, textarea } = require("../server/node_modules/hyperaxe");
  2. const { template, i18n } = require('./main_views');
  3. const moment = require("../server/node_modules/moment");
  4. const { config } = require('../server/SSB_server.js');
  5. const { renderUrl } = require('../backend/renderUrl');
  6. const userId = config.keys.id
  7. const getFilteredAudios = (filter, audios, userId) => {
  8. const now = Date.now();
  9. let filtered =
  10. filter === 'mine' ? audios.filter(a => a.author === userId) :
  11. filter === 'recent' ? audios.filter(a => new Date(a.createdAt).getTime() >= now - 86400000) :
  12. filter === 'top' ? [...audios].sort((a, b) => {
  13. const sumA = Object.values(a.opinions || {}).reduce((s, n) => s + (n || 0), 0);
  14. const sumB = Object.values(b.opinions || {}).reduce((s, n) => s + (n || 0), 0);
  15. return sumB - sumA;
  16. }) :
  17. audios;
  18. return filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
  19. };
  20. const renderCardField = (label, value) =>
  21. div({ class: "card-field" },
  22. span({ class: "card-label" }, label),
  23. span({ class: "card-value" }, value)
  24. );
  25. const renderAudioActions = (filter, audio) => {
  26. return filter === 'mine' ? div({ class: "audio-actions" },
  27. form({ method: "GET", action: `/audios/edit/${encodeURIComponent(audio.key)}` },
  28. button({ class: "update-btn", type: "submit" }, i18n.audioUpdateButton)
  29. ),
  30. form({ method: "POST", action: `/audios/delete/${encodeURIComponent(audio.key)}` },
  31. button({ class: "delete-btn", type: "submit" }, i18n.audioDeleteButton)
  32. )
  33. ) : null;
  34. };
  35. const renderAudioList = (filteredAudios, filter) => {
  36. return filteredAudios.length > 0
  37. ? filteredAudios.map(audio =>
  38. div({ class: "audio-item card" },
  39. br,
  40. renderAudioActions(filter, audio),
  41. form({ method: "GET", action: `/audios/${encodeURIComponent(audio.key)}` },
  42. button({ type: "submit", class: "filter-btn" }, i18n.viewDetails)),
  43. audio.title?.trim() ? h2(audio.title) : null,
  44. audio.url
  45. ? div({ class: "audio-container" },
  46. audioHyperaxe({
  47. controls: true,
  48. src: `/blob/${encodeURIComponent(audio.url)}`,
  49. type: audio.mimeType,
  50. preload: 'metadata'
  51. })
  52. )
  53. : p(i18n.audioNoFile),
  54. p(...renderUrl(audio.description)),
  55. audio.tags?.length
  56. ? div({ class: "card-tags" },
  57. audio.tags.map(tag =>
  58. a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link" }, `#${tag}`)
  59. )
  60. )
  61. : null,
  62. br,
  63. p({ class: 'card-footer' },
  64. span({ class: 'date-link' }, `${moment(audio.createdAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed} `),
  65. a({ href: `/author/${encodeURIComponent(audio.author)}`, class: 'user-link' }, `${audio.author}`)
  66. ),
  67. div({ class: "voting-buttons" },
  68. ['interesting','necessary','funny','disgusting','sensible',
  69. 'propaganda','adultOnly','boring','confusing','inspiring','spam']
  70. .map(category =>
  71. form({ method: "POST", action: `/audios/opinions/${encodeURIComponent(audio.key)}/${category}` },
  72. button({ class: "vote-btn" },
  73. `${i18n[`vote${category.charAt(0).toUpperCase() + category.slice(1)}`]} [${audio.opinions?.[category] || 0}]`
  74. )
  75. )
  76. )
  77. ),
  78. )
  79. )
  80. : div(i18n.noAudios);
  81. };
  82. const renderAudioForm = (filter, audioId, audioToEdit) => {
  83. return div({ class: "div-center audio-form" },
  84. form({
  85. action: filter === 'edit' ? `/audios/update/${encodeURIComponent(audioId)}` : "/audios/create",
  86. method: "POST", enctype: "multipart/form-data"
  87. },
  88. label(i18n.audioFileLabel), br(),
  89. input({ type: "file", name: "audio", required: filter !== "edit" }), br(), br(),
  90. label(i18n.audioTagsLabel), br(),
  91. input({ type: "text", name: "tags", placeholder: i18n.audioTagsPlaceholder, value: audioToEdit?.tags?.join(', ') || '' }), br(), br(),
  92. label(i18n.audioTitleLabel), br(),
  93. input({ type: "text", name: "title", placeholder: i18n.audioTitlePlaceholder, value: audioToEdit?.title || '' }), br(), br(),
  94. label(i18n.audioDescriptionLabel), br(),
  95. textarea({name: "description", placeholder: i18n.audioDescriptionPlaceholder, rows:"4", value: audioToEdit?.description || '' }), br(), br(),
  96. button({ type: "submit" }, filter === 'edit' ? i18n.audioUpdateButton : i18n.audioCreateButton)
  97. )
  98. );
  99. };
  100. exports.audioView = async (audios, filter, audioId) => {
  101. const title = filter === 'mine' ? i18n.audioMineSectionTitle :
  102. filter === 'create' ? i18n.audioCreateSectionTitle :
  103. filter === 'edit' ? i18n.audioUpdateSectionTitle :
  104. filter === 'recent' ? i18n.audioRecentSectionTitle :
  105. filter === 'top' ? i18n.audioTopSectionTitle :
  106. i18n.audioAllSectionTitle;
  107. const filteredAudios = getFilteredAudios(filter, audios, userId);
  108. const audioToEdit = audios.find(a => a.key === audioId);
  109. return template(
  110. title,
  111. section(
  112. div({ class: "tags-header" },
  113. h2(title),
  114. p(i18n.audioDescription)
  115. ),
  116. div({ class: "filters" },
  117. form({ method: "GET", action: "/audios" },
  118. ["all", "mine", "recent", "top"].map(f =>
  119. button({
  120. type: "submit", name: "filter", value: f,
  121. class: filter === f ? "filter-btn active" : "filter-btn"
  122. },
  123. i18n[`audioFilter${f.charAt(0).toUpperCase() + f.slice(1)}`]
  124. )
  125. ),
  126. button({ type: "submit", name: "filter", value: "create", class: "create-button" },
  127. i18n.audioCreateButton)
  128. )
  129. )
  130. ),
  131. section(
  132. (filter === 'create' || filter === 'edit')
  133. ? renderAudioForm(filter, audioId, audioToEdit)
  134. : renderAudioList(filteredAudios, filter)
  135. )
  136. );
  137. };
  138. exports.singleAudioView = async (audio, filter) => {
  139. const isAuthor = audio.author === userId;
  140. const hasOpinions = Object.keys(audio.opinions || {}).length > 0;
  141. return template(
  142. i18n.audioTitle,
  143. section(
  144. div({ class: "filters" },
  145. form({ method: "GET", action: "/audios" },
  146. button({ type: "submit", name: "filter", value: "all", class: filter === 'all' ? 'filter-btn active' : 'filter-btn' }, i18n.audioFilterAll),
  147. button({ type: "submit", name: "filter", value: "mine", class: filter === 'mine' ? 'filter-btn active' : 'filter-btn' }, i18n.audioFilterMine),
  148. button({ type: "submit", name: "filter", value: "recent", class: filter === 'recent' ? 'filter-btn active' : 'filter-btn' }, i18n.audioFilterRecent),
  149. button({ type: "submit", name: "filter", value: "top", class: filter === 'top' ? 'filter-btn active' : 'filter-btn' }, i18n.audioFilterTop),
  150. button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.audioCreateButton)
  151. )
  152. ),
  153. div({ class: "tags-header" },
  154. isAuthor ? div({ class: "audio-actions" },
  155. !hasOpinions
  156. ? form({ method: "GET", action: `/audios/edit/${encodeURIComponent(audio.key)}` },
  157. button({ class: "update-btn", type: "submit" }, i18n.audioUpdateButton)
  158. )
  159. : null,
  160. form({ method: "POST", action: `/audios/delete/${encodeURIComponent(audio.key)}` },
  161. button({ class: "delete-btn", type: "submit" }, i18n.audioDeleteButton)
  162. )
  163. ) : null,
  164. form({ method: "GET", action: `/audios/${encodeURIComponent(audio.key)}` },
  165. button({ type: "submit", class: "filter-btn" }, i18n.viewDetails)),
  166. h2(audio.title),
  167. audio.url
  168. ? div({ class: "audio-container" },
  169. audioHyperaxe({
  170. controls: true,
  171. src: `/blob/${encodeURIComponent(audio.url)}`,
  172. type: audio.mimeType,
  173. preload: 'metadata'
  174. })
  175. )
  176. : p(i18n.audioNoFile),
  177. p(...renderUrl(audio.description)),
  178. audio.tags?.length
  179. ? div({ class: "card-tags" },
  180. audio.tags.map(tag =>
  181. a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link", style: "margin-right: 0.8em; margin-bottom: 0.5em;" }, `#${tag}`)
  182. )
  183. )
  184. : null,
  185. br,
  186. p({ class: 'card-footer' },
  187. span({ class: 'date-link' }, `${moment(audio.createdAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed} `),
  188. a({ href: `/author/${encodeURIComponent(audio.author)}`, class: 'user-link' }, `${audio.author}`)
  189. ),
  190. ),
  191. div({ class: "voting-buttons" },
  192. ['interesting', 'necessary', 'funny', 'disgusting', 'sensible', 'propaganda', 'adultOnly', 'boring', 'confusing', 'inspiring', 'spam'].map(category =>
  193. form({ method: "POST", action: `/audios/opinions/${encodeURIComponent(audio.key)}/${category}` },
  194. button({ class: "vote-btn" }, `${i18n[`vote${category.charAt(0).toUpperCase() + category.slice(1)}`]} [${audio.opinions?.[category] || 0}]`)
  195. )
  196. )
  197. )
  198. )
  199. );
  200. };