video_view.js 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. const { form, button, div, h2, p, section, input, label, br, a, video: videoHyperaxe, span, textarea } = require("../server/node_modules/hyperaxe");
  2. const moment = require("../server/node_modules/moment");
  3. const { template, i18n } = require('./main_views');
  4. const { config } = require('../server/SSB_server.js');
  5. const { renderUrl } = require('../backend/renderUrl');
  6. const userId = config.keys.id;
  7. const getFilteredVideos = (filter, videos, userId) => {
  8. const now = Date.now();
  9. let filtered =
  10. filter === 'mine' ? videos.filter(v => v.author === userId) :
  11. filter === 'recent' ? videos.filter(v => new Date(v.createdAt).getTime() >= now - 86400000) :
  12. filter === 'top' ? [...videos].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. videos;
  18. return filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
  19. };
  20. const renderVideoActions = (filter, video) => {
  21. return filter === 'mine' ? div({ class: "video-actions" },
  22. form({ method: "GET", action: `/videos/edit/${encodeURIComponent(video.key)}` },
  23. button({ class: "update-btn", type: "submit" }, i18n.videoUpdateButton)
  24. ),
  25. form({ method: "POST", action: `/videos/delete/${encodeURIComponent(video.key)}` },
  26. button({ class: "delete-btn", type: "submit" }, i18n.videoDeleteButton)
  27. )
  28. ) : null;
  29. };
  30. const renderVideoList = (filteredVideos, filter) => {
  31. return filteredVideos.length > 0
  32. ? filteredVideos.map(video =>
  33. div({ class: "tags-header" },
  34. renderVideoActions(filter, video),
  35. form({ method: "GET", action: `/videos/${encodeURIComponent(video.key)}` },
  36. button({ type: "submit", class: "filter-btn" }, i18n.viewDetails)),
  37. video.title?.trim() ? h2(video.title) : null,
  38. video.url
  39. ? div({ class: "video-container" },
  40. videoHyperaxe({
  41. controls: true,
  42. src: `/blob/${encodeURIComponent(video.url)}`,
  43. type: video.mimeType,
  44. preload: 'metadata',
  45. width: '640',
  46. height: '360'
  47. })
  48. )
  49. : p(i18n.videoNoFile),
  50. video.description?.trim() ? p(...renderUrl(video.description)) : null,
  51. video.tags?.length
  52. ? div({ class: "card-tags" },
  53. video.tags.map(tag =>
  54. a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link", style: "margin-right: 0.8em; margin-bottom: 0.5em;" }, `#${tag}`)
  55. )
  56. )
  57. : null,
  58. br,
  59. p({ class: 'card-footer' },
  60. span({ class: 'date-link' }, `${moment(video.createdAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed} `),
  61. a({ href: `/author/${encodeURIComponent(video.author)}`, class: 'user-link' }, `${video.author}`)
  62. ),
  63. div({ class: "voting-buttons" },
  64. ['interesting','necessary','funny','disgusting','sensible',
  65. 'propaganda','adultOnly','boring','confusing','inspiring','spam']
  66. .map(category =>
  67. form({ method: "POST", action: `/videos/opinions/${encodeURIComponent(video.key)}/${category}` },
  68. button({ class: "vote-btn" },
  69. `${i18n[`vote${category.charAt(0).toUpperCase() + category.slice(1)}`]} [${video.opinions?.[category] || 0}]`
  70. )
  71. )
  72. )
  73. )
  74. )
  75. )
  76. : div(i18n.noVideos);
  77. };
  78. const renderVideoForm = (filter, videoId, videoToEdit) => {
  79. return div({ class: "div-center video-form" },
  80. form({
  81. action: filter === 'edit' ? `/videos/update/${encodeURIComponent(videoId)}` : "/videos/create",
  82. method: "POST", enctype: "multipart/form-data"
  83. },
  84. label(i18n.videoFileLabel), br(),
  85. input({ type: "file", name: "video", required: filter !== "edit" }), br(), br(),
  86. label(i18n.videoTagsLabel), br(),
  87. input({ type: "text", name: "tags", placeholder: i18n.videoTagsPlaceholder, value: videoToEdit?.tags?.join(', ') || '' }), br(), br(),
  88. label(i18n.videoTitleLabel), br(),
  89. input({ type: "text", name: "title", placeholder: i18n.videoTitlePlaceholder, value: videoToEdit?.title || '' }), br(), br(),
  90. label(i18n.videoDescriptionLabel), br(),
  91. textarea({name: "description", placeholder: i18n.videoDescriptionPlaceholder, rows:"4", value: videoToEdit?.description || '' }), br(), br(),
  92. button({ type: "submit" }, filter === 'edit' ? i18n.videoUpdateButton : i18n.videoCreateButton)
  93. )
  94. );
  95. };
  96. exports.videoView = async (videos, filter, videoId) => {
  97. const title = filter === 'mine' ? i18n.videoMineSectionTitle :
  98. filter === 'create' ? i18n.videoCreateSectionTitle :
  99. filter === 'edit' ? i18n.videoUpdateSectionTitle :
  100. filter === 'recent' ? i18n.videoRecentSectionTitle :
  101. filter === 'top' ? i18n.videoTopSectionTitle :
  102. i18n.videoAllSectionTitle;
  103. const filteredVideos = getFilteredVideos(filter, videos, userId);
  104. const videoToEdit = videos.find(v => v.key === videoId);
  105. return template(
  106. title,
  107. section(
  108. div({ class: "tags-header" },
  109. h2(title),
  110. p(i18n.videoDescription)
  111. ),
  112. div({ class: "filters" },
  113. form({ method: "GET", action: "/videos" },
  114. ["all", "mine", "recent", "top"].map(f =>
  115. button({
  116. type: "submit", name: "filter", value: f,
  117. class: filter === f ? "filter-btn active" : "filter-btn"
  118. },
  119. i18n[`videoFilter${f.charAt(0).toUpperCase() + f.slice(1)}`]
  120. )
  121. ),
  122. button({ type: "submit", name: "filter", value: "create", class: "create-button" },
  123. i18n.videoCreateButton)
  124. )
  125. )
  126. ),
  127. section(
  128. (filter === 'create' || filter === 'edit')
  129. ? renderVideoForm(filter, videoId, videoToEdit)
  130. : renderVideoList(filteredVideos, filter)
  131. )
  132. );
  133. };
  134. exports.singleVideoView = async (video, filter) => {
  135. const isAuthor = video.author === userId;
  136. const hasOpinions = Object.keys(video.opinions || {}).length > 0;
  137. return template(
  138. i18n.videoTitle,
  139. section(
  140. div({ class: "filters" },
  141. form({ method: "GET", action: "/videos" },
  142. button({ type: "submit", name: "filter", value: "all", class: filter === 'all' ? 'filter-btn active' : 'filter-btn' }, i18n.videoFilterAll),
  143. button({ type: "submit", name: "filter", value: "mine", class: filter === 'mine' ? 'filter-btn active' : 'filter-btn' }, i18n.videoFilterMine),
  144. button({ type: "submit", name: "filter", value: "recent", class: filter === 'recent' ? 'filter-btn active' : 'filter-btn' }, i18n.videoFilterRecent),
  145. button({ type: "submit", name: "filter", value: "top", class: filter === 'top' ? 'filter-btn active' : 'filter-btn' }, i18n.videoFilterTop),
  146. button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.videoCreateButton)
  147. )
  148. ),
  149. div({ class: "tags-header" },
  150. isAuthor ? div({ class: "video-actions" },
  151. !hasOpinions
  152. ? form({ method: "GET", action: `/videos/edit/${encodeURIComponent(video.key)}` },
  153. button({ class: "update-btn", type: "submit" }, i18n.videoUpdateButton)
  154. )
  155. : null,
  156. form({ method: "POST", action: `/videos/delete/${encodeURIComponent(video.key)}` },
  157. button({ class: "delete-btn", type: "submit" }, i18n.videoDeleteButton)
  158. )
  159. ) : null,
  160. h2(video.title),
  161. video.url
  162. ? div({ class: "video-container" },
  163. videoHyperaxe({
  164. controls: true,
  165. src: `/blob/${encodeURIComponent(video.url)}`,
  166. type: video.mimeType,
  167. preload: 'metadata',
  168. width: '640',
  169. height: '360'
  170. })
  171. )
  172. : p(i18n.videoNoFile),
  173. p(...renderUrl(video.description)),
  174. video.tags?.length
  175. ? div({ class: "card-tags" },
  176. video.tags.map(tag =>
  177. a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link", style: "margin-right: 0.8em; margin-bottom: 0.5em;" }, `#${tag}`)
  178. )
  179. )
  180. : null,
  181. br,
  182. p({ class: 'card-footer' },
  183. span({ class: 'date-link' }, `${moment(video.createdAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed} `),
  184. a({ href: `/author/${encodeURIComponent(video.author)}`, class: 'user-link' }, `${video.author}`)
  185. ),
  186. ),
  187. div({ class: "voting-buttons" },
  188. ['interesting', 'necessary', 'funny', 'disgusting', 'sensible', 'propaganda', 'adultOnly', 'boring', 'confusing', 'inspiring', 'spam'].map(category =>
  189. form({ method: "POST", action: `/videos/opinions/${encodeURIComponent(video.key)}/${category}` },
  190. button({ class: "vote-btn" }, `${i18n[`vote${category.charAt(0).toUpperCase() + category.slice(1)}`]} [${video.opinions?.[category] || 0}]`)
  191. )
  192. )
  193. )
  194. )
  195. );
  196. };