mirror of
https://github.com/VueTorrent/VueTorrent.git
synced 2025-03-14 12:10:18 +03:00
perf: Add RSS articles view (#622) @Larsluph
This commit is contained in:
parent
5a2ed4ea99
commit
f8fcafa1b1
20 changed files with 298 additions and 66 deletions
|
@ -8,9 +8,12 @@
|
|||
</v-card-title>
|
||||
<v-card-text>
|
||||
<v-form ref="feedForm" class="px-6 mt-3">
|
||||
<v-container>
|
||||
<v-container v-if="!hasInitialFeed">
|
||||
<v-text-field v-model="feed.url" :label="$t('modals.newFeed.url')" required />
|
||||
</v-container>
|
||||
<v-container>
|
||||
<v-text-field v-model="feed.name" :label="$t('modals.newFeed.feedName')" required />
|
||||
</v-container>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
<v-divider />
|
||||
|
@ -30,7 +33,6 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex'
|
||||
import qbit from '@/services/qbit'
|
||||
import { Modal } from '@/mixins'
|
||||
import { mdiCancel, mdiTagPlus, mdiPencil } from '@mdi/js'
|
||||
|
@ -43,21 +45,19 @@ export default {
|
|||
initialFeed: Object
|
||||
},
|
||||
data: () => ({
|
||||
feed: { url: '' },
|
||||
feed: { url: '', name: '' },
|
||||
mdiCancel,
|
||||
mdiTagPlus,
|
||||
mdiPencil
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters(['getSelectedFeed']),
|
||||
hasInitialFeed() {
|
||||
return !!(this.initialFeed && this.initialFeed.name)
|
||||
return !!(this.initialFeed && this.initialFeed.name && this.initialFeed.url)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$store.commit('FETCH_FEEDS')
|
||||
if (this.hasInitialFeed) {
|
||||
this.feed = this.initialFeed
|
||||
this.feed = {...this.initialFeed}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -70,7 +70,7 @@ export default {
|
|||
this.dialog = false
|
||||
},
|
||||
edit() {
|
||||
qbit.editFeed(this.feed)
|
||||
qbit.editFeed(this.initialFeed.name, this.feed.name)
|
||||
Vue.$toast.success(this.$t('toast.feedSaved'))
|
||||
this.cancel()
|
||||
}
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
<v-text-field v-model="rule.name" :label="$t('modals.newRule.name')" required />
|
||||
</v-container>
|
||||
<v-container>
|
||||
<v-text-field v-model="rule.def.mustContain" :label="$t('modals.newRule.def.mustContain')" required />
|
||||
<v-text-field v-model="rule.mustContain" :label="$t('modals.newRule.def.mustContain')" required />
|
||||
</v-container>
|
||||
<v-container>
|
||||
<v-subheader class="pa-0">
|
||||
{{ $t('modals.newRule.def.affectedFeeds') }}
|
||||
</v-subheader>
|
||||
<template v-for="(item, index) in availableFeeds">
|
||||
<v-checkbox :key="index" v-model="rule.def.affectedFeeds" hide-details :label="item.name" :value="item.url" />
|
||||
<v-checkbox :key="index" v-model="rule.affectedFeeds" hide-details :label="item.name" :value="item.url" />
|
||||
</template>
|
||||
</v-container>
|
||||
</v-form>
|
||||
|
@ -45,7 +45,6 @@ import { mapGetters } from 'vuex'
|
|||
import qbit from '@/services/qbit'
|
||||
import { Modal } from '@/mixins'
|
||||
import { mdiCancel, mdiTagPlus, mdiPencil } from '@mdi/js'
|
||||
import Vue from 'vue'
|
||||
|
||||
export default {
|
||||
name: 'RuleForm',
|
||||
|
@ -56,18 +55,16 @@ export default {
|
|||
data: () => ({
|
||||
rule: {
|
||||
name: '',
|
||||
def: {
|
||||
mustContain: '',
|
||||
affectedFeeds: [],
|
||||
enabled: true
|
||||
}
|
||||
mustContain: '',
|
||||
affectedFeeds: [],
|
||||
enabled: true
|
||||
},
|
||||
mdiCancel,
|
||||
mdiTagPlus,
|
||||
mdiPencil
|
||||
}),
|
||||
computed: {
|
||||
...mapGetters(['getSelectedRule', 'getFeeds']),
|
||||
...mapGetters(['getFeeds']),
|
||||
availableFeeds() {
|
||||
return this.getFeeds()
|
||||
},
|
||||
|
@ -83,7 +80,7 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
create() {
|
||||
qbit.createRule(this.rule.name, this.rule.def)
|
||||
qbit.createRule(this.rule)
|
||||
this.cancel()
|
||||
},
|
||||
cancel() {
|
||||
|
@ -91,8 +88,8 @@ export default {
|
|||
this.dialog = false
|
||||
},
|
||||
edit() {
|
||||
qbit.editRule(this.rule)
|
||||
Vue.$toast.success(this.$t('toast.ruleSaved'))
|
||||
qbit.renameRule(this.initialRule.name, this.rule.name)
|
||||
this.$toast.success(this.$t('toast.ruleSaved'))
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
</v-tooltip>
|
||||
<v-tooltip bottom open-delay="400">
|
||||
<template #activator="{ on }">
|
||||
<v-btn small fab :text="!mobile" class="mr-0 ml-0" :aria-label="$t('navbar.topActions.removeSelected')" v-on="on" @click="removeTorrents">
|
||||
<v-btn :text="!mobile" small fab class="mr-0 ml-0" :aria-label="$t('navbar.topActions.removeSelected')" v-on="on" @click="removeTorrents">
|
||||
<v-icon color="grey">
|
||||
{{ mdiDelete }}
|
||||
</v-icon>
|
||||
|
@ -50,6 +50,16 @@
|
|||
</template>
|
||||
<span>{{ $t('navbar.topActions.searchNew') }}</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom open-delay="400">
|
||||
<template #activator="{ on }">
|
||||
<v-btn :text="!mobile" small fab color="grey--text" class="mr-0 ml-0" :aria-label="$t('navbar.topActions.rssArticles')" v-on="on" @click="goToRss">
|
||||
<v-icon color="grey">
|
||||
{{ mdiRss }}
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<span>{{ $t('navbar.topActions.rssArticles') }}</span>
|
||||
</v-tooltip>
|
||||
<v-tooltip bottom open-delay="400">
|
||||
<template #activator="{ on }">
|
||||
<v-btn small fab :text="!mobile" class="mr-0 ml-0" :aria-label="$t('navbar.topActions.openSettings')" v-on="on" @click="goToSettings">
|
||||
|
@ -67,7 +77,18 @@
|
|||
import { General } from '@/mixins'
|
||||
import { mapState } from 'vuex'
|
||||
import qbit from '@/services/qbit'
|
||||
import { mdiSort, mdiCog, mdiCheckboxBlankOutline, mdiCheckboxMarked, mdiSearchWeb, mdiDelete, mdiPlus, mdiPlay, mdiPause } from '@mdi/js'
|
||||
import {
|
||||
mdiSort,
|
||||
mdiCog,
|
||||
mdiCheckboxBlankOutline,
|
||||
mdiCheckboxMarked,
|
||||
mdiSearchWeb,
|
||||
mdiDelete,
|
||||
mdiPlus,
|
||||
mdiPlay,
|
||||
mdiPause,
|
||||
mdiRss
|
||||
} from '@mdi/js'
|
||||
|
||||
export default {
|
||||
name: 'TopActions',
|
||||
|
@ -79,6 +100,7 @@ export default {
|
|||
mdiSort,
|
||||
mdiPlus,
|
||||
mdiSearchWeb,
|
||||
mdiRss,
|
||||
mdiPlay,
|
||||
mdiPause,
|
||||
mdiDelete,
|
||||
|
@ -105,6 +127,9 @@ export default {
|
|||
addModal(name) {
|
||||
this.createModal(name)
|
||||
},
|
||||
goToRss() {
|
||||
if (this.$route.name !== 'rss') this.$router.push({ name: 'rss' })
|
||||
},
|
||||
goToSettings() {
|
||||
if (this.$route.name !== 'settings') this.$router.push({ name: 'settings' })
|
||||
}
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
<v-list-item-content>
|
||||
<v-list-item-title v-text="item.name" />
|
||||
</v-list-item-content>
|
||||
<v-list-item-action>
|
||||
<v-icon @click="editFeed(item)">
|
||||
{{ mdiPencil }}
|
||||
</v-icon>
|
||||
</v-list-item-action>
|
||||
<v-list-item-action>
|
||||
<v-icon color="red" @click="deleteFeed(item)">
|
||||
{{ mdiDelete }}
|
||||
|
@ -25,17 +30,21 @@
|
|||
</v-row>
|
||||
</v-card>
|
||||
</template>
|
||||
<script>
|
||||
|
||||
<script lang="ts">
|
||||
import { mapGetters } from 'vuex'
|
||||
import qbit from '@/services/qbit'
|
||||
import { mdiDelete } from '@mdi/js'
|
||||
import {mdiDelete, mdiPencil} from '@mdi/js'
|
||||
|
||||
import { Tab, General, FullScreenModal } from '@/mixins'
|
||||
import {Feed} from "@/types/vuetorrent";
|
||||
import {defineComponent} from "vue";
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: 'Feeds',
|
||||
mixins: [Tab, General, FullScreenModal],
|
||||
data: () => ({
|
||||
mdiPencil,
|
||||
mdiDelete
|
||||
}),
|
||||
computed: {
|
||||
|
@ -51,7 +60,10 @@ export default {
|
|||
activeMethod() {
|
||||
this.$store.commit('FETCH_FEEDS')
|
||||
},
|
||||
deleteFeed(item) {
|
||||
editFeed(item: Feed) {
|
||||
this.createModal('FeedForm', { initialFeed: {url: item.url, name: item.name}})
|
||||
},
|
||||
deleteFeed(item: Feed) {
|
||||
qbit.deleteFeed(item.name)
|
||||
this.$store.commit('FETCH_FEEDS')
|
||||
},
|
||||
|
@ -59,5 +71,5 @@ export default {
|
|||
this.createModal('FeedForm')
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -86,7 +86,8 @@
|
|||
"pauseSelected": "Pause selected torrents",
|
||||
"removeSelected": "Remove selected torrents",
|
||||
"openSettings": "Open settings",
|
||||
"searchNew": "Search new torrent"
|
||||
"searchNew": "Search new torrent",
|
||||
"rssArticles": "View RSS feed articles"
|
||||
},
|
||||
"sessionStats": {
|
||||
"tooltip": "Since the last time qBittorrent was restarted"
|
||||
|
@ -137,6 +138,21 @@
|
|||
"action": ""
|
||||
}
|
||||
},
|
||||
"rss": {
|
||||
"title": "RSS Articles",
|
||||
"columnTitle": {
|
||||
"feedName": "Feed Name",
|
||||
"author": "Author",
|
||||
"category": "Category",
|
||||
"date": "Date",
|
||||
"description": "Description",
|
||||
"id": "ID",
|
||||
"link": "Link",
|
||||
"title": "Title",
|
||||
"torrentURL": "Torrent Download URL",
|
||||
"actions": ""
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"tabName": {
|
||||
"VueTorrent": "VueTorrent",
|
||||
|
|
|
@ -83,7 +83,8 @@
|
|||
"pauseSelected": "Mettre en pause les torrents sélectionnés",
|
||||
"removeSelected": "Supprimer les torrents sélectionnés",
|
||||
"openSettings": "Ouvrir les paramètres",
|
||||
"searchNew": "Rechercher un nouveau torrent"
|
||||
"searchNew": "Rechercher un nouveau torrent",
|
||||
"rssArticles": "Voir les articles des flux RSS"
|
||||
},
|
||||
"sessionStats": {
|
||||
"tooltip": "Depuis le dernier redémarrage de qBittorrent"
|
||||
|
@ -133,6 +134,21 @@
|
|||
"action": "Action"
|
||||
}
|
||||
},
|
||||
"rss": {
|
||||
"title": "Articles RSS",
|
||||
"columnTitle": {
|
||||
"feedName": "Nom du Flux",
|
||||
"author": "Auteur",
|
||||
"category": "Catégorie",
|
||||
"date": "Date",
|
||||
"description": "Description",
|
||||
"id": "ID",
|
||||
"link": "Lien",
|
||||
"title": "Titre",
|
||||
"torrentURL": "URL du torrent",
|
||||
"actions": ""
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"tabName": {
|
||||
"VueTorrent": "vuetorrent",
|
||||
|
|
|
@ -1,27 +1,26 @@
|
|||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { mapGetters } from 'vuex'
|
||||
import {Component, Vue} from "vue-property-decorator";
|
||||
import {defineComponent} from "vue";
|
||||
|
||||
@Component({
|
||||
computed: mapGetters(['getTheme'])
|
||||
})
|
||||
export default class General extends Vue {
|
||||
getTheme!: () => string
|
||||
|
||||
get theme() {
|
||||
return this.getTheme()
|
||||
}
|
||||
get isMobile() {
|
||||
return this.$vuetify.breakpoint.smAndDown
|
||||
}
|
||||
|
||||
createModal(name: string, props: any) {
|
||||
const component = {
|
||||
component: name,
|
||||
props,
|
||||
guid: uuidv4()
|
||||
export default defineComponent({
|
||||
computed: {
|
||||
...mapGetters(['getTheme']),
|
||||
theme() {
|
||||
return this.getTheme()
|
||||
},
|
||||
isMobile() {
|
||||
return this.$vuetify.breakpoint.smAndDown
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
createModal(name: string, props?: any) {
|
||||
const component = {
|
||||
component: name,
|
||||
props,
|
||||
guid: uuidv4()
|
||||
}
|
||||
|
||||
this.$store.commit('ADD_MODAL', component)
|
||||
this.$store.commit('ADD_MODAL', component)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -19,6 +19,11 @@ const router = new Router({
|
|||
name: 'settings',
|
||||
component: () => import('./views/Settings.vue')
|
||||
},
|
||||
{
|
||||
path: '/rss',
|
||||
name: 'rss',
|
||||
component: () => import('./views/RssArticles.vue')
|
||||
},
|
||||
{
|
||||
path: '/torrent/:hash',
|
||||
name: 'torrentDetail',
|
||||
|
|
|
@ -5,7 +5,7 @@ import type {
|
|||
AppPreferences,
|
||||
Category,
|
||||
Feed,
|
||||
FeedRule,
|
||||
FeedRule as QbitFeedRule,
|
||||
SearchJob,
|
||||
SearchPlugin,
|
||||
SearchStatus,
|
||||
|
@ -15,8 +15,9 @@ import type {
|
|||
Torrent
|
||||
} from '@/types/qbit/models'
|
||||
import type { MainDataResponse, SearchResultsResponse, TorrentPeersResponse } from '@/types/qbit/responses'
|
||||
import type { AddTorrentPayload, AppPreferencesPayload, LoginPayload } from '@/types/qbit/payloads'
|
||||
import type { AddTorrentPayload, AppPreferencesPayload, CreateFeedPayload, LoginPayload } from '@/types/qbit/payloads'
|
||||
import type { SortOptions } from '@/types/vuetorrent'
|
||||
import type {FeedRule as VtFeedRule} from '@/types/vuetorrent/rss'
|
||||
import type { Priority } from '@/enums/qbit'
|
||||
|
||||
type Parameters = Record<string, any>
|
||||
|
@ -153,25 +154,25 @@ export class QBitApi {
|
|||
|
||||
// RSS
|
||||
|
||||
async createFeed(url: string, path?: string): Promise<void> {
|
||||
async createFeed(payload: CreateFeedPayload): Promise<void> {
|
||||
await this.execute('/rss/addFeed', {
|
||||
url: url,
|
||||
path: path
|
||||
url: payload.url,
|
||||
path: payload.name
|
||||
})
|
||||
}
|
||||
|
||||
async createRule(ruleName: string, ruleDef: FeedRule) {
|
||||
async createRule(rule: VtFeedRule) {
|
||||
return this.execute('/rss/setRule', {
|
||||
ruleName: ruleName,
|
||||
ruleDef: JSON.stringify(ruleDef, ['enabled', 'mustContain', 'mustNotContain', 'useRegex', 'affectedFeeds'])
|
||||
ruleName: rule.name,
|
||||
ruleDef: JSON.stringify(rule, ['enabled', 'mustContain', 'mustNotContain', 'useRegex', 'affectedFeeds'])
|
||||
})
|
||||
}
|
||||
|
||||
async getFeeds(): Promise<Record<string, Feed>> {
|
||||
return this.axios.get('/rss/items').then(res => res.data)
|
||||
async getFeeds(withData: boolean = false): Promise<Record<string, Feed>> {
|
||||
return this.axios.get('/rss/items', { params: {withData}}).then(res => res.data)
|
||||
}
|
||||
|
||||
async getRules(): Promise<Record<string, FeedRule>> {
|
||||
async getRules(): Promise<Record<string, QbitFeedRule>> {
|
||||
return this.axios.get('/rss/rules').then(res => res.data)
|
||||
}
|
||||
|
||||
|
@ -182,7 +183,7 @@ export class QBitApi {
|
|||
})
|
||||
}
|
||||
|
||||
async editRule(ruleName: string, newRuleName: string): Promise<void> {
|
||||
async renameRule(ruleName: string, newRuleName: string): Promise<void> {
|
||||
await this.execute('/rss/renameRule', {
|
||||
ruleName,
|
||||
newRuleName
|
||||
|
|
|
@ -78,7 +78,7 @@ export default {
|
|||
},
|
||||
FETCH_CATEGORIES: async (state: StoreState) => (state.categories = Object.values(await qbit.getCategories())),
|
||||
FETCH_TAGS: async (state: StoreState) => (state.tags = await qbit.getAvailableTags()),
|
||||
FETCH_FEEDS: async (state: StoreState) => (state.rss.feeds = Object.entries(await qbit.getFeeds()).map(([key, value]) => ({ name: key, ...value }))),
|
||||
FETCH_FEEDS: async (state: StoreState) => (state.rss.feeds = Object.entries(await qbit.getFeeds(true)).map(([key, value]) => ({ name: key, ...value }))),
|
||||
FETCH_RULES: async (state: StoreState) => (state.rss.rules = Object.entries(await qbit.getRules()).map(([key, value]) => ({ name: key, ...value }))),
|
||||
FETCH_SEARCH_PLUGINS: async (state: StoreState) => (state.searchPlugins = await qbit.getSearchPlugins()),
|
||||
SET_CURRENT_ITEM_COUNT: (state: StoreState, count: number) => (state.filteredTorrentsCount = count),
|
||||
|
|
|
@ -1,4 +1,11 @@
|
|||
import {FeedArticle} from "@/types/qbit/models"
|
||||
|
||||
export default interface Feed {
|
||||
uid: string
|
||||
url: string
|
||||
title?: string
|
||||
lastBuildDate?: string
|
||||
isLoading?: boolean
|
||||
hasError?: boolean
|
||||
articles?: FeedArticle[]
|
||||
}
|
||||
|
|
11
src/types/qbit/models/FeedArticle.ts
Normal file
11
src/types/qbit/models/FeedArticle.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
export interface FeedArticle {
|
||||
author: string
|
||||
category: string
|
||||
date: string
|
||||
description: string
|
||||
id: string
|
||||
isRead: boolean
|
||||
link: string
|
||||
title: string
|
||||
torrentURL: string
|
||||
}
|
|
@ -12,6 +12,7 @@ import type SearchPlugin from './SearchPlugin'
|
|||
import type SearchJob from './SearchJob'
|
||||
import type SearchStatus from './SearchStatus'
|
||||
import type SearchResult from './SearchResult'
|
||||
import {FeedArticle} from "@/types/qbit/models/FeedArticle"
|
||||
|
||||
type ApplicationVersion = string
|
||||
|
||||
|
@ -26,6 +27,7 @@ export type {
|
|||
TorrentFile,
|
||||
TorrentProperties,
|
||||
FeedRule,
|
||||
FeedArticle,
|
||||
Feed,
|
||||
SearchPlugin,
|
||||
SearchJob,
|
||||
|
|
6
src/types/qbit/payloads/CreateFeedPayload.ts
Normal file
6
src/types/qbit/payloads/CreateFeedPayload.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import type { BasePayload } from '.'
|
||||
|
||||
export default interface CreateFeedPayload extends BasePayload {
|
||||
url: string
|
||||
name?: string
|
||||
}
|
|
@ -2,6 +2,7 @@ import type LoginPayload from './LoginPayload'
|
|||
import type AddTorrentPayload from './AddTorrentPayload'
|
||||
import type PeerLogPayload from './PeerLogPayload'
|
||||
import type { AppPreferencesPayload } from './AppPreferencesPayload'
|
||||
import type CreateFeedPayload from "./CreateFeedPayload"
|
||||
import type BasePayload from './BasePayload'
|
||||
|
||||
export { AppPreferencesPayload, LoginPayload, AddTorrentPayload, PeerLogPayload, BasePayload }
|
||||
export { AppPreferencesPayload, LoginPayload, AddTorrentPayload, PeerLogPayload, BasePayload, CreateFeedPayload }
|
||||
|
|
|
@ -1,5 +1,12 @@
|
|||
import {FeedArticle} from "@/types/qbit/models";
|
||||
|
||||
export default interface Feed {
|
||||
name: string
|
||||
uid?: string
|
||||
url: string
|
||||
title?: string
|
||||
lastBuildDate?: string
|
||||
isLoading?: boolean
|
||||
hasError?: boolean
|
||||
articles?: FeedArticle[]
|
||||
}
|
||||
|
|
6
src/types/vuetorrent/rss/FeedArticle.ts
Normal file
6
src/types/vuetorrent/rss/FeedArticle.ts
Normal file
|
@ -0,0 +1,6 @@
|
|||
import {FeedArticle as QbitFeedArticle} from "@/types/qbit/models"
|
||||
|
||||
export default interface FeedArticle extends QbitFeedArticle {
|
||||
feedName: string
|
||||
parsedDate: Date
|
||||
}
|
5
src/types/vuetorrent/rss/index.ts
Normal file
5
src/types/vuetorrent/rss/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Feed from './Feed'
|
||||
import FeedArticle from './FeedArticle'
|
||||
import FeedRule from './FeedRule'
|
||||
|
||||
export {Feed, FeedArticle, FeedRule}
|
117
src/views/RssArticles.vue
Normal file
117
src/views/RssArticles.vue
Normal file
|
@ -0,0 +1,117 @@
|
|||
<template>
|
||||
<div class="px-1 px-sm-5 background noselect">
|
||||
<v-row no-gutters class="grey--text" align="center" justify="center">
|
||||
<v-col>
|
||||
<h1 style="font-size: 1.6em !important" class="subtitle-1 ml-2">
|
||||
{{ $t('modals.rss.title') }}
|
||||
</h1>
|
||||
</v-col>
|
||||
<v-col class="align-center justify-center">
|
||||
<v-card-actions class="justify-end">
|
||||
<v-btn small elevation="0" @click="close">
|
||||
<v-icon>{{ mdiClose }}</v-icon>
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-row class="ma-0 pa-0">
|
||||
<v-data-table
|
||||
id="articlesTable"
|
||||
:headers="headers"
|
||||
:items="articles"
|
||||
:items-per-page="15"
|
||||
:search="filter"
|
||||
:custom-filter="customFilter"
|
||||
:sort-by.sync="sortBy"
|
||||
:sort-desc.sync="reverse">
|
||||
<template #top>
|
||||
<v-text-field ref="filterRef" v-model="filter" label="Filter" class="mx-4" />
|
||||
</template>
|
||||
<template #[`item.title`]="{ item }">
|
||||
<a :href="item.link" target="_blank" v-text="item.title" />
|
||||
</template>
|
||||
<template #[`item.parsedDate`]="{ item }">
|
||||
{{ item.parsedDate.toLocaleString() }}
|
||||
</template>
|
||||
<template #[`item.actions`]="{ item }">
|
||||
<v-icon @click="downloadTorrent(item)">
|
||||
{{ mdiDownload }}
|
||||
</v-icon>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {General} from "@/mixins"
|
||||
import {mapState} from "vuex"
|
||||
import {defineComponent} from "vue"
|
||||
import {FeedArticle} from "@/types/vuetorrent/rss"
|
||||
import {Feed, FeedRule} from "@/types/vuetorrent"
|
||||
import {mdiClose, mdiDownload} from "@mdi/js";
|
||||
|
||||
type RssState = {feeds: Feed[], rules: FeedRule[]}
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RssArticles',
|
||||
mixins: [General],
|
||||
data() {
|
||||
return {
|
||||
headers: [
|
||||
{text: this.$i18n.t('modals.rss.columnTitle.id'), value: 'id'},
|
||||
{text: this.$i18n.t('modals.rss.columnTitle.title'), value: 'title'},
|
||||
// {text: this.$i18n.t('modals.rss.columnTitle.description'), value: 'description'},
|
||||
{text: this.$i18n.t('modals.rss.columnTitle.category'), value: 'category'},
|
||||
{text: this.$i18n.t('modals.rss.columnTitle.author'), value: 'author'},
|
||||
{text: this.$i18n.t('modals.rss.columnTitle.date'), value: 'parsedDate'},
|
||||
{text: this.$i18n.t('modals.rss.columnTitle.feedName'), value: 'feedName'},
|
||||
// {text: this.$i18n.t('modals.rss.columnTitle.link'), value: 'link'},
|
||||
// {text: this.$i18n.t('modals.rss.columnTitle.torrentURL'), value: 'torrentURL'},
|
||||
{text: this.$i18n.t('modals.rss.columnTitle.actions'), value: 'actions', sortable: false}
|
||||
],
|
||||
filter: '',
|
||||
sortBy: "date",
|
||||
reverse: true,
|
||||
mdiDownload,
|
||||
mdiClose
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.handleKeyboardShortcut)
|
||||
},
|
||||
created() {
|
||||
this.$store.commit('FETCH_FEEDS')
|
||||
},
|
||||
beforeDestroy() {
|
||||
document.removeEventListener('keydown', this.handleKeyboardShortcut)
|
||||
},
|
||||
computed: {
|
||||
...mapState(['rss']),
|
||||
articles(): FeedArticle[] {
|
||||
const articles: FeedArticle[] = [];
|
||||
(this.rss as RssState).feeds.forEach((feed: Feed) => {
|
||||
feed.articles && articles.push(...feed.articles.map(article => ({feedName: feed.name, parsedDate: new Date(article.date), ...article})))
|
||||
})
|
||||
return articles
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.$router.back()
|
||||
},
|
||||
customFilter(value: string, query: string, item?: any): boolean {
|
||||
return (item as FeedArticle).title.toLowerCase().indexOf(query.toLowerCase()) !== -1
|
||||
},
|
||||
downloadTorrent(item: FeedArticle) {
|
||||
this.createModal('AddModal', { initialMagnet: item.torrentURL })
|
||||
},
|
||||
handleKeyboardShortcut(e: KeyboardEvent) {
|
||||
if (e.key === "Escape") {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
|
@ -20,8 +20,7 @@ export default defineConfig(({ command, mode }) => {
|
|||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vue: ['vue', 'vue-router', 'vue-router/composables', 'vuex', 'vuex-persist'],
|
||||
vuetify: ['vuetify']
|
||||
vue: ['vue', 'vue-router', 'vue-router/composables', 'vuex', 'vuex-persist']
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
Loading…
Add table
Reference in a new issue