1
0

EditHistoryModal.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. <template>
  2. <div>
  3. <b-modal
  4. v-model="isOpen"
  5. centered
  6. size="md"
  7. :scrollable="true"
  8. hide-footer
  9. header-class="py-2"
  10. body-class="p-0"
  11. title-class="w-100 text-center pl-4 font-weight-bold"
  12. title-tag="p">
  13. <template #modal-header="{ close }">
  14. <template v-if="historyIndex === undefined">
  15. <div class="d-flex flex-grow-1 justify-content-between align-items-center">
  16. <span style="width:40px;"></span>
  17. <h5 class="font-weight-bold mb-0">Post History</h5>
  18. <b-button size="sm" variant="link" @click="close()">
  19. <i class="far fa-times text-dark fa-lg"></i>
  20. </b-button>
  21. </div>
  22. </template>
  23. <template v-else>
  24. <div class="d-flex flex-grow-1 justify-content-between align-items-center pt-1">
  25. <b-button size="sm" variant="link" @click.prevent="historyIndex = undefined">
  26. <i class="fas fa-chevron-left text-primary fa-lg"></i>
  27. </b-button>
  28. <div class="d-flex align-items-center">
  29. <div class="d-flex align-items-center" style="gap: 5px;">
  30. <div class="d-flex align-items-center" style="gap: 5px;">
  31. <img
  32. :src="allHistory[0].account.avatar"
  33. width="16"
  34. height="16"
  35. class="rounded-circle"
  36. onerror="this.src='/storage/avatars/default.jpg';this.onerror=null;">
  37. <span class="font-weight-bold">{{ allHistory[0].account.username }}</span>
  38. </div>
  39. <div>{{ historyIndex == (allHistory.length - 1) ? 'created' : 'edited' }} {{ formatTime(allHistory[historyIndex].created_at) }}</div>
  40. </div>
  41. </div>
  42. <b-button size="sm" variant="link" @click="close()">
  43. <i class="fas fa-times text-dark fa-lg"></i>
  44. </b-button>
  45. </div>
  46. </template>
  47. </template>
  48. <div v-if="isLoading" class="d-flex align-items-center justify-content-center" style="min-height: 500px;">
  49. <b-spinner />
  50. </div>
  51. <template v-else>
  52. <div v-if="historyIndex === undefined" class="list-group border-top-0">
  53. <div
  54. v-for="(history, idx) in allHistory"
  55. class="list-group-item d-flex align-items-center justify-content-between" style="gap: 5px;">
  56. <div class="d-flex align-items-center" style="gap: 5px;">
  57. <div class="d-flex align-items-center" style="gap: 5px;">
  58. <img
  59. :src="history.account.avatar"
  60. width="24"
  61. height="24"
  62. class="rounded-circle"
  63. onerror="this.src='/storage/avatars/default.jpg';this.onerror=null;">
  64. <span class="font-weight-bold">{{ history.account.username }}</span>
  65. </div>
  66. <div>{{ idx == (allHistory.length - 1) ? 'created' : 'edited' }} {{ formatTime(history.created_at) }}</div>
  67. </div>
  68. <a class="stretched-link text-decoration-none" href="#" @click.prevent="historyIndex = idx">
  69. <div class="d-flex align-items-center" style="gap:5px;">
  70. <i class="far fa-chevron-right text-primary fa-lg"></i>
  71. </div>
  72. </a>
  73. </div>
  74. </div>
  75. <div v-else class="d-flex align-items-center flex-column border-top-0 justify-content-center">
  76. <!-- <img :src="allHistory[historyIndex].media_attachments[0].url" style="max-height: 400px;object-fit: contain;"> -->
  77. <template v-if="postType() === 'text'">
  78. </template>
  79. <template v-else-if="postType() === 'image'">
  80. <div style="width: 100%">
  81. <blur-hash-image
  82. :width="32"
  83. :height="32"
  84. :punch="1"
  85. class="img-contain border-bottom"
  86. :hash="allHistory[historyIndex].media_attachments[0].blurhash"
  87. :src="allHistory[historyIndex].media_attachments[0].url"
  88. />
  89. </div>
  90. </template>
  91. <template v-else-if="postType() === 'album'">
  92. <div style="width: 100%">
  93. <b-carousel
  94. controls
  95. indicators
  96. background="#000000"
  97. style="text-shadow: 1px 1px 2px #333;"
  98. >
  99. <b-carousel-slide
  100. v-for="(media, idx) in allHistory[historyIndex].media_attachments"
  101. :key="'pfph:'+media.id+':'+idx"
  102. :img-src="media.url"
  103. ></b-carousel-slide>
  104. </b-carousel>
  105. </div>
  106. </template>
  107. <template v-else-if="postType() === 'video'">
  108. <div style="width: 100%">
  109. <div class="embed-responsive embed-responsive-16by9 border-bottom">
  110. <video class="video" controls playsinline preload="metadata" loop>
  111. <source :src="allHistory[historyIndex].media_attachments[0].url" :type="allHistory[historyIndex].media_attachments[0].mime">
  112. </video>
  113. </div>
  114. </div>
  115. </template>
  116. <div class="w-100 my-4 px-4 text-break justify-content-start">
  117. <p class="mb-0" v-html="allHistory[historyIndex].content"></p>
  118. <!-- <p class="mb-0" v-html="getDiff(historyIndex)"></p> -->
  119. </div>
  120. </div>
  121. </template>
  122. </b-modal>
  123. </div>
  124. </template>
  125. <script type="text/javascript">
  126. export default {
  127. props: {
  128. status: {
  129. type: Object
  130. }
  131. },
  132. data() {
  133. return {
  134. isOpen: false,
  135. isLoading: true,
  136. allHistory: [],
  137. historyIndex: undefined,
  138. user: window._sharedData.user
  139. }
  140. },
  141. methods: {
  142. open() {
  143. this.isOpen = true;
  144. this.isLoading = true;
  145. this.historyIndex = undefined;
  146. this.allHistory = [];
  147. setTimeout(() => {
  148. this.fetchHistory();
  149. }, 300);
  150. },
  151. fetchHistory() {
  152. axios.get(`/api/v1/statuses/${this.status.id}/history`)
  153. .then(res => {
  154. this.allHistory = res.data;
  155. })
  156. .finally(() => {
  157. this.isLoading = false;
  158. })
  159. },
  160. getDiff(idx) {
  161. if(idx == this.allHistory.length - 1) {
  162. return this.allHistory[this.allHistory.length - 1].content;
  163. }
  164. // let r = Diff.diffChars(this.allHistory[idx - 1].content.replace(/(<([^>]+)>)/gi, ""), this.allHistory[idx].content.replace(/(<([^>]+)>)/gi, ""));
  165. let fragment = document.createElement('div');
  166. r.forEach((part) => {
  167. // green for additions, red for deletions
  168. // grey for common parts
  169. const color = part.added ? 'green' :
  170. part.removed ? 'red' : 'grey';
  171. let span = document.createElement('span');
  172. span.style.color = color;
  173. console.log(part.value, part.value.length)
  174. if(part.added) {
  175. let trimmed = part.value.trim();
  176. if(!trimmed.length) {
  177. span.appendChild(document.createTextNode('·'));
  178. } else {
  179. span.appendChild(document.createTextNode(part.value));
  180. }
  181. } else {
  182. span.appendChild(document.createTextNode(part.value));
  183. }
  184. fragment.appendChild(span);
  185. });
  186. return fragment.innerHTML;
  187. },
  188. formatTime(ts) {
  189. let date = Date.parse(ts);
  190. let seconds = Math.floor((new Date() - date) / 1000);
  191. let interval = Math.floor(seconds / 63072000);
  192. if (interval < 0) {
  193. return "0s";
  194. }
  195. if (interval >= 1) {
  196. return interval + (interval == 1 ? ' year' : ' years') + " ago";
  197. }
  198. interval = Math.floor(seconds / 604800);
  199. if (interval >= 1) {
  200. return interval + (interval == 1 ? ' week' : ' weeks') + " ago";
  201. }
  202. interval = Math.floor(seconds / 86400);
  203. if (interval >= 1) {
  204. return interval + (interval == 1 ? ' day' : ' days') + " ago";
  205. }
  206. interval = Math.floor(seconds / 3600);
  207. if (interval >= 1) {
  208. return interval + (interval == 1 ? ' hour' : ' hours') + " ago";
  209. }
  210. interval = Math.floor(seconds / 60);
  211. if (interval >= 1) {
  212. return interval + (interval == 1 ? ' minute' : ' minutes') + " ago";
  213. }
  214. return Math.floor(seconds) + " seconds ago";
  215. },
  216. postType() {
  217. if(this.historyIndex === undefined) {
  218. return;
  219. }
  220. let post = this.allHistory[this.historyIndex];
  221. if(!post) {
  222. return 'text';
  223. }
  224. let media = post.media_attachments;
  225. if(!media || !media.length) {
  226. return 'text';
  227. }
  228. if(media.length == 1) {
  229. return media[0].type;
  230. }
  231. return 'album';
  232. }
  233. }
  234. }
  235. </script>
  236. <style lang="scss">
  237. .img-contain {
  238. img {
  239. object-fit: contain;
  240. }
  241. }
  242. </style>