diff options
author | Devlin Junker <devlin.junker@gmail.com> | 2023-08-30 22:13:58 -0700 |
---|---|---|
committer | Benjamin Brahmer <info@b-brahmer.de> | 2023-09-03 12:05:24 +0200 |
commit | a3077f1b97b65d477e9b83f7a8b914d1a304b83c (patch) | |
tree | b67fca1e04c3baec3a93e621bfbcfab066db89d8 /src/components/feed-display/FeedItemRow.vue | |
parent | 83f99debaa1a6bfde88b62e43e6a0852ce928063 (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.vue | 240 |
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> |