WhatIsNew.vue 3.4 KB

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