mirror of
https://github.com/amtoaer/bili-sync.git
synced 2026-08-28 03:28:05 +08:00
feat: 支持设置快捷订阅的路径默认值 (#502)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
use bili_sync_entity::rule::Rule;
|
use bili_sync_entity::rule::Rule;
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::bilibili::CollectionType;
|
use crate::bilibili::CollectionType;
|
||||||
@@ -91,3 +91,8 @@ pub struct UpdateVideoSourceRequest {
|
|||||||
pub rule: Option<Rule>,
|
pub rule: Option<Rule>,
|
||||||
pub use_dynamic_api: Option<bool>,
|
pub use_dynamic_api: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct DefaultPathRequest {
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use axum::Router;
|
use axum::Router;
|
||||||
use axum::extract::{Extension, Path};
|
use axum::extract::{Extension, Path, Query};
|
||||||
use axum::routing::{get, post, put};
|
use axum::routing::{get, post, put};
|
||||||
use bili_sync_entity::rule::Rule;
|
use bili_sync_entity::rule::Rule;
|
||||||
use bili_sync_entity::*;
|
use bili_sync_entity::*;
|
||||||
@@ -14,19 +14,25 @@ use sea_orm::{ColumnTrait, DatabaseConnection, EntityTrait, QuerySelect, Transac
|
|||||||
use crate::adapter::_ActiveModel;
|
use crate::adapter::_ActiveModel;
|
||||||
use crate::api::error::InnerApiError;
|
use crate::api::error::InnerApiError;
|
||||||
use crate::api::request::{
|
use crate::api::request::{
|
||||||
InsertCollectionRequest, InsertFavoriteRequest, InsertSubmissionRequest, UpdateVideoSourceRequest,
|
DefaultPathRequest, InsertCollectionRequest, InsertFavoriteRequest, InsertSubmissionRequest,
|
||||||
|
UpdateVideoSourceRequest,
|
||||||
};
|
};
|
||||||
use crate::api::response::{
|
use crate::api::response::{
|
||||||
UpdateVideoSourceResponse, VideoSource, VideoSourceDetail, VideoSourcesDetailsResponse, VideoSourcesResponse,
|
UpdateVideoSourceResponse, VideoSource, VideoSourceDetail, VideoSourcesDetailsResponse, VideoSourcesResponse,
|
||||||
};
|
};
|
||||||
use crate::api::wrapper::{ApiError, ApiResponse, ValidatedJson};
|
use crate::api::wrapper::{ApiError, ApiResponse, ValidatedJson};
|
||||||
use crate::bilibili::{BiliClient, Collection, CollectionItem, FavoriteList, Submission};
|
use crate::bilibili::{BiliClient, Collection, CollectionItem, FavoriteList, Submission};
|
||||||
|
use crate::config::{PathSafeTemplate, TEMPLATE};
|
||||||
use crate::utils::rule::FieldEvaluatable;
|
use crate::utils::rule::FieldEvaluatable;
|
||||||
|
|
||||||
pub(super) fn router() -> Router {
|
pub(super) fn router() -> Router {
|
||||||
Router::new()
|
Router::new()
|
||||||
.route("/video-sources", get(get_video_sources))
|
.route("/video-sources", get(get_video_sources))
|
||||||
.route("/video-sources/details", get(get_video_sources_details))
|
.route("/video-sources/details", get(get_video_sources_details))
|
||||||
|
.route(
|
||||||
|
"/video-sources/{type}/default-path",
|
||||||
|
get(get_video_sources_default_path),
|
||||||
|
) // 仅用于前端获取默认路径
|
||||||
.route("/video-sources/{type}/{id}", put(update_video_source))
|
.route("/video-sources/{type}/{id}", put(update_video_source))
|
||||||
.route("/video-sources/{type}/{id}/evaluate", post(evaluate_video_source))
|
.route("/video-sources/{type}/{id}/evaluate", post(evaluate_video_source))
|
||||||
.route("/video-sources/favorites", post(insert_favorite))
|
.route("/video-sources/favorites", post(insert_favorite))
|
||||||
@@ -154,6 +160,20 @@ pub async fn get_video_sources_details(
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_video_sources_default_path(
|
||||||
|
Path(source_type): Path<String>,
|
||||||
|
Query(params): Query<DefaultPathRequest>,
|
||||||
|
) -> Result<ApiResponse<String>, ApiError> {
|
||||||
|
let template_name = match source_type.as_str() {
|
||||||
|
"favorites" => "favorite_default_path",
|
||||||
|
"collections" => "collection_default_path",
|
||||||
|
"submissions" => "submission_default_path",
|
||||||
|
_ => return Err(InnerApiError::BadRequest("Invalid video source type".to_string()).into()),
|
||||||
|
};
|
||||||
|
let (template, params) = (TEMPLATE.load(), serde_json::to_value(params)?);
|
||||||
|
Ok(ApiResponse::ok(template.path_safe_render(template_name, ¶ms)?))
|
||||||
|
}
|
||||||
|
|
||||||
/// 更新视频来源
|
/// 更新视频来源
|
||||||
pub async fn update_video_source(
|
pub async fn update_video_source(
|
||||||
Path((source_type, id)): Path<(String, i32)>,
|
Path((source_type, id)): Path<(String, i32)>,
|
||||||
|
|||||||
@@ -8,7 +8,9 @@ use validator::Validate;
|
|||||||
|
|
||||||
use crate::bilibili::{Credential, DanmakuOption, FilterOption};
|
use crate::bilibili::{Credential, DanmakuOption, FilterOption};
|
||||||
use crate::config::default::{default_auth_token, default_bind_address, default_time_format};
|
use crate::config::default::{default_auth_token, default_bind_address, default_time_format};
|
||||||
use crate::config::item::{ConcurrentLimit, NFOTimeType, SkipOption};
|
use crate::config::item::{
|
||||||
|
ConcurrentLimit, NFOTimeType, SkipOption, default_collection_path, default_favorite_path, default_submission_path,
|
||||||
|
};
|
||||||
use crate::utils::model::{load_db_config, save_db_config};
|
use crate::utils::model::{load_db_config, save_db_config};
|
||||||
|
|
||||||
pub static CONFIG_DIR: LazyLock<PathBuf> =
|
pub static CONFIG_DIR: LazyLock<PathBuf> =
|
||||||
@@ -25,6 +27,12 @@ pub struct Config {
|
|||||||
pub skip_option: SkipOption,
|
pub skip_option: SkipOption,
|
||||||
pub video_name: String,
|
pub video_name: String,
|
||||||
pub page_name: String,
|
pub page_name: String,
|
||||||
|
#[serde(default = "default_favorite_path")]
|
||||||
|
pub favorite_default_path: String,
|
||||||
|
#[serde(default = "default_collection_path")]
|
||||||
|
pub collection_default_path: String,
|
||||||
|
#[serde(default = "default_submission_path")]
|
||||||
|
pub submission_default_path: String,
|
||||||
pub interval: u64,
|
pub interval: u64,
|
||||||
pub upper_path: PathBuf,
|
pub upper_path: PathBuf,
|
||||||
pub nfo_time_type: NFOTimeType,
|
pub nfo_time_type: NFOTimeType,
|
||||||
@@ -98,6 +106,9 @@ impl Default for Config {
|
|||||||
skip_option: SkipOption::default(),
|
skip_option: SkipOption::default(),
|
||||||
video_name: "{{title}}".to_owned(),
|
video_name: "{{title}}".to_owned(),
|
||||||
page_name: "{{bvid}}".to_owned(),
|
page_name: "{{bvid}}".to_owned(),
|
||||||
|
favorite_default_path: default_favorite_path(),
|
||||||
|
collection_default_path: default_collection_path(),
|
||||||
|
submission_default_path: default_submission_path(),
|
||||||
interval: 1200,
|
interval: 1200,
|
||||||
upper_path: CONFIG_DIR.join("upper_face"),
|
upper_path: CONFIG_DIR.join("upper_face"),
|
||||||
nfo_time_type: NFOTimeType::FavTime,
|
nfo_time_type: NFOTimeType::FavTime,
|
||||||
|
|||||||
@@ -12,8 +12,11 @@ pub static TEMPLATE: LazyLock<VersionedCache<handlebars::Handlebars<'static>>> =
|
|||||||
fn create_template(config: &Config) -> Result<handlebars::Handlebars<'static>> {
|
fn create_template(config: &Config) -> Result<handlebars::Handlebars<'static>> {
|
||||||
let mut handlebars = handlebars::Handlebars::new();
|
let mut handlebars = handlebars::Handlebars::new();
|
||||||
handlebars.register_helper("truncate", Box::new(truncate));
|
handlebars.register_helper("truncate", Box::new(truncate));
|
||||||
handlebars.path_safe_register("video", config.video_name.to_owned())?;
|
handlebars.path_safe_register("video", config.video_name.clone())?;
|
||||||
handlebars.path_safe_register("page", config.page_name.to_owned())?;
|
handlebars.path_safe_register("page", config.page_name.clone())?;
|
||||||
|
handlebars.path_safe_register("favorite_default_path", config.favorite_default_path.clone())?;
|
||||||
|
handlebars.path_safe_register("collection_default_path", config.collection_default_path.clone())?;
|
||||||
|
handlebars.path_safe_register("submission_default_path", config.submission_default_path.clone())?;
|
||||||
Ok(handlebars)
|
Ok(handlebars)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,3 +85,15 @@ impl PathSafeTemplate for handlebars::Handlebars<'_> {
|
|||||||
Ok(filenamify(&self.render(name, data)?).replace("__SEP__", std::path::MAIN_SEPARATOR_STR))
|
Ok(filenamify(&self.render(name, data)?).replace("__SEP__", std::path::MAIN_SEPARATOR_STR))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn default_favorite_path() -> String {
|
||||||
|
"收藏夹/{{name}}".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_collection_path() -> String {
|
||||||
|
"合集/{{name}}".to_owned()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn default_submission_path() -> String {
|
||||||
|
"投稿/{{name}}".to_owned()
|
||||||
|
}
|
||||||
|
|||||||
@@ -221,6 +221,10 @@ class ApiClient {
|
|||||||
return this.post<boolean>(`/video-sources/${type}/${id}/evaluate`, null);
|
return this.post<boolean>(`/video-sources/${type}/${id}/evaluate`, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getDefaultPath(type: string, name: string): Promise<ApiResponse<string>> {
|
||||||
|
return this.get<string>(`/video-sources/${type}/default-path`, { name });
|
||||||
|
}
|
||||||
|
|
||||||
async getConfig(): Promise<ApiResponse<Config>> {
|
async getConfig(): Promise<ApiResponse<Config>> {
|
||||||
return this.get<Config>('/config');
|
return this.get<Config>('/config');
|
||||||
}
|
}
|
||||||
@@ -268,6 +272,7 @@ const api = {
|
|||||||
apiClient.updateVideoSource(type, id, request),
|
apiClient.updateVideoSource(type, id, request),
|
||||||
evaluateVideoSourceRules: (type: string, id: number) =>
|
evaluateVideoSourceRules: (type: string, id: number) =>
|
||||||
apiClient.evaluateVideoSourceRules(type, id),
|
apiClient.evaluateVideoSourceRules(type, id),
|
||||||
|
getDefaultPath: (type: string, name: string) => apiClient.getDefaultPath(type, name),
|
||||||
getConfig: () => apiClient.getConfig(),
|
getConfig: () => apiClient.getConfig(),
|
||||||
updateConfig: (config: Config) => apiClient.updateConfig(config),
|
updateConfig: (config: Config) => apiClient.updateConfig(config),
|
||||||
getDashboard: () => apiClient.getDashboard(),
|
getDashboard: () => apiClient.getDashboard(),
|
||||||
|
|||||||
@@ -20,18 +20,18 @@
|
|||||||
| FavoriteWithSubscriptionStatus
|
| FavoriteWithSubscriptionStatus
|
||||||
| CollectionWithSubscriptionStatus
|
| CollectionWithSubscriptionStatus
|
||||||
| UpperWithSubscriptionStatus;
|
| UpperWithSubscriptionStatus;
|
||||||
export let type: 'favorite' | 'collection' | 'upper' = 'favorite';
|
export let type: 'favorites' | 'collections' | 'submissions' = 'favorites';
|
||||||
export let onSubscriptionSuccess: (() => void) | null = null;
|
export let onSubscriptionSuccess: (() => void) | null = null;
|
||||||
|
|
||||||
let dialogOpen = false;
|
let dialogOpen = false;
|
||||||
|
|
||||||
function getIcon() {
|
function getIcon() {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'favorite':
|
case 'favorites':
|
||||||
return HeartIcon;
|
return HeartIcon;
|
||||||
case 'collection':
|
case 'collections':
|
||||||
return FolderIcon;
|
return FolderIcon;
|
||||||
case 'upper':
|
case 'submissions':
|
||||||
return UserIcon;
|
return UserIcon;
|
||||||
default:
|
default:
|
||||||
return VideoIcon;
|
return VideoIcon;
|
||||||
@@ -40,11 +40,11 @@
|
|||||||
|
|
||||||
function getTypeLabel() {
|
function getTypeLabel() {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'favorite':
|
case 'favorites':
|
||||||
return '收藏夹';
|
return '收藏夹';
|
||||||
case 'collection':
|
case 'collections':
|
||||||
return '合集';
|
return '合集';
|
||||||
case 'upper':
|
case 'submissions':
|
||||||
return 'UP 主';
|
return 'UP 主';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
@@ -53,11 +53,11 @@
|
|||||||
|
|
||||||
function getTitle(): string {
|
function getTitle(): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'favorite':
|
case 'favorites':
|
||||||
return (item as FavoriteWithSubscriptionStatus).title;
|
return (item as FavoriteWithSubscriptionStatus).title;
|
||||||
case 'collection':
|
case 'collections':
|
||||||
return (item as CollectionWithSubscriptionStatus).title;
|
return (item as CollectionWithSubscriptionStatus).title;
|
||||||
case 'upper':
|
case 'submissions':
|
||||||
return (item as UpperWithSubscriptionStatus).uname;
|
return (item as UpperWithSubscriptionStatus).uname;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
@@ -66,12 +66,10 @@
|
|||||||
|
|
||||||
function getSubtitle(): string {
|
function getSubtitle(): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'favorite':
|
case 'favorites':
|
||||||
return `uid: ${(item as FavoriteWithSubscriptionStatus).mid}`;
|
return `uid: ${(item as FavoriteWithSubscriptionStatus).mid}`;
|
||||||
case 'collection':
|
case 'collections':
|
||||||
return `uid: ${(item as CollectionWithSubscriptionStatus).mid}`;
|
return `uid: ${(item as CollectionWithSubscriptionStatus).mid}`;
|
||||||
case 'upper':
|
|
||||||
return '';
|
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@@ -79,7 +77,7 @@
|
|||||||
|
|
||||||
function getDescription(): string {
|
function getDescription(): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'upper':
|
case 'submissions':
|
||||||
return (item as UpperWithSubscriptionStatus).sign || '';
|
return (item as UpperWithSubscriptionStatus).sign || '';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
@@ -88,9 +86,9 @@
|
|||||||
|
|
||||||
function isDisabled(): boolean {
|
function isDisabled(): boolean {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'collection':
|
case 'collections':
|
||||||
return (item as CollectionWithSubscriptionStatus).invalid;
|
return (item as CollectionWithSubscriptionStatus).invalid;
|
||||||
case 'upper': {
|
case 'submissions': {
|
||||||
return (item as UpperWithSubscriptionStatus).invalid;
|
return (item as UpperWithSubscriptionStatus).invalid;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -100,9 +98,9 @@
|
|||||||
|
|
||||||
function getDisabledReason(): string {
|
function getDisabledReason(): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'collection':
|
case 'collections':
|
||||||
return '已失效';
|
return '已失效';
|
||||||
case 'upper':
|
case 'submissions':
|
||||||
return '账号已注销';
|
return '账号已注销';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
@@ -111,7 +109,7 @@
|
|||||||
|
|
||||||
function getCount(): number | null {
|
function getCount(): number | null {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'favorite':
|
case 'favorites':
|
||||||
return (item as FavoriteWithSubscriptionStatus).media_count;
|
return (item as FavoriteWithSubscriptionStatus).media_count;
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
@@ -124,7 +122,7 @@
|
|||||||
|
|
||||||
function getAvatarUrl(): string {
|
function getAvatarUrl(): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'upper':
|
case 'submissions':
|
||||||
return (item as UpperWithSubscriptionStatus).face;
|
return (item as UpperWithSubscriptionStatus).face;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
@@ -171,7 +169,7 @@
|
|||||||
? 'opacity-50'
|
? 'opacity-50'
|
||||||
: ''}"
|
: ''}"
|
||||||
>
|
>
|
||||||
{#if avatarUrl && type === 'upper'}
|
{#if avatarUrl && type === 'submissions'}
|
||||||
<img
|
<img
|
||||||
src={avatarUrl}
|
src={avatarUrl}
|
||||||
alt={title}
|
alt={title}
|
||||||
@@ -265,5 +263,3 @@
|
|||||||
|
|
||||||
<!-- 订阅对话框 -->
|
<!-- 订阅对话框 -->
|
||||||
<SubscriptionDialog bind:open={dialogOpen} {item} {type} onSuccess={handleSubscriptionSuccess} />
|
<SubscriptionDialog bind:open={dialogOpen} {item} {type} onSuccess={handleSubscriptionSuccess} />
|
||||||
<!-- 订阅对话框 -->
|
|
||||||
<SubscriptionDialog bind:open={dialogOpen} {item} {type} onSuccess={handleSubscriptionSuccess} />
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { Button } from '$lib/components/ui/button/index.js';
|
import { Button } from '$lib/components/ui/button/index.js';
|
||||||
import { Input } from '$lib/components/ui/input/index.js';
|
import { Input } from '$lib/components/ui/input/index.js';
|
||||||
import { Label } from '$lib/components/ui/label/index.js';
|
import { Label } from '$lib/components/ui/label/index.js';
|
||||||
|
import { toast } from 'svelte-sonner';
|
||||||
import {
|
import {
|
||||||
Sheet,
|
Sheet,
|
||||||
SheetContent,
|
SheetContent,
|
||||||
@@ -10,7 +11,6 @@
|
|||||||
SheetHeader,
|
SheetHeader,
|
||||||
SheetTitle
|
SheetTitle
|
||||||
} from '$lib/components/ui/sheet/index.js';
|
} from '$lib/components/ui/sheet/index.js';
|
||||||
import { toast } from 'svelte-sonner';
|
|
||||||
import api from '$lib/api';
|
import api from '$lib/api';
|
||||||
import type {
|
import type {
|
||||||
FavoriteWithSubscriptionStatus,
|
FavoriteWithSubscriptionStatus,
|
||||||
@@ -22,47 +22,40 @@
|
|||||||
ApiError
|
ApiError
|
||||||
} from '$lib/types';
|
} from '$lib/types';
|
||||||
|
|
||||||
export let open = false;
|
interface Props {
|
||||||
export let item:
|
open: boolean;
|
||||||
| FavoriteWithSubscriptionStatus
|
item:
|
||||||
| CollectionWithSubscriptionStatus
|
| FavoriteWithSubscriptionStatus
|
||||||
| UpperWithSubscriptionStatus
|
| CollectionWithSubscriptionStatus
|
||||||
| null = null;
|
| UpperWithSubscriptionStatus
|
||||||
export let type: 'favorite' | 'collection' | 'upper' = 'favorite';
|
| null;
|
||||||
export let onSuccess: (() => void) | null = null;
|
type: 'favorites' | 'collections' | 'submissions';
|
||||||
|
onSuccess: (() => void) | null;
|
||||||
|
}
|
||||||
|
|
||||||
let customPath = '';
|
let {
|
||||||
let loading = false;
|
open = $bindable(false),
|
||||||
|
item = null,
|
||||||
|
type = 'favorites',
|
||||||
|
onSuccess = null
|
||||||
|
}: Props = $props();
|
||||||
|
|
||||||
|
let customPath = $state('');
|
||||||
|
let loading = $state(false);
|
||||||
|
|
||||||
// 根据类型和 item 生成默认路径
|
// 根据类型和 item 生成默认路径
|
||||||
function generateDefaultPath(): string {
|
async function generateDefaultPath(): Promise<string> {
|
||||||
if (!item) return '';
|
if (!itemTitle) return '';
|
||||||
|
return (await api.getDefaultPath(type, itemTitle)).data;
|
||||||
switch (type) {
|
|
||||||
case 'favorite': {
|
|
||||||
const favorite = item as FavoriteWithSubscriptionStatus;
|
|
||||||
return `收藏夹/${favorite.title}`;
|
|
||||||
}
|
|
||||||
case 'collection': {
|
|
||||||
const collection = item as CollectionWithSubscriptionStatus;
|
|
||||||
return `合集/${collection.title}`;
|
|
||||||
}
|
|
||||||
case 'upper': {
|
|
||||||
const upper = item as UpperWithSubscriptionStatus;
|
|
||||||
return `UP 主/${upper.uname}`;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getTypeLabel(): string {
|
function getTypeLabel(): string {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'favorite':
|
case 'favorites':
|
||||||
return '收藏夹';
|
return '收藏夹';
|
||||||
case 'collection':
|
case 'collections':
|
||||||
return '合集';
|
return '合集';
|
||||||
case 'upper':
|
case 'submissions':
|
||||||
return 'UP 主';
|
return 'UP 主';
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
@@ -73,11 +66,11 @@
|
|||||||
if (!item) return '';
|
if (!item) return '';
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'favorite':
|
case 'favorites':
|
||||||
return (item as FavoriteWithSubscriptionStatus).title;
|
return (item as FavoriteWithSubscriptionStatus).title;
|
||||||
case 'collection':
|
case 'collections':
|
||||||
return (item as CollectionWithSubscriptionStatus).title;
|
return (item as CollectionWithSubscriptionStatus).title;
|
||||||
case 'upper':
|
case 'submissions':
|
||||||
return (item as UpperWithSubscriptionStatus).uname;
|
return (item as UpperWithSubscriptionStatus).uname;
|
||||||
default:
|
default:
|
||||||
return '';
|
return '';
|
||||||
@@ -92,7 +85,7 @@
|
|||||||
let response;
|
let response;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'favorite': {
|
case 'favorites': {
|
||||||
const favorite = item as FavoriteWithSubscriptionStatus;
|
const favorite = item as FavoriteWithSubscriptionStatus;
|
||||||
const request: InsertFavoriteRequest = {
|
const request: InsertFavoriteRequest = {
|
||||||
fid: favorite.fid,
|
fid: favorite.fid,
|
||||||
@@ -101,7 +94,7 @@
|
|||||||
response = await api.insertFavorite(request);
|
response = await api.insertFavorite(request);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'collection': {
|
case 'collections': {
|
||||||
const collection = item as CollectionWithSubscriptionStatus;
|
const collection = item as CollectionWithSubscriptionStatus;
|
||||||
const request: InsertCollectionRequest = {
|
const request: InsertCollectionRequest = {
|
||||||
sid: collection.sid,
|
sid: collection.sid,
|
||||||
@@ -111,7 +104,7 @@
|
|||||||
response = await api.insertCollection(request);
|
response = await api.insertCollection(request);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'upper': {
|
case 'submissions': {
|
||||||
const upper = item as UpperWithSubscriptionStatus;
|
const upper = item as UpperWithSubscriptionStatus;
|
||||||
const request: InsertSubmissionRequest = {
|
const request: InsertSubmissionRequest = {
|
||||||
upper_id: upper.mid,
|
upper_id: upper.mid,
|
||||||
@@ -145,10 +138,20 @@
|
|||||||
open = false;
|
open = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 当对话框打开时重置 path
|
$effect(() => {
|
||||||
$: if (open && item) {
|
if (open && item) {
|
||||||
customPath = generateDefaultPath();
|
generateDefaultPath()
|
||||||
}
|
.then((path) => {
|
||||||
|
customPath = path;
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
toast.error('获取默认路径失败', {
|
||||||
|
description: (error as ApiError).message
|
||||||
|
});
|
||||||
|
customPath = '';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const typeLabel = getTypeLabel();
|
const typeLabel = getTypeLabel();
|
||||||
const itemTitle = getItemTitle();
|
const itemTitle = getItemTitle();
|
||||||
@@ -173,14 +176,14 @@
|
|||||||
<span class="text-muted-foreground text-sm font-medium">{typeLabel}名称:</span>
|
<span class="text-muted-foreground text-sm font-medium">{typeLabel}名称:</span>
|
||||||
<span class="text-sm">{itemTitle}</span>
|
<span class="text-sm">{itemTitle}</span>
|
||||||
</div>
|
</div>
|
||||||
{#if type === 'favorite'}
|
{#if type === 'favorites'}
|
||||||
{@const favorite = item as FavoriteWithSubscriptionStatus}
|
{@const favorite = item as FavoriteWithSubscriptionStatus}
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-muted-foreground text-sm font-medium">视频数量:</span>
|
<span class="text-muted-foreground text-sm font-medium">视频数量:</span>
|
||||||
<span class="text-sm">{favorite.media_count} 个</span>
|
<span class="text-sm">{favorite.media_count} 个</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if type === 'upper'}
|
{#if type === 'submissions'}
|
||||||
{@const upper = item as UpperWithSubscriptionStatus}
|
{@const upper = item as UpperWithSubscriptionStatus}
|
||||||
{#if upper.sign}
|
{#if upper.sign}
|
||||||
<div class="flex items-start gap-2">
|
<div class="flex items-start gap-2">
|
||||||
|
|||||||
@@ -281,6 +281,9 @@ export interface Config {
|
|||||||
skip_option: SkipOption;
|
skip_option: SkipOption;
|
||||||
video_name: string;
|
video_name: string;
|
||||||
page_name: string;
|
page_name: string;
|
||||||
|
favorite_default_path: string;
|
||||||
|
collection_default_path: string;
|
||||||
|
submission_default_path: string;
|
||||||
interval: number;
|
interval: number;
|
||||||
upper_path: string;
|
upper_path: string;
|
||||||
nfo_time_type: string;
|
nfo_time_type: string;
|
||||||
|
|||||||
@@ -77,7 +77,7 @@
|
|||||||
<div style="max-width: 450px; width: 100%;">
|
<div style="max-width: 450px; width: 100%;">
|
||||||
<SubscriptionCard
|
<SubscriptionCard
|
||||||
item={collection}
|
item={collection}
|
||||||
type="collection"
|
type="collections"
|
||||||
onSubscriptionSuccess={handleSubscriptionSuccess}
|
onSubscriptionSuccess={handleSubscriptionSuccess}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@
|
|||||||
<div style="max-width: 450px; width: 100%;">
|
<div style="max-width: 450px; width: 100%;">
|
||||||
<SubscriptionCard
|
<SubscriptionCard
|
||||||
item={favorite}
|
item={favorite}
|
||||||
type="favorite"
|
type="favorites"
|
||||||
onSubscriptionSuccess={handleSubscriptionSuccess}
|
onSubscriptionSuccess={handleSubscriptionSuccess}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -78,7 +78,7 @@
|
|||||||
<div style="max-width: 450px; width: 100%;">
|
<div style="max-width: 450px; width: 100%;">
|
||||||
<SubscriptionCard
|
<SubscriptionCard
|
||||||
item={upper}
|
item={upper}
|
||||||
type="upper"
|
type="submissions"
|
||||||
onSubscriptionSuccess={handleSubscriptionSuccess}
|
onSubscriptionSuccess={handleSubscriptionSuccess}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -191,8 +191,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Separator />
|
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<Label for="backend-auth-token">后端 API 认证Token</Label>
|
<Label for="backend-auth-token">后端 API 认证Token</Label>
|
||||||
@@ -209,6 +207,23 @@
|
|||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="favorite-default-path">收藏夹快捷订阅路径模板</Label>
|
||||||
|
<Input id="favorite-default-path" bind:value={formData.favorite_default_path} />
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="collection-default-path">合集快捷订阅路径模板</Label>
|
||||||
|
<Input id="collection-default-path" bind:value={formData.collection_default_path} />
|
||||||
|
</div>
|
||||||
|
<div class="space-y-2">
|
||||||
|
<Label for="submission-default-path">UP 主投稿快捷订阅路径模板</Label>
|
||||||
|
<Input id="submission-default-path" bind:value={formData.submission_default_path} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
<Switch id="cdn-sorting" bind:checked={formData.cdn_sorting} />
|
<Switch id="cdn-sorting" bind:checked={formData.cdn_sorting} />
|
||||||
|
|||||||
Reference in New Issue
Block a user