News.vue 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121
  1. <script setup lang="ts">
  2. import { ref, onMounted } from "vue";
  3. import { formatDistance } from "date-fns";
  4. import { marked } from "marked";
  5. import DOMPurify from "dompurify";
  6. import { useWebsocketsStore } from "@/stores/websockets";
  7. import ws from "@/ws";
  8. const { socket } = useWebsocketsStore();
  9. const news = ref([]);
  10. const init = () => {
  11. socket.dispatch("news.getPublished", res => {
  12. if (res.status === "success") news.value = res.data.news;
  13. });
  14. socket.dispatch("apis.joinRoom", "news");
  15. };
  16. const { sanitize } = DOMPurify;
  17. onMounted(() => {
  18. marked.use({
  19. renderer: {
  20. table(header, body) {
  21. return `<table class="table">
  22. <thead>${header}</thead>
  23. <tbody>${body}</tbody>
  24. </table>`;
  25. }
  26. }
  27. });
  28. socket.on("event:news.created", res => news.value.unshift(res.data.news));
  29. socket.on("event:news.updated", res => {
  30. if (res.data.news.status === "draft") {
  31. news.value = news.value.filter(
  32. item => item._id !== res.data.news._id
  33. );
  34. return;
  35. }
  36. for (let n = 0; n < news.value.length; n += 1) {
  37. if (news.value[n]._id === res.data.news._id)
  38. news.value[n] = {
  39. ...news.value[n],
  40. ...res.data.news
  41. };
  42. }
  43. });
  44. socket.on("event:news.deleted", res => {
  45. news.value = news.value.filter(item => item._id !== res.data.newsId);
  46. });
  47. ws.onConnect(init);
  48. });
  49. </script>
  50. <template>
  51. <div class="app">
  52. <page-metadata title="News" />
  53. <main-header />
  54. <div class="container">
  55. <div class="content-wrapper">
  56. <h1 class="has-text-centered page-title">News</h1>
  57. <div
  58. v-for="item in news"
  59. :key="item._id"
  60. class="section news-item"
  61. >
  62. <div v-html="sanitize(marked(item.markdown))"></div>
  63. <div class="info">
  64. <hr />
  65. By
  66. <user-link
  67. :user-id="item.createdBy"
  68. :alt="item.createdBy"
  69. />&nbsp;<span :title="new Date(item.createdAt)">
  70. {{
  71. formatDistance(item.createdAt, new Date(), {
  72. addSuffix: true
  73. })
  74. }}
  75. </span>
  76. </div>
  77. </div>
  78. <h3 v-if="news.length === 0" class="has-text-centered">
  79. No news items were found.
  80. </h3>
  81. </div>
  82. </div>
  83. <main-footer />
  84. </div>
  85. </template>
  86. <style lang="less" scoped>
  87. .night-mode {
  88. p {
  89. color: var(--light-grey-2);
  90. }
  91. }
  92. .container {
  93. width: calc(100% - 32px);
  94. }
  95. .section {
  96. border: 1px solid var(--light-grey-3);
  97. max-width: 100%;
  98. margin-top: 50px;
  99. &:last-of-type {
  100. margin-bottom: 50px;
  101. }
  102. }
  103. </style>