News.vue 2.4 KB

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