WhatIsNew.vue 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. <script setup lang="ts">
  2. import { defineAsyncComponent, onMounted, ref } from "vue";
  3. import { formatDistance } from "date-fns";
  4. import { marked } from "marked";
  5. import dompurify from "dompurify";
  6. import { useModels } from "@/composables/useModels";
  7. import { useModalsStore } from "@/stores/modals";
  8. import { useWebsocketStore } from "@/stores/websocket";
  9. const Modal = defineAsyncComponent(() => import("@/components/Modal.vue"));
  10. defineProps({
  11. modalUuid: { type: String, required: true }
  12. });
  13. const { runJob } = useWebsocketStore();
  14. const { registerModel, onDeleted } = useModels();
  15. const { closeCurrentModal } = useModalsStore();
  16. const news = ref();
  17. onMounted(async () => {
  18. let firstVisited = localStorage.getItem("firstVisited");
  19. const newUser = !firstVisited;
  20. const [model] = await runJob("data.news.newest", {
  21. showToNewUsers: newUser,
  22. limit: 1
  23. });
  24. if (model && newUser) {
  25. firstVisited = Date.now().toString();
  26. localStorage.setItem("firstVisited", firstVisited);
  27. } else if (
  28. !model ||
  29. (localStorage.getItem("whatIsNew") &&
  30. parseInt(localStorage.getItem("whatIsNew") as string) >=
  31. Date.parse(model.createdAt)) ||
  32. parseInt(firstVisited as string) >= model.createdAt
  33. ) {
  34. closeCurrentModal(true);
  35. return;
  36. }
  37. localStorage.setItem("whatIsNew", Date.parse(model.createdAt).toString());
  38. const _model = await registerModel(model, { news: "createdBy" });
  39. news.value = _model;
  40. await onDeleted("news", ({ oldDoc }) => {
  41. if (oldDoc._id === news.value?._id) closeCurrentModal(true);
  42. });
  43. marked.use({
  44. renderer: {
  45. table(header, body) {
  46. return `<table class="table">
  47. <thead>${header}</thead>
  48. <tbody>${body}</tbody>
  49. </table>`;
  50. }
  51. }
  52. });
  53. });
  54. const { sanitize } = dompurify;
  55. </script>
  56. <template>
  57. <modal v-if="news" title="News" class="what-is-news-modal">
  58. <template #body>
  59. <div
  60. class="section news-item"
  61. v-html="sanitize(marked(news.markdown))"
  62. ></div>
  63. </template>
  64. <template #footer>
  65. <span v-if="news.createdBy">
  66. By&nbsp;
  67. <router-link
  68. :to="{ path: `/u/${news.createdBy.username}` }"
  69. :title="news.createdBy._id"
  70. >
  71. {{ news.createdBy.name }}
  72. </router-link> </span
  73. >&nbsp;
  74. <span :title="new Date(news.createdAt).toString()">
  75. {{
  76. formatDistance(new Date(news.createdAt), new Date(), {
  77. addSuffix: true
  78. })
  79. }}
  80. </span>
  81. </template>
  82. </modal>
  83. </template>
  84. <style lang="less">
  85. .what-is-news-modal .modal-card .modal-card-foot {
  86. column-gap: 0;
  87. }
  88. </style>
  89. <style lang="less" scoped>
  90. .night-mode {
  91. .modal-card,
  92. .modal-card-head,
  93. .modal-card-body {
  94. background-color: var(--dark-grey-3);
  95. }
  96. strong,
  97. p {
  98. color: var(--light-grey-2);
  99. }
  100. .section {
  101. background-color: transparent !important;
  102. }
  103. }
  104. .modal-card-head {
  105. border-bottom: none;
  106. background-color: ghostwhite;
  107. padding: 15px;
  108. }
  109. .modal-card-title {
  110. font-size: 14px;
  111. }
  112. .news-item {
  113. box-shadow: unset !important;
  114. }
  115. .delete {
  116. background: transparent;
  117. &:hover {
  118. background: transparent;
  119. }
  120. &:before,
  121. &:after {
  122. background-color: var(--light-grey-3);
  123. }
  124. }
  125. .sect {
  126. div[class^="sect-head"],
  127. div[class*=" sect-head"] {
  128. padding: 12px;
  129. text-transform: uppercase;
  130. font-weight: bold;
  131. color: var(--white);
  132. }
  133. .sect-head-features {
  134. background-color: dodgerblue;
  135. }
  136. .sect-head-improvements {
  137. background-color: seagreen;
  138. }
  139. .sect-head-bugs {
  140. background-color: brown;
  141. }
  142. .sect-head-upcoming {
  143. background-color: mediumpurple;
  144. }
  145. .sect-body {
  146. padding: 15px 25px;
  147. li {
  148. list-style-type: disc;
  149. }
  150. }
  151. }
  152. </style>