document_view.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. const { form, button, div, h2, p, section, input, label, br, a, 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 getFilteredDocuments = (filter, documents, userId) => {
  8. const now = Date.now();
  9. let filtered =
  10. filter === 'mine' ? documents.filter(d => d.author === userId) :
  11. filter === 'recent' ? documents.filter(d => new Date(d.createdAt).getTime() >= now - 86400000) :
  12. filter === 'top' ? [...documents].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. documents;
  18. if (filter !== 'top') {
  19. filtered = filtered.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
  20. }
  21. return filtered;
  22. };
  23. const renderDocumentActions = (filter, doc) => {
  24. return filter === 'mine' ? div({ class: "document-actions" },
  25. form({ method: "GET", action: `/documents/edit/${encodeURIComponent(doc.key)}` },
  26. button({ class: "update-btn", type: "submit" }, i18n.documentUpdateButton)
  27. ),
  28. form({ method: "POST", action: `/documents/delete/${encodeURIComponent(doc.key)}` },
  29. button({ class: "delete-btn", type: "submit" }, i18n.documentDeleteButton)
  30. )
  31. ) : null;
  32. };
  33. const renderDocumentList = (filteredDocs, filter) => {
  34. const seen = new Set();
  35. const unique = [];
  36. for (const doc of filteredDocs) {
  37. if (seen.has(doc.title)) continue;
  38. seen.add(doc.title);
  39. unique.push(doc);
  40. }
  41. return unique.length > 0
  42. ? unique.map(doc =>
  43. div({ class: "tags-header" },
  44. renderDocumentActions(filter, doc),
  45. form({ method: "GET", action: `/documents/${encodeURIComponent(doc.key)}` },
  46. button({ type: "submit", class: "filter-btn" }, i18n.viewDetails)
  47. ),
  48. doc.title?.trim() ? h2(doc.title) : null,
  49. div({
  50. id: `pdf-container-${doc.key}`,
  51. class: 'pdf-viewer-container',
  52. 'data-pdf-url': `/blob/${encodeURIComponent(doc.url)}`
  53. }),
  54. doc.description?.trim() ? p(...renderUrl(doc.description)) : null,
  55. doc.tags.length
  56. ? div({ class: "card-tags" }, doc.tags.map(tag =>
  57. a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link" }, `#${tag}`)
  58. ))
  59. : null,
  60. br(),
  61. p({ class: 'card-footer' },
  62. span({ class: 'date-link' }, `${moment(doc.createdAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed} `),
  63. a({ href: `/author/${encodeURIComponent(doc.author)}`, class: 'user-link' }, doc.author)
  64. ),
  65. div({ class: "voting-buttons" },
  66. ['interesting','necessary','funny','disgusting','sensible','propaganda','adultOnly','boring','confusing','inspiring','spam']
  67. .map(category =>
  68. form({ method: "POST", action: `/documents/opinions/${encodeURIComponent(doc.key)}/${category}` },
  69. button({ class: "vote-btn" },
  70. `${i18n[`vote${category.charAt(0).toUpperCase() + category.slice(1)}`]} [${doc.opinions?.[category] || 0}]`
  71. )
  72. )
  73. )
  74. )
  75. )
  76. )
  77. : div(i18n.noDocuments);
  78. };
  79. const renderDocumentForm = (filter, documentId, docToEdit) => {
  80. return div({ class: "div-center document-form" },
  81. form({
  82. action: filter === 'edit' ? `/documents/update/${encodeURIComponent(documentId)}` : "/documents/create",
  83. method: "POST", enctype: "multipart/form-data"
  84. },
  85. label(i18n.documentFileLabel), br(),
  86. input({ type: "file", name: "document", accept: "application/pdf", required: filter !== "edit" }), br(), br(),
  87. label(i18n.documentTagsLabel), br(),
  88. input({ type: "text", name: "tags", placeholder: i18n.documentTagsPlaceholder, value: docToEdit?.tags?.join(', ') || '' }), br(), br(),
  89. label(i18n.documentTitleLabel), br(),
  90. input({ type: "text", name: "title", placeholder: i18n.documentTitlePlaceholder, value: docToEdit?.title || '' }), br(), br(),
  91. label(i18n.documentDescriptionLabel), br(),
  92. textarea({name: "description", placeholder: i18n.documentDescriptionPlaceholder, rows:"4", value: docToEdit?.description || '' }), br(), br(),
  93. button({ type: "submit" }, filter === 'edit' ? i18n.documentUpdateButton : i18n.documentCreateButton)
  94. )
  95. );
  96. };
  97. exports.documentView = async (documents, filter, documentId) => {
  98. const title = filter === 'mine' ? i18n.documentMineSectionTitle :
  99. filter === 'create' ? i18n.documentCreateSectionTitle :
  100. filter === 'edit' ? i18n.documentUpdateSectionTitle :
  101. filter === 'recent' ? i18n.documentRecentSectionTitle :
  102. filter === 'top' ? i18n.documentTopSectionTitle :
  103. i18n.documentAllSectionTitle;
  104. const filteredDocs = getFilteredDocuments(filter, documents, userId);
  105. const docToEdit = documents.find(d => d.key === documentId);
  106. const isDocView = ['mine', 'create', 'edit', 'all', 'recent', 'top'].includes(filter);
  107. const tpl = template(
  108. title,
  109. section(
  110. div({ class: "tags-header" },
  111. h2(title),
  112. p(i18n.documentDescription)
  113. ),
  114. div({ class: "filters" },
  115. form({ method: "GET", action: "/documents" },
  116. button({ type: "submit", name: "filter", value: "all", class: filter === 'all' ? 'filter-btn active' : 'filter-btn' }, i18n.documentFilterAll),
  117. button({ type: "submit", name: "filter", value: "mine", class: filter === 'mine' ? 'filter-btn active' : 'filter-btn' }, i18n.documentFilterMine),
  118. button({ type: "submit", name: "filter", value: "recent", class: filter === 'recent' ? 'filter-btn active' : 'filter-btn' }, i18n.documentFilterRecent),
  119. button({ type: "submit", name: "filter", value: "top", class: filter === 'top' ? 'filter-btn active' : 'filter-btn' }, i18n.documentFilterTop),
  120. button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.documentCreateButton)
  121. )
  122. )
  123. ),
  124. section(
  125. (filter === 'create' || filter === 'edit')
  126. ? renderDocumentForm(filter, documentId, docToEdit)
  127. : renderDocumentList(filteredDocs, filter)
  128. )
  129. );
  130. return `${tpl}
  131. ${isDocView
  132. ? `<script type="module" src="/js/pdf.min.mjs"></script>
  133. <script src="/js/pdf-viewer.js"></script>`
  134. : ''}`;
  135. };
  136. exports.singleDocumentView = async (doc, filter) => {
  137. const isAuthor = doc.author === userId;
  138. const hasOpinions = Object.keys(doc.opinions || {}).length > 0;
  139. const tpl = template(
  140. i18n.documentTitle,
  141. section(
  142. div({ class: "filters" },
  143. form({ method: "GET", action: "/documents" },
  144. button({ type: "submit", name: "filter", value: "all", class: filter === 'all' ? 'filter-btn active' : 'filter-btn' }, i18n.documentFilterAll),
  145. button({ type: "submit", name: "filter", value: "mine", class: filter === 'mine' ? 'filter-btn active' : 'filter-btn' }, i18n.documentFilterMine),
  146. button({ type: "submit", name: "filter", value: "recent", class: filter === 'recent' ? 'filter-btn active' : 'filter-btn' }, i18n.documentFilterRecent),
  147. button({ type: "submit", name: "filter", value: "top", class: filter === 'top' ? 'filter-btn active' : 'filter-btn' }, i18n.documentFilterTop),
  148. button({ type: "submit", name: "filter", value: "create", class: "create-button" }, i18n.documentCreateButton)
  149. )
  150. ),
  151. div({ class: "tags-header" },
  152. isAuthor ? div({ class: "document-actions" },
  153. !hasOpinions
  154. ? form({ method: "GET", action: `/documents/edit/${encodeURIComponent(doc.key)}` },
  155. button({ class: "update-btn", type: "submit" }, i18n.documentUpdateButton)
  156. )
  157. : null,
  158. form({ method: "POST", action: `/documents/delete/${encodeURIComponent(doc.key)}` },
  159. button({ class: "delete-btn", type: "submit" }, i18n.documentDeleteButton)
  160. )
  161. ) : null,
  162. h2(doc.title),
  163. div({
  164. id: `pdf-container-${doc.key}`,
  165. class: 'pdf-viewer-container',
  166. 'data-pdf-url': `/blob/${encodeURIComponent(doc.url)}`
  167. }),
  168. p(...renderUrl(doc.description)),
  169. doc.tags.length
  170. ? div({ class: "card-tags" }, doc.tags.map(tag =>
  171. a({ href: `/search?query=%23${encodeURIComponent(tag)}`, class: "tag-link" }, `#${tag}`)
  172. ))
  173. : null,
  174. br,
  175. p({ class: 'card-footer' },
  176. span({ class: 'date-link' }, `${moment(doc.createdAt).format('YYYY/MM/DD HH:mm:ss')} ${i18n.performed} `),
  177. a({ href: `/author/${encodeURIComponent(doc.author)}`, class: 'user-link' }, `${doc.author}`)
  178. ),
  179. ),
  180. div({ class: "voting-buttons" },
  181. ['interesting', 'necessary', 'funny', 'disgusting', 'sensible', 'propaganda', 'adultOnly', 'boring', 'confusing', 'inspiring', 'spam'].map(category =>
  182. form({ method: "POST", action: `/documents/opinions/${encodeURIComponent(doc.key)}/${category}` },
  183. button({ class: "vote-btn" }, `${i18n[`vote${category.charAt(0).toUpperCase() + category.slice(1)}`]} [${doc.opinions?.[category] || 0}]`)
  184. )
  185. )
  186. )
  187. )
  188. );
  189. return `${tpl}
  190. ${filter === 'mine' || filter === 'edit' || filter === 'top' || filter === 'recent' || filter === 'all'
  191. ? `<script type="module" src="/js/pdf.min.mjs"></script>
  192. <script src="/js/pdf-viewer.js"></script>`
  193. : ''}`;
  194. };