Apply lint

This commit is contained in:
CzBiX
2020-03-30 23:34:23 +08:00
parent e3ff054ea6
commit 8e935c85b4
38 changed files with 1106 additions and 590 deletions

View File

@@ -6,13 +6,20 @@ module.exports = {
plugins: [
],
extends: [
'plugin:vue/essential',
'plugin:vue/strongly-recommended',
'eslint:recommended',
'@vue/typescript',
'@vue/typescript/recommended',
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'vue/singleline-html-element-content-newline': ['warn', {
ignores: ['pre', 'textarea', 'span', 'v-icon'],
}]
},
parserOptions: {
parser: '@typescript-eslint/parser',
@@ -21,6 +28,7 @@ module.exports = {
{
files: [
'**/__tests__/*.{j,t}s?(x)',
'**/tests/unit/**/*.spec.{j,t}s?(x)',
],
env: {
jest: true,

737
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,8 @@
"devDependencies": {
"@types/jest": "^24.9.1",
"@types/lodash": "^4.14.149",
"@typescript-eslint/eslint-plugin": "^2.25.0",
"@typescript-eslint/parser": "^2.25.0",
"@vue/cli-plugin-babel": "^4.2.3",
"@vue/cli-plugin-eslint": "^4.2.3",
"@vue/cli-plugin-router": "^4.2.3",
@@ -35,10 +37,10 @@
"@vue/cli-plugin-unit-jest": "^4.2.3",
"@vue/cli-plugin-vuex": "^4.2.3",
"@vue/cli-service": "^4.2.3",
"@vue/eslint-config-typescript": "^4.0.0",
"@vue/eslint-config-typescript": "^5.0.2",
"@vue/test-utils": "1.0.0-beta.29",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"eslint": "^6.8.0",
"eslint-plugin-vue": "^6.2.2",
"lint-staged": "^9.5.0",
"sass": "^1.26.3",
"sass-loader": "^8.0.2",

View File

@@ -1,4 +1,5 @@
import Axios, { AxiosInstance, AxiosResponse, AxiosError } from 'axios';
/* eslint-disable @typescript-eslint/camelcase */
import Axios, { AxiosInstance, AxiosResponse } from 'axios';
import { RssNode, RssRule } from '@/types';
class Api {
@@ -193,7 +194,7 @@ class Api {
}).then(Api.handleResponse);
}
public addRssFeed(url: string, path: string = '') {
public addRssFeed(url: string, path = '') {
const params: any = {
url,
path,

View File

@@ -13,12 +13,19 @@
<v-divider />
<v-expansion-panels
class="drawer-footer"
>
<v-expansion-panel
lazy
@input="drawerFooterOpen"
>
<v-expansion-panel lazy @input="drawerFooterOpen">
<v-expansion-panel-header>
<div class="d-flex align-center">
<v-icon class="footer-icon shrink">mdi-information-outline</v-icon>
<span class="footer-title">Status info</span>
<v-icon class="footer-icon shrink">
mdi-information-outline
</v-icon>
<span class="footer-title">
Status info
</span>
</div>
</v-expansion-panel-header>
<v-expansion-panel-content>
@@ -36,8 +43,14 @@
</v-content>
<add-form v-if="preferences" />
<login-form v-if="needAuth" v-model="needAuth" />
<logs-dialog v-if="drawerOptions.showLogs" v-model="drawerOptions.showLogs" />
<login-form
v-if="needAuth"
v-model="needAuth"
/>
<logs-dialog
v-if="drawerOptions.showLogs"
v-model="drawerOptions.showLogs"
/>
<RssDialog
v-if="drawerOptions.showRss"
v-model="drawerOptions.showRss"
@@ -60,10 +73,7 @@
<script lang="ts">
import Vue from 'vue';
import {
mapActions, mapGetters, mapState, mapMutations,
} from 'vuex';
import Axios, { AxiosError } from 'axios';
import { mapGetters, mapState, mapMutations } from 'vuex';
import GlobalDialog from './components/GlobalDialog.vue';
import GlobalSnackBar from './components/GlobalSnackBar.vue';

View File

@@ -63,7 +63,10 @@
</v-row>
<v-row no-gutters>
<template v-if="showMore">
<v-col cols="12" sm="6">
<v-col
cols="12"
sm="6"
>
<v-combobox
:label="$t('category', 1)"
prepend-icon="mdi-folder"
@@ -74,7 +77,10 @@
@input="setParams('category', $event.value)"
/>
</v-col>
<v-col cols="12" sm="6">
<v-col
cols="12"
sm="6"
>
<v-checkbox
prepend-icon="mdi-file-tree"
:label="$t('label.create_subfolder')"
@@ -83,14 +89,20 @@
/>
</v-col>
</template>
<v-col cols="12" sm="6">
<v-col
cols="12"
sm="6"
>
<v-checkbox
v-model="autoStart"
:label="$t('label.start_torrent')"
prepend-icon="mdi-play-pause"
/>
</v-col>
<v-col cols="12" sm="6">
<v-col
cols="12"
sm="6"
>
<v-checkbox
prepend-icon="mdi-progress-check"
:label="$t('label.skip_hash_check')"
@@ -99,7 +111,10 @@
/>
</v-col>
<template v-if="showMore">
<v-col cols="12" sm="6">
<v-col
cols="12"
sm="6"
>
<v-checkbox
:label="$t('label.in_sequential_order')"
prepend-icon="mdi-sort-descending"
@@ -107,7 +122,10 @@
@change="setParams('sequentialDownload', $event.value)"
/>
</v-col>
<v-col cols="12" sm="6">
<v-col
cols="12"
sm="6"
>
<v-checkbox
prepend-icon="mdi-ray-start-end"
:label="$t('label.first_and_last_pieces_first')"
@@ -126,9 +144,18 @@
/>
</v-card-text>
<v-card-actions>
<v-btn text @click="showMore = !showMore" v-text="showMore ? $t('less') : $t('more')" />
<v-btn
text
@click="showMore = !showMore"
v-text="showMore ? $t('less') : $t('more')"
/>
<v-spacer />
<v-btn text @click="dialog = false">{{ $t('cancel') }}</v-btn>
<v-btn
text
@click="dialog = false"
>
{{ $t('cancel') }}
</v-btn>
<v-btn
text
@click="submit"
@@ -149,11 +176,11 @@ import { isNil } from 'lodash';
import Vue from 'vue';
import { mapState } from 'vuex';
import { tr } from '@/locale';
import api from '../Api';
import Component from 'vue-class-component';
import { Watch } from 'vue-property-decorator';
/* eslint-disable @typescript-eslint/camelcase */
const defaultParams = {
urls: null,
category: null,
@@ -163,6 +190,7 @@ const defaultParams = {
sequentialDownload: false,
firstLastPiecePrio: false,
};
/* eslint-enable @typescript-eslint/camelcase */
@Component({
computed: {
@@ -189,9 +217,9 @@ export default class AddForm extends Vue {
pasteUrl!: string | null
prefs!: any
$refs!: {
form: any,
file: any,
fileZone: HTMLElement,
form: any;
file: any;
fileZone: HTMLElement;
}
get params() {
@@ -213,6 +241,7 @@ export default class AddForm extends Vue {
created() {
defaultParams.paused = this.prefs.start_paused_enabled;
/* eslint-disable-next-line @typescript-eslint/camelcase */
defaultParams.root_path = this.prefs.create_subfolder_enabled;
this.showMore = !this.phoneLayout;
}
@@ -301,7 +330,7 @@ export default class AddForm extends Vue {
}
@Watch('files')
onFilesChange(v: FileList) {
onFilesChange() {
this.$refs.form.validate();
}
}

View File

@@ -41,7 +41,11 @@
:group="child"
/>
</template>
<v-list-item v-else :key="item.title" @click="item.click ? item.click() : null">
<v-list-item
v-else
:key="item.title"
@click="item.click ? item.click() : null"
>
<v-list-item-icon>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-icon>
@@ -58,14 +62,14 @@
<script lang="ts">
import { sortBy, sumBy, defaultTo, isUndefined } from 'lodash';
import Vue from 'vue';
import { mapState, mapMutations, mapGetters } from 'vuex';
import { mapGetters } from 'vuex';
import { tr } from '@/locale';
import { Torrent, Category } from '@/types';
import FilterGroup from './drawer/FilterGroup.vue';
import api from '../Api';
import { formatSize } from '../filters';
import { SiteMap, StateType, AllStateTypes } from '../consts';
import { SiteMap, StateType } from '../consts';
import Component from 'vue-class-component';
import { Prop, Emit } from 'vue-property-decorator';
@@ -113,24 +117,18 @@ const stateList = [
];
interface MenuItem {
icon: string,
'icon-alt'?: string,
title: string,
model?: boolean,
select?: string,
click?: () => void,
children?: MenuChildrenItem[],
icon: string;
'icon-alt'?: string;
title: string;
model?: boolean;
select?: string;
click?: () => void;
children?: MenuChildrenItem[];
}
interface MenuChildrenItem extends MenuItem {
key: string | null,
append?: string,
}
interface Data {
tr: any,
basicItems: MenuItem[],
endItems: MenuItem[],
key: string | null;
append?: string;
}
@Component({

View File

@@ -1,130 +1,180 @@
<template>
<div
class="footer d-flex"
:class="topLayoutClass"
v-if="isDataReady">
<div
class="d-flex shrink"
:class="phoneLayout ? 'flex-column' : 'align-center'"
v-if="app"
class="footer d-flex"
:class="topLayoutClass"
v-if="isDataReady"
>
<div v-if="!phoneLayout">
<v-tooltip top>
<template v-slot:activator="{ on }">
<span v-on="on">
qBittorrent {{ app.version }}
<div
class="d-flex shrink"
:class="phoneLayout ? 'flex-column' : 'align-center'"
v-if="app"
>
<div v-if="!phoneLayout">
<v-tooltip top>
<template v-slot:activator="{ on }">
<span v-on="on">
qBittorrent {{ app.version }}
</span>
</template>
<span>
API version: {{ app.apiVersion }}
</span>
</template>
</v-tooltip>
</div>
<v-divider
vertical
class="mx-2"
v-if="!phoneLayout"
/>
<div class="icon-label">
<v-icon>mdi-sprout</v-icon>
{{ allTorrents.length }} [{{ totalSize | formatSize }}]
</div>
<v-divider
vertical
class="mx-2"
v-if="!phoneLayout"
/>
<div class="icon-label">
<v-icon>mdi-nas</v-icon>
{{ info.free_space_on_disk | formatSize }}
</div>
<v-divider
vertical
class="mx-2"
v-if="!phoneLayout"
/>
<div
class="icon-label"
v-if="!phoneLayout"
>
<v-icon class="icon-upload-download">
mdi-swap-vertical-bold
</v-icon>
<span>
API version: {{ app.apiVersion }}
{{ info.alltime_dl | formatSize }}/{{ info.alltime_ul | formatSize }}
</span>
</v-tooltip>
</div>
</div>
<v-divider vertical class="mx-2" v-if="!phoneLayout"/>
<div class="icon-label">
<v-icon>mdi-sprout</v-icon>
{{ allTorrents.length }} [{{ totalSize | formatSize }}]
</div>
<v-divider vertical class="mx-2" v-if="!phoneLayout"/>
<div class="icon-label">
<v-icon>mdi-nas</v-icon>
{{ info.free_space_on_disk | formatSize }}
</div>
<v-divider vertical class="mx-2" v-if="!phoneLayout"/>
<div class="icon-label" v-if="!phoneLayout">
<v-icon class="icon-upload-download">mdi-swap-vertical-bold</v-icon>
<span>
{{ info.alltime_dl | formatSize }}/{{ info.alltime_ul | formatSize }}
</span>
</div>
</div>
<div
class="d-flex shrink"
:class="phoneLayout ? 'flex-column' : 'align-center'"
v-if="info"
>
<div v-if="!phoneLayout" class="icon-label">
<v-icon>mdi-access-point-network</v-icon>
{{ info.dht_nodes }} nodes
</div>
<v-divider vertical class="mx-2" v-if="!phoneLayout"/>
<div class="icon-label">
<v-tooltip top>
<template v-slot:activator="{ on }">
<v-icon
v-on="on"
:color="info.connection_status | connectionIconColor"
>mdi-{{ info.connection_status | connectionIcon }}</v-icon>
<span v-if="phoneLayout">
<div
class="d-flex shrink"
:class="phoneLayout ? 'flex-column' : 'align-center'"
v-if="info"
>
<div
v-if="!phoneLayout"
class="icon-label"
>
<v-icon>mdi-access-point-network</v-icon>
{{ info.dht_nodes }} nodes
</div>
<v-divider
vertical
class="mx-2"
v-if="!phoneLayout"
/>
<div class="icon-label">
<v-tooltip top>
<template v-slot:activator="{ on }">
<v-icon
v-on="on"
:color="info.connection_status | connectionIconColor"
>
mdi-{{ info.connection_status | connectionIcon }}
</v-icon>
<span v-if="phoneLayout">
Network {{ info.connection_status }}
</span>
</template>
<span>
Network {{ info.connection_status }}
</span>
</template>
</v-tooltip>
</div>
<v-divider
vertical
class="mx-2"
v-if="!phoneLayout"
/>
<div class="icon-label">
<v-switch
v-if="phoneLayout"
hide-details
:value="speedLimited"
@change="toggleSpeedLimitsMode"
label="Alternative speed limits"
class="mt-0 pt-0 speed-switch"
>
<template v-slot:prepend>
<v-icon
v-bind="speedModeBind"
>
mdi-speedometer
</v-icon>
</template>
</v-switch>
<v-tooltip
top
v-else
>
<template v-slot:activator="{ on }">
<v-icon
v-on="on"
v-bind="speedModeBind"
@click="toggleSpeedLimitsMode"
>
mdi-speedometer
</v-icon>
</template>
<span>
Alternative speed limits {{ speedLimited ? 'enabled' : 'disabled' }}
</span>
</v-tooltip>
</div>
<v-divider
vertical
class="mx-2"
v-if="!phoneLayout"
/>
<div class="icon-label">
<v-icon
:color=" info.dl_info_speed > 0 ? 'success' : null"
>
mdi-download
</v-icon>
<span>
Network {{ info.connection_status }}
{{ info.dl_info_speed | formatSize }}/s
<template v-if="info.dl_rate_limit">
({{ info.dl_rate_limit | formatSize }}/s)
</template>
<template v-if="!phoneLayout">
[{{ info.dl_info_data | formatSize }}]
</template>
</span>
</v-tooltip>
</div>
<v-divider vertical class="mx-2" v-if="!phoneLayout"/>
<div class="icon-label">
<v-switch
v-if="phoneLayout"
hide-details
:value="speedLimited"
@change="toggleSpeedLimitsMode"
label="Alternative speed limits"
class="mt-0 pt-0 speed-switch"
>
<template v-slot:prepend>
<v-icon
v-bind="speedModeBind"
>mdi-speedometer</v-icon>
</template>
</v-switch>
<v-tooltip top v-else>
<template v-slot:activator="{ on }">
<v-icon
v-on="on"
v-bind="speedModeBind"
@click="toggleSpeedLimitsMode"
>mdi-speedometer</v-icon>
</template>
</div>
<v-divider
vertical
class="mx-2"
v-if="!phoneLayout"
/>
<div class="icon-label">
<v-icon
:color=" info.up_info_speed > 0 ? 'warning' : null"
>
mdi-upload
</v-icon>
<span>
Alternative speed limits {{ speedLimited ? 'enabled' : 'disabled' }}
{{ info.up_info_speed | formatSize }}/s
<template v-if="info.up_rate_limit">
({{ info.up_rate_limit | formatSize }}/s)
</template>
<template v-if="!phoneLayout">
[{{ info.up_info_data | formatSize }}]
</template>
</span>
</v-tooltip>
</div>
<v-divider vertical class="mx-2" v-if="!phoneLayout"/>
<div class="icon-label">
<v-icon
:color=" info.dl_info_speed > 0 ? 'success' : null"
>mdi-download</v-icon>
<span>
{{ info.dl_info_speed | formatSize }}/s
<template v-if="info.dl_rate_limit">
({{ info.dl_rate_limit | formatSize}}/s)
</template>
<template v-if="!phoneLayout">
[{{ info.dl_info_data | formatSize }}]
</template>
</span>
</div>
<v-divider vertical class="mx-2" v-if="!phoneLayout"/>
<div class="icon-label">
<v-icon
:color=" info.up_info_speed > 0 ? 'warning' : null"
>mdi-upload</v-icon>
<span>
{{ info.up_info_speed | formatSize }}/s
<template v-if="info.up_rate_limit">
({{ info.up_rate_limit | formatSize}}/s)
</template>
<template v-if="!phoneLayout">
[{{ info.up_info_data | formatSize }}]
</template>
</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts">

View File

@@ -35,15 +35,11 @@
</template>
<script lang="ts">
import Vue from 'vue';
import {
computed, ref, watch, Ref,
} from '@vue/composition-api';
import { computed, ref, watch } from '@vue/composition-api';
import { tr } from '@/locale';
import { DialogType, DialogConfig } from '@/store/types';
import { useMutations, useState } from '@/store';
import { timeout } from '@/utils';
const BUTTONS = {
[DialogType.Alert]: [

View File

@@ -21,9 +21,6 @@
<script>
import { useMutations, useState } from '@/store';
import { tr } from '@/locale';
import { timeout } from '@/utils';
export default {
setup() {
const mutations = useMutations(['closeSnackBar']);
@@ -48,7 +45,6 @@ export default {
}
return {
tr,
config,
changed,
clickBtn,

View File

@@ -1,7 +1,14 @@
<template>
<v-dialog v-model="value" persistent width="25em">
<v-dialog
v-model="value"
persistent
width="25em"
>
<v-card>
<v-toolbar dark color="primary">
<v-toolbar
dark
color="primary"
>
<v-toolbar-title>{{ $t('login') }}</v-toolbar-title>
</v-toolbar>
<v-card-text>
@@ -9,9 +16,11 @@
ref="form"
v-model="valid"
>
<div class="pa-0"
@keyup.enter.capture="submit"
v-bind="{ [`grid-list-${$vuetify.breakpoint.name}`]: true }">
<div
class="pa-0"
@keyup.enter.capture="submit"
v-bind="{ [`grid-list-${$vuetify.breakpoint.name}`]: true }"
>
<v-text-field
v-model="params.username"
prepend-icon="mdi-account"

View File

@@ -151,7 +151,9 @@
<v-icon :color="row.item.state | stateColor">
{{ row.item.state | stateIcon }}
</v-icon>
<span class="torrent-title">{{ row.item.name }}</span>
<span class="torrent-title">
{{ row.item.name }}
</span>
</td>
<td>{{ row.item.size | formatSize }}</td>
<td>
@@ -215,9 +217,7 @@ import ConfirmSetCategoryDialog from './dialogs/ConfirmSetCategoryDialog.vue'
import EditTrackerDialog from './dialogs/EditTrackerDialog.vue'
import InfoDialog from './dialogs/InfoDialog.vue'
import api from '../Api'
import { formatSize, formatDuration } from '../filters'
import { torrentIsState } from '../utils'
import { StateType } from '../consts'
import { formatSize } from '../filters'
import { DialogType, TorrentFilter, ConfigPayload, DialogConfig, SnackBarConfig } from '../store/types'
import Component from 'vue-class-component'
import { Torrent, Category } from '../types'

View File

@@ -1,5 +1,10 @@
<template>
<v-dialog :value="true" @input="closeDialog" :fullscreen="phoneLayout" width="40em">
<v-dialog
:value="true"
@input="closeDialog"
:fullscreen="phoneLayout"
width="40em"
>
<v-card>
<v-card-title
class="headline grey lighten-4"
@@ -10,7 +15,10 @@
<v-card-text class="pb-0">
{{ $t('dialog.delete_torrents.msg') }}
<ol class="torrents pt-6">
<li v-for="(row, i) in torrents" :key="i">
<li
v-for="(row, i) in torrents"
:key="i"
>
{{ row.name }}
</li>
</ol>
@@ -30,7 +38,12 @@
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="closeDialog">{{ $t('cancel') }}</v-btn>
<v-btn
text
@click="closeDialog"
>
{{ $t('cancel') }}
</v-btn>
<v-btn
@click="submit"
color="warning"

View File

@@ -1,5 +1,10 @@
<template>
<v-dialog :value="true" @input="closeDialog" :fullscreen="phoneLayout" width="40em">
<v-dialog
:value="true"
@input="closeDialog"
:fullscreen="phoneLayout"
width="40em"
>
<v-card>
<v-card-title
class="headline grey lighten-4"
@@ -15,7 +20,10 @@
{{ $t('dialog.set_category.reset') }}
</template>
<ol class="torrents pt-6">
<li v-for="(row, i) in torrents" :key="i">
<li
v-for="(row, i) in torrents"
:key="i"
>
{{ row.name }}
</li>
</ol>
@@ -25,12 +33,17 @@
v-model="moveSameNamed"
prepend-icon="mdi-file-multiple"
class="mt-0"
:label="$t('dialog.set_category.also.move_same_name_torrents', sameNamedTorrents.length)"
:label="$t('dialog.set_category.also_move_same_name_torrents', sameNamedTorrents.length)"
/>
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="closeDialog">{{ $t('cancel') }}</v-btn>
<v-btn
text
@click="closeDialog"
>
{{ $t('cancel') }}
</v-btn>
<v-btn
@click="submit"
color="warning"

View File

@@ -16,11 +16,26 @@
<v-card-text class="pa-0">
<v-stepper v-model="step">
<v-stepper-header>
<v-stepper-step :complete="step > 1" step="1">Search</v-stepper-step>
<v-stepper-step
:complete="step > 1"
step="1"
>
Search
</v-stepper-step>
<v-divider />
<v-stepper-step :complete="step > 2" step="2">Preview</v-stepper-step>
<v-stepper-step
:complete="step > 2"
step="2"
>
Preview
</v-stepper-step>
<v-divider />
<v-stepper-step :complete="step > 3" step="3">Result</v-stepper-step>
<v-stepper-step
:complete="step > 3"
step="3"
>
Result
</v-stepper-step>
</v-stepper-header>
<v-stepper-items>
<v-stepper-content step="1">
@@ -41,7 +56,10 @@
<v-stepper-content step="2">
{{ toEdit.length }} torrent(s) to update.
<ol class="torrents pt-6">
<li v-for="(row, i) in toEdit" :key="i">
<li
v-for="(row, i) in toEdit"
:key="i"
>
{{ row.name }}
<br>
{{ row.origUrl }}
@@ -69,7 +87,9 @@
@click="back"
v-if="step < 3"
v-text="step == 1 ? 'Cancel' : 'Back'"
>Back</v-btn>
>
Back
</v-btn>
<v-btn
@click="foward"
color="warning"

View File

@@ -108,7 +108,7 @@ import Trackers from './Trackers.vue';
import Peers from './Peers.vue';
import Panel from './Panel.vue';
import Component from 'vue-class-component';
import { Prop, Emit, Watch, PropSync } from 'vue-property-decorator';
import { Prop, Emit, PropSync } from 'vue-property-decorator';
import { Torrent } from '../../types';
@Component({

View File

@@ -20,7 +20,11 @@
v-if="!logs.length"
/>
<ol class="logs caption">
<li v-for="(row, i) in logs" :key="i" :class="row.type | typeColor">
<li
v-for="(row, i) in logs"
:key="i"
:class="row.type | typeColor"
>
[{{ row.type | formatType }} {{ row.timestamp / 1000 | formatTimestamp }}]
<span v-html="row.message" />
</li>
@@ -29,14 +33,18 @@
</v-card-text>
<v-card-actions>
<v-spacer />
<v-btn text @click="closeDialog">Close</v-btn>
<v-btn
text
@click="closeDialog"
>
Close
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>
<script lang="ts">
import Vue from 'vue';
import api from '@/Api';
import Component from 'vue-class-component';
import HasTask from '../../mixins/hasTask';

View File

@@ -1,13 +1,19 @@
<template>
<fieldset class="panel" v-if="!single">
<legend v-text="title" />
<div class="inner">
<fieldset
class="panel"
v-if="!single"
>
<legend v-text="title" />
<div class="inner">
<slot />
</div>
</fieldset>
<div
v-else
class="inner"
>
<slot />
</div>
</fieldset>
<div class="inner" v-else>
<slot />
</div>
</template>
<script lang="ts">
@@ -16,7 +22,10 @@ import Vue from 'vue';
export default Vue.extend({
props: {
single: Boolean,
title: String,
title: {
type: String,
required: true,
},
},
});
</script>

View File

@@ -15,16 +15,20 @@
:title="row.item.country"
:alt="codeToFlag(row.item.country_code).char"
:src="codeToFlag(row.item.country_code).url"
/>
>
<template v-else>
{{ codeToFlag(row.item.country_code).char }}
</template>
</template>
{{ row.item.ip }}
<span class="grey--text">:{{ row.item.port }}</span>
<span class="grey--text">
:{{ row.item.port }}
</span>
</td>
<td>{{ row.item.connection }}</td>
<td :title="row.item.flags_desc">{{ row.item.flags }}</td>
<td :title="row.item.flags_desc">
{{ row.item.flags }}
</td>
<td>{{ row.item.client }}</td>
<td>{{ row.item.progress | progress }}</td>
<td>{{ row.item.dl_speed | networkSpeed }}</td>
@@ -40,7 +44,6 @@
<script lang="ts">
import { map, merge, cloneDeep } from 'lodash';
import Vue from 'vue';
import { codeToFlag, isWindows } from '../../utils';
import api from '../../Api';
import { formatSize } from '../../filters';

View File

@@ -12,16 +12,28 @@
<v-icon class="mr-2">mdi-rss-box</v-icon>
<span>RSS</span>
<v-spacer />
<v-btn icon @click="closeDialog">
<v-btn
icon
@click="closeDialog"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<div class="toolbar">
<v-btn icon @click="addRssItem" :title="$t('dialog.rss.add_feed')">
<v-btn
icon
@click="addRssItem"
:title="$t('dialog.rss.add_feed')"
>
<v-icon>mdi-link-plus</v-icon>
</v-btn>
<v-btn icon :disabled="!selectNode" @click="deleteRssItem" :title="$t('delete')">
<v-btn
icon
:disabled="!selectNode"
@click="deleteRssItem"
:title="$t('delete')"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-spacer />
@@ -38,16 +50,22 @@
:label="$t('dialog.rss.auto_download')"
hide-details
/>
<v-btn icon @click="showRulesDialog = true" :title="$t('settings')">
<v-btn
icon
@click="showRulesDialog = true"
:title="$t('settings')"
>
<v-icon>mdi-settings</v-icon>
</v-btn>
</div>
<v-divider />
<div class="content">
<div class="content-inner">
<div v-if="!rssNode" class="loading">
<v-progress-circular indeterminate>
</v-progress-circular>
<div
v-if="!rssNode"
class="loading"
>
<v-progress-circular indeterminate />
</div>
<template v-else>
<div class="rss-items">
@@ -67,7 +85,10 @@
size="22"
width="2"
/>
<v-icon v-else v-text="getRowIcon(row)" />
<v-icon
v-else
v-text="getRowIcon(row)"
/>
</template>
<template v-slot:label="row">
{{ row.item.name }}
@@ -82,17 +103,24 @@
<div class="rss-info">
<p>
{{ $t('title._') }}:
<a v-if="selectItem" target="_blank" :href="selectItem.url">{{ selectItem.title }}</a>
<a
v-if="selectItem"
target="_blank"
:href="selectItem.url"
>{{ selectItem.title }}</a>
</p>
<p>{{ $t('date') }}: {{ (selectItem ? selectItem.lastBuildDate : null) | date }}</p>
</div>
<v-divider/>
<v-divider />
<div class="list-wrapper">
<v-list
v-if="selectItem"
dense
>
<v-list-item-group v-model="selectArticle" color="primary" >
<v-list-item-group
v-model="selectArticle"
color="primary"
>
<v-list-item
v-for="article in selectItem.articles"
:key="article.id"
@@ -100,11 +128,17 @@
>
<v-list-item-content>
<v-list-item-title>
<span :title="article.title" v-text="article.title"/>
<span
:title="article.title"
v-text="article.title"
/>
</v-list-item-title>
</v-list-item-content>
<v-list-item-action>
<v-btn icon @click.stop="downloadTorrent(article)">
<v-btn
icon
@click.stop="downloadTorrent(article)"
>
<v-icon>mdi-download</v-icon>
</v-btn>
</v-list-item-action>
@@ -118,13 +152,22 @@
<div class="rss-info">
<p>
{{ $t('title._') }}:
<a v-if="selectArticle" target="_blank" :href="selectArticle.link">{{ selectArticle.title }}</a>
<a
v-if="selectArticle"
target="_blank"
:href="selectArticle.link"
>{{ selectArticle.title }}</a>
</p>
<p>{{ `${$t('category', 1)}: ${selectArticle ? selectArticle.category: ''}` }}</p>
<p>{{ $t('date') }}: {{ (selectArticle ? selectArticle.date: null) | date }}</p>
</div>
<v-divider/>
<iframe class="iframe" sandbox="allow-same-origin" v-if="selectArticle" v-body="selectArticle.description" />
<v-divider />
<iframe
class="iframe"
sandbox="allow-same-origin"
v-if="selectArticle"
v-body="selectArticle.description"
/>
</div>
</template>
</div>
@@ -132,13 +175,16 @@
</v-card-text>
</v-card>
<RssRulesDialog v-if="showRulesDialog" :rss-node="rssNode" v-model="showRulesDialog"/>
<RssRulesDialog
v-if="showRulesDialog"
:rss-node="rssNode"
v-model="showRulesDialog"
/>
</v-dialog>
</template>
<script lang="ts">
import { get } from 'lodash'
import Vue from 'vue'
import { mapActions, mapMutations, mapState } from 'vuex'
import Component from 'vue-class-component'
import { Prop, Watch, Emit } from 'vue-property-decorator'

View File

@@ -12,31 +12,48 @@
<v-icon class="mr-2">mdi-filter</v-icon>
<span v-text="$t('dialog.rss_rule.title')" />
<v-spacer />
<v-btn icon @click="closeDialog">
<v-btn
icon
@click="closeDialog"
>
<v-icon>mdi-close</v-icon>
</v-btn>
</v-card-title>
<v-card-text>
<div class="toolbar">
<v-btn icon @click="addRssRule" :title="$t('dialog.rss_rule.add_rule')">
<v-btn
icon
@click="addRssRule"
:title="$t('dialog.rss_rule.add_rule')"
>
<v-icon>mdi-plus</v-icon>
</v-btn>
<v-btn icon :disabled="!selectedRuleName" @click="deleteRssRule" :title="$t('delete')">
<v-btn
icon
:disabled="!selectedRuleName"
@click="deleteRssRule"
:title="$t('delete')"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
</div>
<v-divider />
<div class="content">
<div v-if="!rssRules" class="loading">
<v-progress-circular indeterminate>
</v-progress-circular>
<div
v-if="!rssRules"
class="loading"
>
<v-progress-circular indeterminate />
</div>
<template v-else>
<div class="rss-rules">
<v-list
dense
>
<v-list-item-group v-model="selectedRuleName" color="primary" >
<v-list-item-group
v-model="selectedRuleName"
color="primary"
>
<v-list-item
v-for="(value, key) in rssRules"
:key="key"
@@ -58,35 +75,50 @@
<v-divider vertical />
<div class="rule-details">
<v-form class="rule-form">
<p class="form-title" v-text="$t('dialog.rss_rule.rule_settings')" />
<p
class="form-title"
v-text="$t('dialog.rss_rule.rule_settings')"
/>
<v-checkbox dense :label="$t('dialog.rss_rule.use_regex')"
<v-checkbox
dense
:label="$t('dialog.rss_rule.use_regex')"
:disabled="!selectedRule.enabled"
:value="selectedRule.useRegex"
@change="editRule('useRegex', $event)"
/>
<v-text-field dense :label="$t('dialog.rss_rule.must_contain')"
<v-text-field
dense
:label="$t('dialog.rss_rule.must_contain')"
:disabled="!selectedRule.enabled"
:value="selectedRule.mustContain"
@change="editRule('mustContain', $event)"
/>
<v-text-field dense :label="$t('dialog.rss_rule.must_not_contain')"
<v-text-field
dense
:label="$t('dialog.rss_rule.must_not_contain')"
:disabled="!selectedRule.enabled"
:value="selectedRule.mustNotContain"
@change="editRule('mustNotContain', $event)"
/>
<v-text-field dense :label="$t('dialog.rss_rule.episode_filter')"
<v-text-field
dense
:label="$t('dialog.rss_rule.episode_filter')"
:disabled="!selectedRule.enabled"
:value="selectedRule.episodeFilter"
@change="editRule('episodeFilter', $event)"
/>
<v-checkbox dense :label="$t('dialog.rss_rule.smart_episode')"
<v-checkbox
dense
:label="$t('dialog.rss_rule.smart_episode')"
:disabled="!selectedRule.enabled"
:value="selectedRule.smartFilter"
@change="editRule('smartFilter', $event)"
/>
<v-select dense :label="$t('dialog.rss_rule.assign_category')"
<v-select
dense
:label="$t('dialog.rss_rule.assign_category')"
:items="categoryItems"
:disabled="!selectedRule.enabled"
:value="selectedRule.assignedCategory"
@@ -94,9 +126,12 @@
/>
</v-form>
<v-divider/>
<v-divider />
<p class="feeds-title" v-text="$t('dialog.rss_rule.apply_to_feeds')"/>
<p
class="feeds-title"
v-text="$t('dialog.rss_rule.apply_to_feeds')"
/>
<v-list
dense
v-if="selectedRule.enabled"
@@ -132,7 +167,7 @@ import Component from 'vue-class-component';
import { tr } from '@/locale'
import { Prop, Emit, Watch } from 'vue-property-decorator';
import { RssRule, Category, RssNode, RssItem } from '../../types';
import { RssRule, Category, RssNode } from '../../types';
import api from '../../Api';
import { mapActions, mapMutations, mapGetters } from 'vuex';
import { DialogConfig, DialogType, SnackBarConfig } from '../../store/types';

View File

@@ -21,11 +21,10 @@
<script lang="ts">
import { groupBy } from 'lodash';
import Vue from 'vue';
import api from '../../Api';
import BaseTorrentInfo from './baseTorrentInfo'
import Component from 'vue-class-component';
import { Prop, Watch } from 'vue-property-decorator';
import { Prop } from 'vue-property-decorator';
/* eslint-disable camelcase */
interface File {

View File

@@ -78,7 +78,6 @@
<script lang="ts">
import { chunk, countBy } from 'lodash';
import Vue from 'vue';
import api from '../../Api';
import {
formatDuration, formatSize, formatTimestamp, toPrecision,
@@ -135,7 +134,7 @@ export default class TorrentInfo extends BaseTorrentInfo {
{ label: 'Created on', value: prop => formatTimestamp(prop.creation_date) },
{ label: 'Added on', value: prop => formatTimestamp(prop.addition_date) },
{ label: 'Completed on', value: prop => formatTimestamp(prop.completion_date) },
{ label: 'Torrent hash', value: prop => this.torrent.hash },
{ label: 'Torrent hash', value: () => this.torrent.hash },
{ label: 'Save path', value: prop => prop.save_path },
{ label: 'Comment', value: prop => prop.comment },
]

View File

@@ -21,10 +21,9 @@
</template>
<script lang="ts">
import Vue from 'vue';
import api from '../../Api';
import Component from 'vue-class-component';
import { Prop, Watch } from 'vue-property-decorator';
import { Prop } from 'vue-property-decorator';
import BaseTorrentInfo from './baseTorrentInfo';
@Component({

View File

@@ -18,9 +18,15 @@
@click.stop="select(child.key)"
>
<v-list-item-icon>
<v-icon v-if="isFontIcon(child.icon)">{{ child.icon }}</v-icon>
<v-icon v-if="isFontIcon(child.icon)">
{{ child.icon }}
</v-icon>
<div v-else>
<v-img :src="child.icon" width='20px' height="20px" />
<v-img
:src="child.icon"
width="20px"
height="20px"
/>
</div>
</v-list-item-icon>
<v-list-item-content>
@@ -42,25 +48,19 @@
</template>
<script lang="ts">
import Vue, { PropType } from 'vue';
import { mapState, mapMutations } from 'vuex';
import { FilterGroup as types } from '../types';
import Vue from 'vue';
import Component from 'vue-class-component';
import { Prop } from 'vue-property-decorator';
import { Group } from '../types'
interface Data {
model: boolean;
selected: string | null;
}
@Component
export default class FilterGroup extends Vue {
@Prop()
readonly group!: Group
model = false
selected: string | null = null
export default Vue.extend({
props: {
group: Object as PropType<types.Group>,
},
data(): Data {
return {
model: this.group.model,
selected: null,
};
},
created() {
const s = this.$store.getters.config.filter[this.group.select];
if (this.group.children.some(child => child.key === s)) {
@@ -68,22 +68,22 @@ export default Vue.extend({
} else {
this.select(null);
}
},
methods: {
select(key: string | null) {
this.selected = this.selected === key ? null : key;
this.$store.commit('updateConfig', {
key: 'filter',
value: {
[this.group.select]: this.selected,
},
});
},
isFontIcon(icon: string) {
return icon.startsWith('mdi-');
},
},
});
}
select(key: string | null) {
this.selected = this.selected === key ? null : key;
this.$store.commit('updateConfig', {
key: 'filter',
value: {
[this.group.select]: this.selected,
},
});
}
isFontIcon(icon: string) {
return icon.startsWith('mdi-');
}
}
</script>
<style lang="scss" scoped>

View File

@@ -1,16 +1,14 @@
export declare module FilterGroup {
export interface Group {
title: string;
icon: string;
children: Child[];
model: boolean;
select: string;
}
export interface Child {
title: string;
key: string | null;
icon: string;
append: string | null;
}
export interface Group {
title: string;
icon: string;
children: Child[];
model: boolean;
select: string;
}
export interface Child {
title: string;
key: string | null;
icon: string;
append: string | null;
}

View File

@@ -6,7 +6,7 @@ export default class HasTask extends Vue {
destroy?: boolean
call?: CallableFunction
taskId?: number
interval: number = 2000
interval = 2000
setTaskAndRun(call: CallableFunction, interval?: number) {
this.call = call

View File

@@ -7,6 +7,7 @@ Vue.use(Vuetify);
let locale = i18n.locale();
locale = locale === 'zh-CN' ? 'zh-Hans' : locale.split('-', 1)[0];
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { default: translation } = require('vuetify/src/locale/' + locale);
export default new Vuetify({

View File

@@ -1,5 +1,4 @@
function getSiteIcon(name: String) {
// eslint-disable-next-line
function getSiteIcon(name: string) {
return require(`@/assets/site_icons/${name}.png`);
}

View File

@@ -31,7 +31,7 @@ export function loadConfig() {
return JSON.parse(tmp);
}
export const configStore : Module<ConfigState, any> = {
export const configStore: Module<ConfigState, any> = {
state() {
return {
userConfig: loadConfig(),

View File

@@ -2,7 +2,7 @@ import { merge, isString, cloneDeep } from 'lodash'
import { Module } from 'vuex';
import { DialogState } from './types';
export const dialogStore : Module<DialogState, any> = {
export const dialogStore: Module<DialogState, any> = {
state() {
return {
config: null,

View File

@@ -2,7 +2,7 @@ import { cloneDeep, isString } from 'lodash';
import { Module } from 'vuex';
import { SnackBarState } from './types';
export const snackBarStore : Module<SnackBarState, any> = {
export const snackBarStore: Module<SnackBarState, any> = {
state() {
return {
config: null,

View File

@@ -8,9 +8,9 @@ export interface RootState {
}
export interface TorrentFilter {
state: string
category: string
site: string
state: string;
category: string;
site: string;
}
export interface ConfigState {
@@ -18,8 +18,8 @@ export interface ConfigState {
}
export interface ConfigPayload {
key: string,
value: any,
key: string;
value: any;
}
export enum DialogType {
@@ -32,28 +32,28 @@ export enum DialogType {
export interface DialogConfig {
content: {
title?: String,
text: String,
callback?: CallableFunction,
type?: DialogType,
buttons?: any,
title?: string;
text: string;
callback?: CallableFunction;
type?: DialogType;
buttons?: any;
rules?: CallableFunction[],
placeholder?: string,
},
width?: string,
rules?: CallableFunction[];
placeholder?: string;
};
width?: string;
}
export interface DialogState {
config: DialogConfig | null,
config: DialogConfig | null;
}
export interface SnackBarConfig {
text: string,
btnText?: string,
callback?: CallableFunction,
text: string;
btnText?: string;
callback?: CallableFunction;
}
export interface SnackBarState {
config: SnackBarConfig | null,
config: SnackBarConfig | null;
}

View File

@@ -49,9 +49,9 @@ export interface Torrent extends BaseTorrent {
}
export interface Category {
key: string
name: string
savePath?: string
key: string;
name: string;
savePath?: string;
}
export interface ServerState {
@@ -88,45 +88,45 @@ export interface MainData {
}
export interface RssTorrent {
category?: string
comment?: string
date?: string
description?: string
id: string
link: string
title: string
torrentURL: string
category?: string;
comment?: string;
date?: string;
description?: string;
id: string;
link: string;
title: string;
torrentURL: string;
}
export interface RssItem {
articles: RssTorrent[]
hasError: boolean
isLoading: boolean
lastBuildDate: string
title: string
uid: string
url: string
articles: RssTorrent[];
hasError: boolean;
isLoading: boolean;
lastBuildDate: string;
title: string;
uid: string;
url: string;
}
export interface RssNode {
[key: string]: RssNode | RssItem
[key: string]: RssNode | RssItem;
}
export interface RssRule {
enabled: boolean,
mustContain: string,
mustNotContain: string,
useRegex: boolean,
episodeFilter: string,
smartFilter: boolean,
previouslyMatchedEpisodes: string[],
affectedFeeds: string[],
createSubfolder: boolean | null,
ignoreDays: number,
lastMatch: string,
addPaused: boolean | null,
assignedCategory: string,
savepath: string,
enabled: boolean;
mustContain: string;
mustNotContain: string;
useRegex: boolean;
episodeFilter: string;
smartFilter: boolean;
previouslyMatchedEpisodes: string[];
affectedFeeds: string[];
createSubfolder: boolean | null;
ignoreDays: number;
lastMatch: string;
addPaused: boolean | null;
assignedCategory: string;
savepath: string;
}
export interface TorrentProperties {

View File

@@ -2,7 +2,7 @@ import Vuex from 'vuex';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import '@/directives';
import FilterGroup from '@/components/drawer/FilterGroup.vue';
import { FilterGroup as types } from '@/components/types';
import * as types from '@/components/types';
import { mock } from '../utils';
const localVue = createLocalVue();
@@ -101,7 +101,6 @@ test('unselect if can not found children', () => {
const wrapper = mount({
group,
});
const { vm } = wrapper;
expect((wrapper.vm as any).selected).toBeNull();
});

View File

@@ -47,6 +47,7 @@ describe('all torrents getter', () => {
store.replaceState(mockState({
mainData: {
categories: {},
// eslint-disable-next-line @typescript-eslint/camelcase
server_state: undefined as any,
torrents: {
a: mockBaseTorrent({}),

View File

@@ -1,6 +1,4 @@
import {
findSameNamedTorrents, timeout, codeToFlag, sleep,
} from '@/utils';
import { findSameNamedTorrents, codeToFlag, sleep } from '@/utils';
import { mockTorrent } from './utils';
test('timeout', async () => {

View File

@@ -1,6 +1,7 @@
import { Torrent, BaseTorrent } from '@/types';
const emptyBaseTorrent: BaseTorrent = {
/* eslint-disable @typescript-eslint/camelcase */
added_on: 0,
amount_left: 0,
auto_tmm: false,
@@ -43,6 +44,7 @@ const emptyBaseTorrent: BaseTorrent = {
uploaded: 0,
uploaded_session: 0,
upspeed: 0,
/* eslint-enable @typescript-eslint/camelcase */
};
const emptyTorrent: Torrent = Object.assign({}, emptyBaseTorrent, {