document_view.js 9.1 KB

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