perf: complete RSS rule settings (#731) @Larsluph

This commit is contained in:
Rémi Marseault 2023-03-22 10:41:07 +01:00 committed by GitHub
parent 521c6e6c7f
commit 36901701c3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 405 additions and 215 deletions

View file

@ -87,10 +87,9 @@
<v-row no-gutters class="flex-gap">
<v-col>
<div class="d-flex flex-column align-center">
<p class="subtitle-1 mb-1">{{ $t('modals.add.contentLayout') }}</p>
<p class="subtitle-1 mb-1">{{ $t('enums.contentLayout.title') }}</p>
<v-select
v-model="contentLayout"
:label="$t('modals.add.contentLayout')"
flat
solo
dense
@ -103,10 +102,9 @@
</v-col>
<v-col>
<div class="d-flex flex-column align-center">
<p class="subtitle-1 mb-1">{{ $t('modals.add.stopCondition') }}</p>
<p class="subtitle-1 mb-1">{{ $t('enums.stopCondition.title') }}</p>
<v-select
v-model="stopCondition"
:label="$t('modals.add.stopCondition')"
flat
solo
dense
@ -196,15 +194,15 @@ export default {
skip_checking: false,
contentLayout: 'Original',
contentLayoutOptions: [
{ text: this.$t('modals.add.contentLayoutOptions.original'), value: AppPreferences.ContentLayout.ORIGINAL },
{ text: this.$t('modals.add.contentLayoutOptions.subfolder'), value: AppPreferences.ContentLayout.SUBFOLDER },
{ text: this.$t('modals.add.contentLayoutOptions.nosubfolder'), value: AppPreferences.ContentLayout.NO_SUBFOLDER }
{ text: this.$t('enums.contentLayout.original'), value: AppPreferences.ContentLayout.ORIGINAL },
{ text: this.$t('enums.contentLayout.subfolder'), value: AppPreferences.ContentLayout.SUBFOLDER },
{ text: this.$t('enums.contentLayout.nosubfolder'), value: AppPreferences.ContentLayout.NO_SUBFOLDER }
],
stopCondition: 'None',
stopConditionOptions: [
{ text: this.$t('modals.add.stopConditionOptions.none'), value: AppPreferences.StopCondition.NONE },
{ text: this.$t('modals.add.stopConditionOptions.metadataReceived'), value: AppPreferences.StopCondition.METADATA_RECEIVED },
{ text: this.$t('modals.add.stopConditionOptions.filesChecked'), value: AppPreferences.StopCondition.FILES_CHECKED }
{ text: this.$t('enums.stopCondition.none'), value: AppPreferences.StopCondition.NONE },
{ text: this.$t('enums.stopCondition.metadataReceived'), value: AppPreferences.StopCondition.METADATA_RECEIVED },
{ text: this.$t('enums.stopCondition.filesChecked'), value: AppPreferences.StopCondition.FILES_CHECKED }
],
autoTMM: true,
sequentialDownload: false,

View file

@ -0,0 +1,73 @@
<template>
<v-dialog v-model="dialog" max-width="500px">
<v-card flat>
<v-container class="pa-0 project done">
<v-card-title class="justify-center">
<v-toolbar flat dense class="transparent">
<v-toolbar-title class="mx-auto">
<h2>{{ $t('modals.matchingArticles.title') }}</h2>
</v-toolbar-title>
<v-btn fab small class="transparent elevation-0" @click="close">
<v-icon>{{ mdiClose }}</v-icon>
</v-btn>
</v-toolbar>
</v-card-title>
<v-card-text class="pb-0">
<v-list subheader>
<template v-for="item in matchingArticles" :key="item">
<v-divider inset v-if="item.type === 'divider'" />
<v-subheader inset v-else-if="item.type === 'subheader'">{{ item.value }}</v-subheader>
<v-list-item v-else class="mb-3">{{ item.value }}</v-list-item>
</template>
</v-list>
</v-card-text>
</v-container>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import qbit from '@/services/qbit'
import { mdiClose } from '@mdi/js'
import { Modal } from '@/mixins'
export default defineComponent({
name: 'MatchingArticles',
mixins: [Modal],
props: {
ruleName: String
},
data() {
return {
matchingArticles: [] as { type: string, value?: string }[],
mdiClose
}
},
async mounted() {
if (this.ruleName === undefined) {
this.close()
return
}
const articles = await qbit.getMatchingArticles(this.ruleName)
for (const feedName in articles) {
const feedArticles = articles[feedName]
if (this.matchingArticles.length > 0)
this.matchingArticles.push({ type: 'divider' })
this.matchingArticles.push({type: 'subheader', value: feedName})
for (const i in feedArticles) {
const article = feedArticles[i]
this.matchingArticles.push({type: 'item', value: article})
}
}
},
methods: {
close() {
this.dialog = false
}
}
})
</script>

View file

@ -1,73 +1,162 @@
<template>
<v-dialog v-model="dialog" max-width="350px">
<v-dialog v-model="dialog" max-width="500px">
<v-card flat>
<v-card-title class="pa-0">
<v-toolbar-title class="ma-4 primarytext--text">
<h3>{{ hasInitialRule ? $t('modals.newRule.titleEdit') : $t('modals.newRule.titleCreate') }}</h3>
</v-toolbar-title>
</v-card-title>
<v-card-text class="pa-2">
<v-form ref="ruleForm">
<v-container>
<v-text-field v-model="rule.name" :label="$t('modals.newRule.name')" required />
</v-container>
<v-container>
<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.affectedFeeds" hide-details :label="item.name" :value="item.url" />
</template>
</v-container>
</v-form>
</v-card-text>
<v-divider />
<v-card-actions class="justify-end">
<v-btn v-if="!hasInitialRule" class="accent white--text elevation-0 px-4" @click="create">
{{ $t('create') }}
</v-btn>
<v-btn v-else class="accent white--text elevation-0 px-4" @click="edit">
{{ $t('edit') }}
</v-btn>
<v-btn class="error white--text elevation-0 px-4" @click="cancel">
{{ $t('cancel') }}
</v-btn>
</v-card-actions>
<v-container class="pa-0 project done">
<v-card-title class="justify-center">
<v-toolbar flat dense class="transparent">
<v-toolbar-title class="mx-auto">
<h2>{{ hasInitialRule ? $t('modals.newRule.titleEdit') : $t('modals.newRule.titleCreate') }}</h2>
</v-toolbar-title>
<v-btn fab small class="transparent elevation-0" @click="close">
<v-icon>{{ mdiClose }}</v-icon>
</v-btn>
</v-toolbar>
</v-card-title>
<v-card-text class="pb-0">
<v-form ref="form" v-model="valid">
<v-container>
<v-text-field v-model="rule.name" :label="$t('modals.newRule.name')" required />
<v-divider />
<v-checkbox hide-details v-model="rule.useRegex" :label="$t('modals.newRule.def.useRegex')" />
<v-text-field hide-details v-model="rule.mustContain" :label="$t('modals.newRule.def.mustContain')" />
<v-text-field hide-details v-model="rule.mustNotContain" :label="$t('modals.newRule.def.mustNotContain')" />
<v-checkbox hide-details v-model="rule.smartFilter" :label="$t('modals.newRule.def.smartFilter')" />
<v-text-field v-model="rule.episodeFilter" :label="$t('modals.newRule.def.episodeFilter')" />
<v-divider />
<v-row>
<p class="subtitle-1 mb-1">{{ $t('modals.newRule.def.assignedCategory') }}</p>
<v-select flat solo dense hide-details background-color="background" class="rounded-xl" v-model="rule.assignedCategory" :items="availableCategories" />
</v-row>
<v-text-field hide-details v-model="rule.savePath" :label="$t('modals.newRule.def.savePath')" />
<v-text-field hide-details v-model="rule.ignoreDays" :label="$t('modals.newRule.def.ignoreDays')" required type="number" min="0" />
<v-text-field disabled :value="lastMatch" :label="$t('modals.newRule.def.lastMatch.title')" />
<v-divider />
<v-row no-gutters class="my-2 flex-gap">
<v-col>
<div class="d-flex flex-column align-center">
<p class="subtitle-1 mb-1">{{ $t('modals.newRule.def.addPaused.title') }}</p>
<v-select
v-model="rule.addPaused"
flat
solo
dense
hide-details
background-color="background"
class="rounded-xl"
:items="addPausedOptions"
/>
</div>
</v-col>
<v-col>
<div class="d-flex flex-column align-center">
<p class="subtitle-1 mb-1">{{ $t('enums.contentLayout.title') }}</p>
<v-select
v-model="rule.torrentContentLayout"
flat
solo
dense
hide-details
background-color="background"
class="rounded-xl"
:items="contentLayoutOptions"
/>
</div>
</v-col>
</v-row>
<v-subheader class="pa-0">
{{ $t('modals.newRule.def.affectedFeeds') }}
</v-subheader>
<v-checkbox v-for="(item, index) in availableFeeds" :key="index" v-model="rule.affectedFeeds" hide-details :label="item.name" :value="item.url" />
</v-container>
</v-form>
</v-card-text>
<v-divider />
<v-card-actions class="justify-end">
<v-btn v-if="!hasInitialRule" class="accent white--text elevation-0 px-4" @click="setRule" :disabled="!valid">
{{ $t('create') }}
</v-btn>
<v-btn v-else class="accent white--text elevation-0 px-4" @click="setRule" :disabled="!valid">
{{ $t('edit') }}
</v-btn>
<v-btn class="error white--text elevation-0 px-4" @click="cancel">
{{ $t('cancel') }}
</v-btn>
</v-card-actions>
</v-container>
</v-card>
</v-dialog>
</template>
<script>
<script lang="ts">
import { defineComponent } from 'vue'
import { mapGetters } from 'vuex'
import qbit from '@/services/qbit'
import { Modal } from '@/mixins'
import { mdiCancel, mdiTagPlus, mdiPencil } from '@mdi/js'
import { mdiClose } from '@mdi/js'
import i18n from '@/plugins/i18n'
import {AppPreferences} from '@/enums/qbit'
import {Category} from "@/types/vuetorrent";
export default {
export default defineComponent({
name: 'RuleForm',
mixins: [Modal],
props: {
initialRule: Object
},
props: ['initialRule'],
data: () => ({
valid: false,
rule: {
name: '',
mustContain: '',
addPaused: null,
affectedFeeds: [],
enabled: true
assignedCategory: '',
enabled: true,
episodeFilter: '',
ignoreDays: 0,
lastMatch: '',
mustContain: '',
mustNotContain: '',
name: '',
savePath: '',
smartFilter: false,
torrentContentLayout: null,
useRegex: false
},
mdiCancel,
mdiTagPlus,
mdiPencil
addPausedOptions: [
{ text: i18n.t('useGlobalSettings'), value: null },
{ text: i18n.t('modals.newRule.def.addPaused.always'), value: true },
{ text: i18n.t('modals.newRule.def.addPaused.never'), value: false }
],
contentLayoutOptions: [
{ text: i18n.t('useGlobalSettings'), value: null },
{ text: i18n.t('enums.contentLayout.original'), value: AppPreferences.ContentLayout.ORIGINAL },
{ text: i18n.t('enums.contentLayout.subfolder'), value: AppPreferences.ContentLayout.SUBFOLDER },
{ text: i18n.t('enums.contentLayout.nosubfolder'), value: AppPreferences.ContentLayout.NO_SUBFOLDER }
],
mdiClose
}),
computed: {
...mapGetters(['getFeeds']),
...mapGetters(['getFeeds', 'getCategories']),
lastMatch() {
if (this.rule.lastMatch === '')
return i18n.t('modals.newRule.def.lastMatch.unknownValue').toString()
const delta = new Date().getTime() - new Date(this.rule.lastMatch).getTime()
return i18n.t('modals.newRule.def.lastMatch.knownValue').toString()
.replace('%1', Math.floor(delta / (1000 * 60 * 60 * 24)).toString())
},
availableFeeds() {
// @ts-expect-error: TS2349: This expression is not callable. Type 'never' has no call signatures.
return this.getFeeds()
},
availableCategories() {
// @ts-expect-error: TS2349: This expression is not callable. Type 'never' has no call signatures.
return this.getCategories().map((c: Category) => c.name)
},
hasInitialRule() {
return !!(this.initialRule && this.initialRule.name)
}
@ -75,25 +164,25 @@ export default {
created() {
this.$store.commit('FETCH_RULES')
if (this.hasInitialRule) {
this.rule = this.initialRule
this.rule = {...this.initialRule}
}
},
methods: {
create() {
qbit.createRule(this.rule)
async setRule() {
if (this.hasInitialRule && this.initialRule.name !== this.rule.name) {
await qbit.renameRule(this.initialRule.name, this.rule.name)
}
await qbit.setRule(this.rule)
this.$toast.success(this.$t('toast.ruleSaved'))
this.cancel()
},
cancel() {
this.$store.commit('FETCH_RULES')
this.dialog = false
this.close()
},
edit() {
qbit.renameRule(this.initialRule.name, this.rule.name)
this.$toast.success(this.$t('toast.ruleSaved'))
this.cancel()
close() {
this.dialog = false
}
}
}
})
</script>
<style></style>

View file

@ -4,7 +4,7 @@
<v-list-item>
<v-select
v-model="settings.torrent_content_layout"
:label="$t('modals.settings.pageDownloads.whenAddTorrent.contentLayout')"
:label="$t('enums.contentLayout.title')"
outlined
dense
small-chips
@ -17,7 +17,7 @@
<v-list-item>
<v-select
v-model="settings.torrent_stop_condition"
:label="$t('modals.settings.pageDownloads.whenAddTorrent.stopCondition')"
:label="$t('enums.stopCondition.title')"
outlined
dense
small-chips
@ -135,14 +135,14 @@ export default {
data() {
return {
contentLayoutOptions: [
{ text: this.$t('modals.add.contentLayoutOptions.original'), value: AppPreferences.ContentLayout.ORIGINAL },
{ text: this.$t('modals.add.contentLayoutOptions.subfolder'), value: AppPreferences.ContentLayout.SUBFOLDER },
{ text: this.$t('modals.add.contentLayoutOptions.nosubfolder'), value: AppPreferences.ContentLayout.NO_SUBFOLDER }
{ text: this.$t('enums.contentLayout.original'), value: AppPreferences.ContentLayout.ORIGINAL },
{ text: this.$t('enums.contentLayout.subfolder'), value: AppPreferences.ContentLayout.SUBFOLDER },
{ text: this.$t('enums.contentLayout.nosubfolder'), value: AppPreferences.ContentLayout.NO_SUBFOLDER }
],
stopConditionOptions: [
{ text: this.$t('modals.add.stopConditionOptions.none'), value: AppPreferences.StopCondition.NONE },
{ text: this.$t('modals.add.stopConditionOptions.metadataReceived'), value: AppPreferences.StopCondition.METADATA_RECEIVED },
{ text: this.$t('modals.add.stopConditionOptions.filesChecked'), value: AppPreferences.StopCondition.FILES_CHECKED }
{ text: this.$t('enums.stopCondition.none'), value: AppPreferences.StopCondition.NONE },
{ text: this.$t('enums.stopCondition.metadataReceived'), value: AppPreferences.StopCondition.METADATA_RECEIVED },
{ text: this.$t('enums.stopCondition.filesChecked'), value: AppPreferences.StopCondition.FILES_CHECKED }
]
}
}

View file

@ -56,6 +56,7 @@ export default defineComponent({
computed: {
...mapGetters(['getFeeds']),
availableFeeds() {
// @ts-expect-error: TS2349: This expression is not callable. Type 'never' has no call signatures.
return this.getFeeds()
}
},

View file

@ -8,6 +8,16 @@
<v-list-item-content>
<v-list-item-title v-text="item.name" />
</v-list-item-content>
<v-list-item-action class="icon">
<v-icon @click="previewMatchingArticles(item.name)">
{{ mdiEye }}
</v-icon>
</v-list-item-action>
<v-list-item-action class="icon">
<v-icon @click="editRule(item)">
{{ mdiPencil }}
</v-icon>
</v-list-item-action>
<v-list-item-action>
<v-icon color="red" @click="deleteRule(item)">
{{ mdiDelete }}
@ -28,7 +38,7 @@
<script>
import { mapGetters } from 'vuex'
import qbit from '@/services/qbit'
import { mdiDelete } from '@mdi/js'
import { mdiEye, mdiPencil, mdiDelete } from '@mdi/js'
import { Tab, General, FullScreenModal } from '@/mixins'
@ -36,6 +46,8 @@ export default {
name: 'Rules',
mixins: [Tab, General, FullScreenModal],
data: () => ({
mdiEye,
mdiPencil,
mdiDelete
}),
computed: {
@ -57,7 +69,19 @@ export default {
},
createRule() {
this.createModal('RuleForm')
},
editRule(item) {
this.createModal('RuleForm', {initialRule: item})
},
previewMatchingArticles(ruleName) {
this.createModal('MatchingArticles', {ruleName})
}
}
}
</script>
<style scoped>
.icon {
margin-left: 16px;
}
</style>

View file

@ -51,6 +51,7 @@
"no": "no",
"filter": "Filter",
"close": "Close",
"useGlobalSettings": "Use Global Settings",
"dashboard": {
"tooltips": {
"toggleSearch": "Toggle Search Filter",
@ -163,11 +164,29 @@
"name": "Name",
"def": {
"mustContain": "Must Contain",
"mustNotContain": "Must Not Contain",
"useRegex": "Use Regular Expressions",
"mustNotContain": "Must Not Contain",
"smartFilter": "Use Smart Episode Filter",
"episodeFilter": "Episode Filter",
"assignedCategory": "Assign Category",
"savePath": "Save to",
"ignoreDays": "Ignore Subsequent Matches for (0 to Disable)",
"lastMatch": {
"title": "Last Match",
"knownValue": "%1 days ago",
"unknownValue": "Unknown"
},
"addPaused": {
"title": "Add Paused",
"always": "Always",
"never": "Never"
},
"affectedFeeds": "Apply Rule to Feeds"
}
},
"matchingArticles": {
"title": "Matching RSS Articles"
},
"pluginManager": {
"title": "Plugin manager"
},
@ -278,9 +297,7 @@
"pageDownloads": {
"subHeaderWhenAddTorrent": "When adding a torrent",
"whenAddTorrent": {
"contentLayout": "Torrent content layout",
"donotAutoStart": "Do not start the download automatically",
"stopCondition": "Torrent stop condition",
"autoDeleteMode": "Delete .torrent files afterwards"
},
"subHeaderPublicSettings": "Public Settings",
@ -480,19 +497,7 @@
"skipHashCheck": "Skip hash check",
"automaticTorrentManagement": "Automatic Torrent Management",
"dropHereForAdd": "Drop here for add",
"oneOrMoreFilesInvalidTorrent": "One or more files are not valid torrents",
"contentLayout": "Content Layout",
"contentLayoutOptions": {
"original": "Original",
"subfolder": "Create Subfolder",
"nosubfolder": "Remove Subfolder"
},
"stopCondition": "Stop Condition",
"stopConditionOptions": {
"none": "None",
"metadataReceived": "Metadata Received",
"filesChecked": "Files Checked"
}
"oneOrMoreFilesInvalidTorrent": "One or more files are not valid torrents"
},
"changeLocation": {
"title": "Change Location"
@ -615,5 +620,19 @@
"copy": "Copy",
"export": "Export Torrent | Export Torrents",
"info": "Show Info"
},
"enums": {
"contentLayout": {
"title": "Torrent content layout",
"original": "Original",
"subfolder": "Create Subfolder",
"nosubfolder": "Remove Subfolder"
},
"stopCondition": {
"title": "Torrent stop condition",
"none": "None",
"metadataReceived": "Metadata Received",
"filesChecked": "Files Checked"
}
}
}

View file

@ -224,9 +224,7 @@
"pageDownloads": {
"subHeaderWhenAddTorrent": "Lors de l'ajout d'un torrent",
"whenAddTorrent": {
"contentLayout": "Disposition du contenu du torrent",
"donotAutoStart": "Ne pas lancer le téléchargement automatiquement",
"stopCondition": "Condition d'arrêt du torrent",
"autoDeleteMode": "Supprimer les fichers .torrent après"
},
"subHeaderPublicSettings": "Paramètres publics",
@ -424,19 +422,7 @@
"skipHashCheck": "Passer la vérification du hash",
"automaticTorrentManagement": "Gestion automatique des torrents",
"dropHereForAdd": "Déposez ici pour ajouter",
"oneOrMoreFilesInvalidTorrent": "Un ou plusieurs fichiers ne sont pas des torrents valides",
"contentLayout": "Disposition des fichiers",
"contentLayoutOptions": {
"original": "Original",
"subfolder": "Créer un sous-dossier",
"nosubfolder": "Supprimer le sous-dossier"
},
"stopCondition": "Condition d'arrêt",
"stopConditionOptions": {
"none": "Aucune",
"metadataReceived": "Metadonnées reçues",
"filesChecked": "Fichiers vérifiés"
}
"oneOrMoreFilesInvalidTorrent": "Un ou plusieurs fichiers ne sont pas des torrents valides"
},
"changeLocation": {
"title": "Changement d'emplacement"
@ -527,5 +513,19 @@
"copy": "Copier",
"export": "Exporter le torrent | Exporter les torrents",
"info": "Propriétés"
},
"enums": {
"contentLayout": {
"title": "Disposition du contenu du torrent",
"original": "Original",
"subfolder": "Créer un sous-dossier",
"nosubfolder": "Supprimer le sous-dossier"
},
"stopCondition": {
"title": "Condition d'arrêt du torrent",
"none": "Aucune",
"metadataReceived": "Metadonnées reçues",
"filesChecked": "Fichiers vérifiés"
}
}
}

View file

@ -151,7 +151,6 @@
"pageDownloads": {
"subHeaderWhenAddTorrent": "Saat menambahkan torrent",
"whenAddTorrent": {
"createSubFolder": "Buat subfolder untuk torrent dengan banyak file",
"donotAutoStart": "Jangan mengunduh secara otomatis"
},
"subHeaderPublicSettings": "Pengaturan Publik",

View file

@ -150,7 +150,6 @@
"pageDownloads": {
"subHeaderWhenAddTorrent": "Torrentを追加したとき",
"whenAddTorrent": {
"createSubFolder": "複数のファイルが有るときはサブフォルダーを作成する",
"donotAutoStart": "自動でダウンロードを開始しない"
},
"subHeaderPublicSettings": "全体の設定",

View file

@ -179,7 +179,6 @@
"pageDownloads": {
"subHeaderWhenAddTorrent": "При добавлении торрента",
"whenAddTorrent": {
"createSubFolder": "Создать подкаталог для торрентов с несколькими файлами",
"donotAutoStart": "Не начинать загрузку автоматически"
},
"subHeaderPublicSettings": "Общие настройки",

View file

@ -278,9 +278,7 @@
"pageDownloads": {
"subHeaderWhenAddTorrent": "Коли додається торент",
"whenAddTorrent": {
"contentLayout": "Оформлення контенту торрента",
"donotAutoStart": "Не запускати завантаження автоматично",
"stopCondition": "Умови зупинки торрентк",
"autoDeleteMode": "Видаляти .torrent файли після завершення"
},
"subHeaderPublicSettings": "Загальні налаштування",
@ -480,19 +478,7 @@
"skipHashCheck": "Пропустити перевірку хешування",
"automaticTorrentManagement": "Автоматичне керування торрентами",
"dropHereForAdd": "Перетягнути для додавання",
"oneOrMoreFilesInvalidTorrent": "Один або кілька файлів не є дійсним торрентом",
"contentLayout": "Макет вмісту",
"contentLayoutOptions": {
"original": "Оригінал",
"subfolder": "Створити підпапку",
"nosubfolder": "Видалити підпапку"
},
"stopCondition": "Умови зупинки",
"stopConditionOptions": {
"none": "Немає",
"metadataReceived": "Метадані отримано",
"filesChecked": "Файли перевірено"
}
"oneOrMoreFilesInvalidTorrent": "Один або кілька файлів не є дійсним торрентом"
},
"changeLocation": {
"title": "Змінити місцезнаходження"
@ -615,5 +601,19 @@
"copy": "Копіювати",
"export": "Експортувати торент | Експортувати торенти",
"info": "Деталі"
},
"enums": {
"contentLayout": {
"title": "Макет вмісту",
"original": "Оригінал",
"subfolder": "Створити підпапку",
"nosubfolder": "Видалити підпапку"
},
"stopCondition": {
"title": "Умови зупинки",
"none": "Немає",
"metadataReceived": "Метадані отримано",
"filesChecked": "Файли перевірено"
}
}
}

View file

@ -206,7 +206,6 @@
"pageDownloads": {
"subHeaderWhenAddTorrent": "Khi thêm một torrent",
"whenAddTorrent": {
"createSubFolder": "Tạo thư mục con cho torrent với nhiều tệp",
"donotAutoStart": "Không tự động bắt đầu tải xuống"
},
"subHeaderPublicSettings": "Cài đặt chung",

View file

@ -237,9 +237,7 @@
"pageDownloads": {
"subHeaderWhenAddTorrent": "当添加种子时",
"whenAddTorrent": {
"contentLayout": "下载内容布局",
"donotAutoStart": "不要开始自动下载",
"stopCondition": "停止条件",
"autoDeleteMode": "随后删除 .torrent 文件"
},
"subHeaderPublicSettings": "公共设置",
@ -439,19 +437,7 @@
"skipHashCheck": "跳过哈希值检查",
"automaticTorrentManagement": "自动种子管理 (ATM)",
"dropHereForAdd": "拖拽到此处即可添加",
"oneOrMoreFilesInvalidTorrent": "存在无效的种子文件",
"contentLayout": "下载内容布局",
"contentLayoutOptions": {
"original": "原始",
"subfolder": "创建子文件夹",
"nosubfolder": "移除子文件夹"
},
"stopCondition": "种子停止条件",
"stopConditionOptions": {
"none": "无",
"metadataReceived": "接受元数据后",
"filesChecked": "检查文件后"
}
"oneOrMoreFilesInvalidTorrent": "存在无效的种子文件"
},
"changeLocation": {
"title": "更改位置"
@ -545,5 +531,19 @@
"copy": "复制",
"export": "导出种子 | 导出种子",
"info": "显示详情"
},
"enums": {
"contentLayout": {
"title": "下载内容布局",
"original": "原始",
"subfolder": "创建子文件夹",
"nosubfolder": "移除子文件夹"
},
"stopCondition": {
"title": "种子停止条件",
"none": "无",
"metadataReceived": "接受元数据后",
"filesChecked": "检查文件后"
}
}
}

View file

@ -237,9 +237,7 @@
"pageDownloads": {
"subHeaderWhenAddTorrent": "當新增種子時",
"whenAddTorrent": {
"contentLayout": "種子內容佈局",
"donotAutoStart": "不要自動開始下載",
"stopCondition": "種子停止條件",
"autoDeleteMode": "事後移除 .torrent 檔案"
},
"subHeaderPublicSettings": "公共設定",
@ -439,19 +437,7 @@
"skipHashCheck": "跳過雜湊值檢查",
"automaticTorrentManagement": "自動種子管理 (ATM)",
"dropHereForAdd": "拖拽至此處新增",
"oneOrMoreFilesInvalidTorrent": "存在無效的種子檔案",
"contentLayout": "內容佈局",
"contentLayoutOptions": {
"original": "原始",
"subfolder": "建立子資料夾",
"nosubfolder": "移除子資料夾"
},
"stopCondition": "停止條件",
"stopConditionOptions": {
"none": "無",
"metadataReceived": "收到元資料後",
"filesChecked": "檢查檔案後"
}
"oneOrMoreFilesInvalidTorrent": "存在無效的種子檔案"
},
"changeLocation": {
"title": "更改位置"
@ -545,5 +531,19 @@
"copy": "複製",
"export": "匯出種子 | 匯出種子",
"info": "顯示詳情"
},
"enums": {
"contentLayout": {
"title": "內容佈局",
"original": "原始",
"subfolder": "建立子資料夾",
"nosubfolder": "移除子資料夾"
},
"stopCondition": {
"title": "停止條件",
"none": "無",
"metadataReceived": "收到元資料後",
"filesChecked": "檢查檔案後"
}
}
}

View file

@ -6,6 +6,7 @@ export default defineComponent({
computed: {
...mapGetters(['getTheme']),
theme() {
// @ts-expect-error: TS2339: Property 'getTheme' does not exist on type 'CreateComponentPublicInstance{}, unknown, {}, {}, {}, ComponentOptionsMixin, ComponentOptionsMixin, {}, {}, {}, false, OptionTypesType{}, {}, {}, {}, {}, {}>, ... 5 more ..., {}>'.
return this.getTheme()
},
isMobile() {

View file

@ -1,37 +1,32 @@
import { mapGetters } from 'vuex'
import { Component, Prop, Vue } from 'vue-property-decorator'
@Component({
computed: mapGetters(['getModalState'])
})
export default class Modal extends Vue {
//data
hndlDialog = true
//props
@Prop() guid!: string
// mapGetters
getModalState!: () => any
// computed
get dialog() {
return this.hndlDialog
}
set dialog(val) {
this.hndlDialog = val
if (!val) this.deleteModal()
}
// methods
deleteModal() {
//this.hndlDialog = false
setTimeout(() => {
this.$store.commit('DELETE_MODAL', this.guid)
}, 300)
}
import { defineComponent } from 'vue'
export default defineComponent({
name: 'Modal',
props: {
guid: String
},
data: () => ({
hndlDialog: true
}),
beforeDestroy() {
this.deleteModal()
},
computed: {
dialog: {
get() {
return this.hndlDialog
},
set(val: boolean) {
this.hndlDialog = val
if (!val) this.deleteModal()
}
}
},
methods: {
deleteModal() {
setTimeout(() => {
this.$store.commit('DELETE_MODAL', this.guid)
}, 300)
}
}
}
})

View file

@ -16,8 +16,7 @@ import type {
} from '@/types/qbit/models'
import type { MainDataResponse, SearchResultsResponse, TorrentPeersResponse } from '@/types/qbit/responses'
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 { FeedRule as VtFeedRule, SortOptions } from '@/types/vuetorrent'
import type { Priority } from '@/enums/qbit'
type Parameters = Record<string, any>
@ -162,10 +161,10 @@ export class QBitApi {
})
}
async createRule(rule: VtFeedRule) {
async setRule(rule: VtFeedRule) {
return this.execute('/rss/setRule', {
ruleName: rule.name,
ruleDef: JSON.stringify(rule, ['enabled', 'mustContain', 'mustNotContain', 'useRegex', 'affectedFeeds'])
ruleDef: JSON.stringify(rule)
})
}
@ -216,6 +215,10 @@ export class QBitApi {
})
}
async getMatchingArticles(ruleName: string): Promise<Record<string, string[]>> {
return this.axios.get('/rss/matchingArticles', {params: {ruleName}}).then(r => r.data)
}
// Post
async addTorrents(params: AddTorrentPayload, torrents: File[]): Promise<void> {

View file

@ -1,6 +1,8 @@
import { AppPreferences } from '@/enums/qbit'
export default interface FeedRule {
/** Add matched torrent in paused mode */
addPaused: boolean
addPaused: boolean | null
/** The feed URLs the rule applied to */
affectedFeeds: string[]
/** Assign category to the torrent */
@ -9,7 +11,7 @@ export default interface FeedRule {
enabled: boolean
/** Episode filter definition */
episodeFilter: string
/** Ignore sunsequent rule matches */
/** Ignore subsequent rule matches */
ignoreDays: number
/** The rule last match time */
lastMatch: string
@ -23,7 +25,8 @@ export default interface FeedRule {
savePath: string
/** Enable smart episode filter */
smartFilter: boolean
torrentContentLayout?: unknown
/** Torrent content layout to use with matched torrent */
torrentContentLayout: AppPreferences.ContentLayout | null
/** Enable regex mode in "mustContain" and "mustNotContain" */
useRegex: boolean
}

View file

@ -1,6 +1,5 @@
import type Category from './Category'
import type Feed from './rss/Feed'
import type FeedRule from './rss/FeedRule'
import type { Feed, FeedArticle, FeedRule } from './rss'
import type SearchStatus from './search/SearchStatus'
import type SearchResult from './search/SearchResult'
import type ModalTemplate from './ModalTemplate'

View file

@ -1,16 +1,5 @@
export default interface FeedRule {
addPaused?: boolean
affectedFeeds?: string[]
assignedCategory?: string
enabled: boolean
episodeFilter?: string
ignoreDays?: number
lastMatch?: string
mustContain?: string
mustNotContain?: string
import { FeedRule as QbitFeedRule } from '@/types/qbit/models'
export default interface FeedRule extends Partial<QbitFeedRule> {
name: string
previouslyMatchedEpisodes?: unknown[]
savePath?: string
smartFilter?: boolean
useRegex?: boolean
}