Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Vue Rewrite] Single Feed Route using FeedItemDisplayList component #2339

Merged
merged 2 commits into from
Aug 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions src/components/FeedItemDisplay.vue
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@
<a :href="item.enclosureLink"><img :src="item.mediaThumbnail" alt=""></a>
</div>

<div v-if="item.mediaDescription" class="enclosure description" v-html="item.mediaDescription" />

Check warning on line 86 in src/components/FeedItemDisplay.vue

View workflow job for this annotation

GitHub Actions / eslint node

'v-html' directive can lead to XSS attack

Check warning on line 86 in src/components/FeedItemDisplay.vue

View workflow job for this annotation

GitHub Actions / eslint

'v-html' directive can lead to XSS attack

<div class="body" :dir="item.rtl && 'rtl'" v-html="item.body" />

Check warning on line 88 in src/components/FeedItemDisplay.vue

View workflow job for this annotation

GitHub Actions / eslint node

'v-html' directive can lead to XSS attack

Check warning on line 88 in src/components/FeedItemDisplay.vue

View workflow job for this annotation

GitHub Actions / eslint

'v-html' directive can lead to XSS attack
</div>
</div>
</template>
Expand Down Expand Up @@ -171,11 +171,11 @@
this.$store.dispatch(item.starred ? ACTIONS.UNSTAR_ITEM : ACTIONS.STAR_ITEM, { item })
},

getMediaType(mime: string): 'audio' | 'video' | false {

Check warning on line 174 in src/components/FeedItemDisplay.vue

View workflow job for this annotation

GitHub Actions / eslint node

'mime' is defined but never used

Check warning on line 174 in src/components/FeedItemDisplay.vue

View workflow job for this annotation

GitHub Actions / eslint

'mime' is defined but never used
// TODO: figure out how to check media type
return false
},
play(item: FeedItem) {

Check warning on line 178 in src/components/FeedItemDisplay.vue

View workflow job for this annotation

GitHub Actions / eslint node

'item' is defined but never used

Check warning on line 178 in src/components/FeedItemDisplay.vue

View workflow job for this annotation

GitHub Actions / eslint

'item' is defined but never used
// TODO: implement play audio/video
},
},
Expand All @@ -183,7 +183,7 @@

</script>

