News.vue 2.5 KB

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