FeedItem.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. <template>
  2. <article
  3. class="bg-gray-900 rounded-xl overflow-hidden border border-gray-800/80 hover:border-gray-700 transition-colors"
  4. :style="{ borderLeftColor: platformColor, borderLeftWidth: '3px' }"
  5. >
  6. <!-- Image-first layout for Instagram (and any post with media) -->
  7. <template v-if="isImageFirst && item.media[0]">
  8. <div class="relative">
  9. <img
  10. :src="item.media[0].thumbnail || item.media[0].url"
  11. :alt="item.media[0].alt || ''"
  12. class="w-full object-cover max-h-44"
  13. />
  14. <!-- Platform badge over image -->
  15. <span
  16. class="absolute top-2 right-2 text-xs font-semibold px-1.5 py-0.5 rounded"
  17. :style="{ backgroundColor: platformColor, color: '#fff' }"
  18. >
  19. {{ $t(`platforms.${item.platform}`) }}
  20. </span>
  21. <!-- Multiple images indicator -->
  22. <span
  23. v-if="item.media.length > 1"
  24. class="absolute top-2 left-2 text-xs bg-black/60 text-white px-1.5 py-0.5 rounded"
  25. >
  26. +{{ item.media.length - 1 }}
  27. </span>
  28. </div>
  29. </template>
  30. <div class="p-3">
  31. <!-- Author row -->
  32. <div class="flex items-center gap-2 mb-2">
  33. <img
  34. v-if="item.author.avatar"
  35. :src="item.author.avatar"
  36. :alt="item.author.name"
  37. class="w-7 h-7 rounded-full object-cover flex-shrink-0"
  38. />
  39. <div
  40. v-else
  41. class="w-7 h-7 rounded-full bg-gray-700 flex items-center justify-center flex-shrink-0 text-xs font-medium"
  42. >
  43. {{ item.author.name?.[0]?.toUpperCase() || '?' }}
  44. </div>
  45. <div class="flex-1 min-w-0">
  46. <span class="text-xs font-semibold text-white truncate block leading-tight">{{ item.author.name || item.author.username }}</span>
  47. <span class="text-xs text-gray-500 leading-tight">{{ timeAgo }}</span>
  48. </div>
  49. <!-- Platform badge (text-only posts) -->
  50. <span
  51. v-if="!isImageFirst"
  52. class="text-xs font-medium px-1.5 py-0.5 rounded flex-shrink-0"
  53. :style="{ backgroundColor: platformColor + '25', color: platformColor }"
  54. >
  55. {{ $t(`platforms.${item.platform}`) }}
  56. </span>
  57. </div>
  58. <!-- Content -->
  59. <p
  60. v-if="item.content"
  61. class="text-xs text-gray-300 leading-relaxed mb-2"
  62. :class="expanded ? '' : 'line-clamp-3'"
  63. >{{ item.content }}</p>
  64. <button
  65. v-if="item.content && item.content.length > 180 && !expanded"
  66. @click="expanded = true"
  67. class="text-xs text-gray-500 hover:text-gray-300 mb-2 -mt-1"
  68. >more</button>
  69. <!-- Inline image (non-Instagram text posts with media) -->
  70. <div v-if="!isImageFirst && item.media?.[0]" class="mb-2">
  71. <img
  72. :src="item.media[0].thumbnail || item.media[0].url"
  73. :alt="item.media[0].alt || ''"
  74. class="rounded-lg w-full object-cover max-h-28"
  75. />
  76. </div>
  77. <!-- Tags -->
  78. <div v-if="item.platformTags?.length" class="flex flex-wrap gap-1 mb-2">
  79. <span
  80. v-for="tag in item.platformTags.slice(0, 4)"
  81. :key="tag"
  82. class="text-xs text-blue-400"
  83. >#{{ tag }}</span>
  84. </div>
  85. <!-- Metrics + link -->
  86. <div class="flex items-center gap-3 text-xs text-gray-600">
  87. <span v-if="item.metrics?.likes">❤ {{ formatNum(item.metrics.likes) }}</span>
  88. <span v-if="item.metrics?.comments">💬 {{ formatNum(item.metrics.comments) }}</span>
  89. <span v-if="item.metrics?.shares">↺ {{ formatNum(item.metrics.shares) }}</span>
  90. <a
  91. v-if="item.url"
  92. :href="item.url"
  93. target="_blank"
  94. rel="noopener noreferrer"
  95. class="ml-auto hover:text-gray-400 transition-colors"
  96. >{{ $t('feed.openOriginal') }}</a>
  97. </div>
  98. </div>
  99. </article>
  100. </template>
  101. <script setup lang="ts">
  102. import { ref, computed } from 'vue'
  103. import { useI18n } from 'vue-i18n'
  104. import dayjs from 'dayjs'
  105. import relativeTime from 'dayjs/plugin/relativeTime'
  106. import 'dayjs/locale/tr'
  107. import 'dayjs/locale/en'
  108. import { PLATFORM_META } from '../../stores/platforms'
  109. import type { FeedItem } from '../../stores/feed'
  110. dayjs.extend(relativeTime)
  111. const { locale } = useI18n()
  112. const props = defineProps<{ item: FeedItem }>()
  113. const expanded = ref(false)
  114. const platformColor = computed(() => PLATFORM_META[props.item.platform]?.color ?? '#6b7280')
  115. const timeAgo = computed(() => dayjs(props.item.createdAt).locale(locale.value).fromNow())
  116. // Show image at the top for Instagram, or any post whose first media is a photo/video
  117. const isImageFirst = computed(() =>
  118. props.item.platform === 'instagram' ||
  119. (props.item.media?.[0]?.type === 'image' && props.item.platform === 'facebook')
  120. )
  121. function formatNum(n: number): string {
  122. if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + 'M'
  123. if (n >= 1000) return (n / 1000).toFixed(1) + 'K'
  124. return String(n)
  125. }
  126. </script>