<style>
<style scoped>
.feed-item-display {
max-height: 100%;
overflow-y: hidden;
Expand Down Expand Up @@ -228,11 +228,10 @@
.article h1 {
font-weight: bold;
font-size: 17px;
margin-top: 25px;
}

.action-bar {
padding: 20px;
padding: 0px 20px 0px 20px;

display: flex;
justify-content: right
Expand All @@ -242,4 +241,8 @@
cursor: pointer;
margin: 5px;
}

.action-bar .material-design-icon:hover {
color: var(--color-text-light);
}
</style>
151 changes: 135 additions & 16 deletions src/components/FeedItemDisplayList.vue
Original file line number Diff line number Diff line change
@@ -1,49 +1,117 @@
<template>
<div class="feed-item-display-container">
<VirtualScroll :reached-end="reachedEnd"
:fetch-key="fetchKey"
@load-more="fetchMore()">
<template v-if="items && items.length > 0">
<template v-for="item in items">
<FeedItemRow :key="item.id" :item="item" />
<div>
<div style="justify-content: right; display: flex">
<NcActions class="filter-container" :force-menu="true">
<template #icon>
<FilterIcon />
</template>
</template>
</VirtualScroll>
<NcActionButton v-if="cfg.unreadFilter" @click="toggleFilter(unreadFilter)">
<template #default>
{{ t("news", "Unread") }}
</template>
<template #icon>
<EyeIcon v-if="filter !== unreadFilter" />
<EyeCheckIcon v-if="filter === unreadFilter" />
</template>
</NcActionButton>
<NcActionButton v-if="cfg.starFilter" @click="toggleFilter(starFilter)">
<template #default>
{{ t("news", "Starred") }}
</template>
<template #icon>
<StarIcon v-if="filter !== starFilter" />
<StarCheckIcon v-if="filter === starFilter" />
</template>
</NcActionButton>
</NcActions>
</div>
<div class="feed-item-display-container">
<VirtualScroll :reached-end="reachedEnd"
:fetch-key="fetchKey"
@load-more="fetchMore()">
<template v-if="items && items.length > 0">
<template v-for="item in filterSortedItems()">
<FeedItemRow :key="item.id" :item="item" />
</template>
</template>
</VirtualScroll>

<div v-if="selected !== undefined" class="feed-item-container">
<FeedItemDisplay :item="selected" />
<div v-if="selected !== undefined" class="feed-item-container">
<FeedItemDisplay :item="selected" />
</div>
</div>
</div>
</template>

<script lang="ts">
import Vue from 'vue'
import _ from 'lodash'

import FilterIcon from 'vue-material-design-icons/Filter.vue'
import StarIcon from 'vue-material-design-icons/Star.vue'
import StarCheckIcon from 'vue-material-design-icons/StarCheck.vue'
import EyeIcon from 'vue-material-design-icons/Eye.vue'
import EyeCheckIcon from 'vue-material-design-icons/EyeCheck.vue'

import NcActions from '@nextcloud/vue/dist/Components/NcActions.js'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'

import VirtualScroll from './VirtualScroll.vue'
import FeedItemRow from './FeedItemRow.vue'
import FeedItemDisplay from './FeedItemDisplay.vue'

import { FeedItem } from '../types/FeedItem'

const DEFAULT_DISPLAY_LIST_CONFIG = {
starFilter: true,
unreadFilter: true,
}

export default Vue.extend({
components: {
VirtualScroll,
FeedItemRow,
FeedItemDisplay,
FilterIcon,
StarIcon,
StarCheckIcon,
EyeIcon,
EyeCheckIcon,
NcActions,
NcActionButton,
},
props: {
items: {
type: Array,
type: Array<FeedItem>,
required: true,
},
fetchKey: {
type: String,
required: true,
},
config: {
type: Object,
default: () => {
return DEFAULT_DISPLAY_LIST_CONFIG
},
},
},
data() {
return {
mounted: false,

// no filter to start
filter: () => { return true as boolean },

// Always want to sort by date (most recent first)
sort: (a: FeedItem, b: FeedItem) => {
if (a.pubDate > b.pubDate) {
return -1
} else {
return 1
}
},
cache: [] as FeedItem[] | undefined,
}
},
computed: {
Expand All @@ -53,6 +121,9 @@ export default Vue.extend({
reachedEnd(): boolean {
return this.mounted && this.$store.state.items.allItemsLoaded[this.fetchKey] === true
},
cfg() {
return _.defaults({ ...this.config }, DEFAULT_DISPLAY_LIST_CONFIG)
},
},
mounted() {
this.mounted = true
Expand All @@ -61,22 +132,70 @@ export default Vue.extend({
fetchMore() {
this.$emit('load-more')
},
noFilter(): boolean {
return true
},
starFilter(item: FeedItem): boolean {
return item.starred
},
unreadFilter(item: FeedItem): boolean {
return item.unread
},
toggleFilter(filter: () => boolean) {
if (this.filter === filter) {
this.filter = this.noFilter
if (filter === this.unreadFilter) {
this.cache = undefined
}
} else {
this.filter = filter as () => boolean
}
},
filterSortedItems(): FeedItem[] {
let response = [...this.items] as FeedItem[]

// if we're filtering on unread, we want to cache the unread items when the user presses the filter button
// that way when the user opens an item, it won't be removed from the displayed list of items (once it's no longer unread)
if (this.filter === this.unreadFilter) {
if (!this.cache) {
if (this.items.length > 0) {
this.cache = this.items.filter(this.unreadFilter)
}
} else if (this.items.length > (this.cache?.length)) {
for (const item of this.items) {
if (item.unread && this.cache.find((unread: FeedItem) => unread.id === item.id) === undefined) {
this.cache.push(item)
}
}
}
response = [...this.cache as FeedItem[]]
} else {
response = response.filter(this.filter)
}

return response.sort(this.sort)
},
},
})
</script>

<style scoped>
.virtual-scroll {
border-top: 1px solid var(--color-border);
width: 100%;
}

.feed-item-display-container {
display: flex;
height: 100%;
}

.virtual-scroll {
width: 100%;
}

.feed-item-container {
max-width: 50%;
overflow-y: hidden;
}

.filter-container {
padding: 5px;
}
</style>
12 changes: 6 additions & 6 deletions src/components/FeedItemRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<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" />

Check warning on line 18 in src/components/FeedItemRow.vue

View workflow job for this annotation

GitHub Actions / eslint node

'v-html' directive can lead to XSS attack

Check warning on line 18 in src/components/FeedItemRow.vue

View workflow job for this annotation

GitHub Actions / eslint

'v-html' directive can lead to XSS attack
</span>
</div>
<div class="date-container">
Expand Down Expand Up @@ -210,31 +210,31 @@
height: 30px;
}

.material-design-icon {
.feed-item-row .button-container .material-design-icon {
color: var(--color-text-lighter)
}

.material-design-icon:hover {
.feed-item-row .button-container .material-design-icon:hover {
color: var(--color-text-light);
}

.material-design-icon.rss-icon:hover {
.feed-item-row .button-container .material-design-icon.rss-icon:hover {
color: #555555;
}

.material-design-icon.starred {
color: rgb(255, 204, 0);
color: rgb(255, 204, 0) !important;
}

.material-design-icon.keep-unread {
.feed-item-row .button-container .material-design-icon.keep-unread {
color: var(--color-main-text);
}

.material-design-icon.starred:hover {
color: #555555;
}

.eye-check-icon {
.feed-item-row .button-container .eye-check-icon {
color: var(--color-primary-light);
}
</style>
31 changes: 23 additions & 8 deletions src/components/Sidebar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,17 @@
<NcAppNavigationItem v-for="topLevelItem in topLevelNav"
:key="topLevelItem.name || topLevelItem.title"
:title="topLevelItem.name || topLevelItem.title"
:icon="topLevelItem.name !== undefined ? 'icon-folder': ''"
:icon="isFolder(topLevelItem) ? 'icon-folder': ''"
:to="isFolder(topLevelItem) ? {} : { name: ROUTES.FEED, params: { feedId: topLevelItem.id.toString() } }"
:allow-collapse="true">
<template #default>
<NcAppNavigationItem v-for="feed in topLevelItem.feeds"
:key="feed.name"
:title="feed.title">
:title="feed.title"
:to="{ name: ROUTES.FEED, props: { feedId: feed.id } }">
<template #icon>
<img v-if="feed.faviconLink"
:src="feed.faviconLink"
alt="feedIcon">
<div v-if="!feed.faviconLink" class="icon-rss" />
<RssIcon v-if="!feed.faviconLink" />
<span v-if="feed.faviconLink" style="width: 24px; background-size: contain;" :style="{ 'backgroundImage': 'url(' + feed.faviconLink + ')' }" />
</template>
<template #actions>
<NcActionButton icon="icon-checkmark"
Expand Down Expand Up @@ -100,8 +100,17 @@
</template>
</NcAppNavigationItem>
</template>
<template v-if="topLevelItem.feedCount > 0" #counter>
<NcCounterBubble>{{ topLevelItem.feedCount }}</NcCounterBubble>
<template #icon>
<RssIcon v-if="topLevelItem.feedCount === undefined && !topLevelItem.faviconLink" />
<span v-if="topLevelItem.feedCount === undefined && topLevelItem.faviconLink" style="height: 16px; width: 16px; background-size: contain;" :style="{ 'backgroundImage': 'url(' + topLevelItem.faviconLink + ')' }" />
</template>
<template #counter>
<NcCounterBubble v-if="topLevelItem.feedCount > 0">
{{ topLevelItem.feedCount }}
</NcCounterBubble>
<NcCounterBubble v-if="topLevelItem.unreadCount > 0">
{{ topLevelItem.unreadCount }}
</NcCounterBubble>
</template>
<template #actions>
<NcActionButton icon="icon-checkmark" @click="alert('TODO: Mark read')">
Expand Down Expand Up @@ -140,6 +149,8 @@ import NcAppNavigationNewItem from '@nextcloud/vue/dist/Components/NcAppNavigati
import NcCounterBubble from '@nextcloud/vue/dist/Components/NcCounterBubble.js'
import NcActionButton from '@nextcloud/vue/dist/Components/NcActionButton.js'

import RssIcon from 'vue-material-design-icons/Rss.vue'

import { ROUTES } from '../routes'
import { ACTIONS, AppState } from '../store'

Expand Down Expand Up @@ -170,6 +181,7 @@ export default Vue.extend({
NcCounterBubble,
NcActionButton,
AddFeed,
RssIcon,
},
data: () => {
return {
Expand Down Expand Up @@ -199,6 +211,9 @@ export default Vue.extend({
alert(msg: string) {
window.alert(msg)
},
isFolder(item: Feed | Folder) {
return (item as Folder).name !== undefined
},
},
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import axios from '@nextcloud/axios'
import * as router from '@nextcloud/router'

import AddFeed from './AddFeed.vue'
import AddFeed from '../AddFeed.vue'

import { ExploreSite } from '../types/ExploreSite'
import { Feed } from '../types/Feed'
import { ExploreSite } from '../../types/ExploreSite'
import { Feed } from '../../types/Feed'

const ExploreComponent = Vue.extend({
components: {
Expand Down
Loading
Loading