summaryrefslogtreecommitdiffstats
path: root/src/components/feed-display/FeedItemRow.vue
diff options
context:
space:
mode:
authorDevlin Junker <devlin.junker@gmail.com>2023-08-30 22:13:58 -0700
committerBenjamin Brahmer <info@b-brahmer.de>2023-09-03 12:05:24 +0200
commita3077f1b97b65d477e9b83f7a8b914d1a304b83c (patch)
treeb67fca1e04c3baec3a93e621bfbcfab066db89d8 /src/components/feed-display/FeedItemRow.vue
parent83f99debaa1a6bfde88b62e43e6a0852ce928063 (diff)
fix loading bug and move to feed-display directory
Signed-off-by: Devlin Junker <devlin.junker@gmail.com>
Diffstat (limited to 'src/components/feed-display/FeedItemRow.vue')
-rw-r--r--src/components/feed-display/FeedItemRow.vue240
1 files changed, 240 insertions, 0 deletions
diff --git a/src/components/feed-display/FeedItemRow.vue b/src/components/feed-display/FeedItemRow.vue
new file mode 100644
index 000000000..03670295a
--- /dev/null
+++ b/src/components/feed-display/FeedItemRow.vue
@@ -0,0 +1,240 @@
+<template>
+ <div class="feed-item-row" @click="select()">
+ <div class="link-container">
+ <a class="external"
+ target="_blank"
+ rel="noreferrer"
+ :href="item.url"
+ :title="t('news', 'Open website')"
+ @click="markRead(item); $event.stopPropagation();">
+ <EarthIcon />
+ </a>
+ <RssIcon v-if="!getFeed(item.feedId).faviconLink" />
+ <span v-if="getFeed(item.feedId).faviconLink" style="width: 24px; background-size: contain;" :style="{ 'backgroundImage': 'url(' + getFeed(item.feedId).faviconLink + ')' }" />
+ </div>
+ <div class="title-container" :class="{ 'unread': item.unread }">
+ <span style="white-space: nowrap" :dir="item.rtl && 'rtl'">
+ {{ item.title }}
+ <span class="intro" v-html="item.intro" />
+ </span>
+ </div>
+ <div class="date-container">
+ <time class="date" :title="formatDate(item.pubDate*1000, 'yyyy-MM-dd HH:mm:ss')" :datetime="formatDatetime(item.pubDate*1000, 'yyyy-MM-ddTHH:mm:ssZ')">
+ {{ getRelativeTimestamp(item.pubDate*1000) }}
+ </time>
+ </div>
+ <div class="button-container" @click="$event.stopPropagation()">
+ <StarIcon :class="{'starred': item.starred }" @click="toggleStarred(item)" />
+ <EyeIcon v-if="item.unread && !keepUnread" @click="toggleKeepUnread(item)" />
+ <EyeCheckIcon v-if="!item.unread && !keepUnread" @click="toggleKeepUnread(item)" />
+ <EyeLockIcon v-if="keepUnread" class="keep-unread" @click="toggleKeepUnread(item)" />
+ <NcActions :force-menu="true">
+ <template #icon>
+ <ShareVariant />
+ </template>
+ <NcActionButton>
+ <template #default>
+ <!-- TODO: Share Menu --> TODO
+ </template>
+ <template #icon>
+ <ShareVariant />
+ </template>
+ </NcActionButton>
+ </NcActions>
+ </div>
+ </div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue'
+import { mapState } from 'vuex'
+
+import EarthIcon from 'vue-material-design-icons/Earth.vue'
+import StarIcon from 'vue-material-design-icons/Star.vue'
+import EyeIcon from 'vue-material-design-icons/Eye.vue'
+import EyeCheckIcon from 'vue-material-design-icons/EyeCheck.vue'
+import EyeLockIcon from 'vue-material-design-icons/EyeLock.vue'
+import RssIcon from 'vue-material-design-icons/Rss.vue'
+import ShareVariant from 'vue-material-design-icons/ShareVariant.vue'
+
+import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
+import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'
+
+import { Feed } from '../../types/Feed'
+import { FeedItem } from '../../types/FeedItem'
+import { ACTIONS, MUTATIONS } from '../../store'
+
+export default Vue.extend({
+ name: 'FeedItemRow',
+ components: {
+ EarthIcon,
+ StarIcon,
+ EyeIcon,
+ EyeCheckIcon,
+ EyeLockIcon,
+ ShareVariant,
+ RssIcon,
+ NcActions,
+ NcActionButton,
+ },
+ props: {
+ item: {
+ type: Object,
+ required: true,
+ },
+ },
+ data: () => {
+ return {
+ keepUnread: false,
+ }
+ },
+ computed: {
+ ...mapState(['feeds']),
+ },
+ methods: {
+ select(): void {
+ this.$store.commit(MUTATIONS.SET_SELECTED_ITEM, { id: this.item.id })
+ this.markRead(this.item)
+ },
+ formatDate(epoch: number): string {
+ return new Date(epoch).toLocaleString()
+ },
+ formatDatetime(epoch: number): string {
+ return new Date(epoch).toISOString()
+ },
+ getRelativeTimestamp(previous: number): string {
+ const current = Date.now()
+
+ const msPerMinute = 60 * 1000
+ const msPerHour = msPerMinute * 60
+ const msPerDay = msPerHour * 24
+ const msPerMonth = msPerDay * 30
+ const msPerYear = msPerDay * 365
+
+ const elapsed = current - previous
+
+ if (elapsed < msPerMinute) {
+ return Math.round(elapsed / 1000) + ' ' + t('news', 'seconds')
+ } else if (elapsed < msPerHour) {
+ return Math.round(elapsed / msPerMinute) + ' ' + t('news', 'minutes ago')
+ } else if (elapsed < msPerDay) {
+ return Math.round(elapsed / msPerHour) + ' ' + t('news', 'hours ago')
+ } else if (elapsed < msPerMonth) {
+ return Math.round(elapsed / msPerDay) + ' ' + t('news', 'days ago')
+ } else if (elapsed < msPerYear) {
+ return Math.round(elapsed / msPerMonth) + ' ' + t('news', 'months ago')
+ } else {
+ return Math.round(elapsed / msPerYear) + ' ' + t('news', 'years ago')
+ }
+ },
+ getFeed(id: number): Feed {
+ return this.$store.getters.feeds.find((feed: Feed) => feed.id === id) || {}
+ },
+ markRead(item: FeedItem): void {
+ if (!this.keepUnread) {
+ this.$store.dispatch(ACTIONS.MARK_READ, { item })
+ }
+ },
+ toggleKeepUnread(item: FeedItem): void {
+ this.keepUnread = !this.keepUnread
+ this.$store.dispatch(ACTIONS.MARK_UNREAD, { item })
+ },
+ toggleStarred(item: FeedItem): void {
+ this.$store.dispatch(item.starred ? ACTIONS.UNSTAR_ITEM : ACTIONS.STAR_ITEM, { item })
+ },
+ },
+})
+
+</script>
+
+<style>
+ .feed-item-container {
+ border-bottom: 1px solid #222;
+ }
+
+ .feed-item-row {
+ display: flex; padding: 5px 10px;
+ }
+
+ .feed-item-row:hover {
+ background-color: var(--color-background-hover);
+ }
+
+ .feed-item-row, .feed-item-row * {
+ cursor: pointer;
+ }
+
+ .feed-item-row .link-container {
+ padding-right: 5px;
+ display: flex;
+ flex-direction: row;
+ align-self: start;
+ }
+
+ .feed-item-row .title-container {
+ color: var(--color-text-lighter);
+
+ flex-grow: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+
+ .feed-item-row .title-container.unread {
+ color: var(--color-main-text);
+ font-weight: bold;
+ }
+
+ .feed-item-row .intro {
+ color: var(--color-text-lighter);
+ font-size: 10pt;
+ font-weight: normal;
+ margin-left: 20px;
+ }
+
+ .feed-item-row .date-container {
+ color: var(--color-text-lighter);
+ padding-left: 4px;
+ white-space: nowrap;
+ }
+
+ .feed-item-row .button-container {
+ display: flex;
+ flex-direction: row;
+ align-self: start;
+ }
+
+ .button-container .action-item .button-vue, .button-container .material-design-icon {
+ width: 30px !important;
+ min-width: 30px;
+ min-height: 30px;
+ height: 30px;
+ }
+
+ .feed-item-row .button-container .material-design-icon {
+ color: var(--color-text-lighter)
+ }
+
+ .feed-item-row .button-container .material-design-icon:hover {
+ color: var(--color-text-light);
+ }
+
+ .feed-item-row .button-container .material-design-icon.rss-icon:hover {
+ color: #555555;
+ }
+
+ .material-design-icon.starred {
+ color: rgb(255, 204, 0) !important;
+ }
+
+ .feed-item-row .button-container .material-design-icon.keep-unread {
+ color: var(--color-main-text);
+ }
+
+ .material-design-icon.starred:hover {
+ color: #555555;
+ }
+
+ .feed-item-row .button-container .eye-check-icon {
+ color: var(--color-primary-light);
+ }
+</style>