feat: add hide video for visitor mode feature
This commit is contained in:
1
backend/drizzle/0007_broad_jasper_sitwell.sql
Normal file
1
backend/drizzle/0007_broad_jasper_sitwell.sql
Normal file
@@ -0,0 +1 @@
|
||||
ALTER TABLE `videos` ADD `visibility` integer DEFAULT 1;
|
||||
826
backend/drizzle/meta/0007_snapshot.json
Normal file
826
backend/drizzle/meta/0007_snapshot.json
Normal file
@@ -0,0 +1,826 @@
|
||||
{
|
||||
"version": "6",
|
||||
"dialect": "sqlite",
|
||||
"id": "107caef6-bda3-4836-b79d-ba3e0107a989",
|
||||
"prevId": "c86dfb86-c8e7-4f13-8523-35b73541e6f0",
|
||||
"tables": {
|
||||
"collection_videos": {
|
||||
"name": "collection_videos",
|
||||
"columns": {
|
||||
"collection_id": {
|
||||
"name": "collection_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"video_id": {
|
||||
"name": "video_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"order": {
|
||||
"name": "order",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {
|
||||
"collection_videos_collection_id_collections_id_fk": {
|
||||
"name": "collection_videos_collection_id_collections_id_fk",
|
||||
"tableFrom": "collection_videos",
|
||||
"tableTo": "collections",
|
||||
"columnsFrom": [
|
||||
"collection_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
},
|
||||
"collection_videos_video_id_videos_id_fk": {
|
||||
"name": "collection_videos_video_id_videos_id_fk",
|
||||
"tableFrom": "collection_videos",
|
||||
"tableTo": "videos",
|
||||
"columnsFrom": [
|
||||
"video_id"
|
||||
],
|
||||
"columnsTo": [
|
||||
"id"
|
||||
],
|
||||
"onDelete": "cascade",
|
||||
"onUpdate": "no action"
|
||||
}
|
||||
},
|
||||
"compositePrimaryKeys": {
|
||||
"collection_videos_collection_id_video_id_pk": {
|
||||
"columns": [
|
||||
"collection_id",
|
||||
"video_id"
|
||||
],
|
||||
"name": "collection_videos_collection_id_video_id_pk"
|
||||
}
|
||||
},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"collections": {
|
||||
"name": "collections",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"name": {
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"continuous_download_tasks": {
|
||||
"name": "continuous_download_tasks",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"subscription_id": {
|
||||
"name": "subscription_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"author_url": {
|
||||
"name": "author_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"author": {
|
||||
"name": "author",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"platform": {
|
||||
"name": "platform",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'active'"
|
||||
},
|
||||
"total_videos": {
|
||||
"name": "total_videos",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"downloaded_count": {
|
||||
"name": "downloaded_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"skipped_count": {
|
||||
"name": "skipped_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"failed_count": {
|
||||
"name": "failed_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"current_video_index": {
|
||||
"name": "current_video_index",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"completed_at": {
|
||||
"name": "completed_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"error": {
|
||||
"name": "error",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"download_history": {
|
||||
"name": "download_history",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"author": {
|
||||
"name": "author",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"source_url": {
|
||||
"name": "source_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"finished_at": {
|
||||
"name": "finished_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"error": {
|
||||
"name": "error",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"video_path": {
|
||||
"name": "video_path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"thumbnail_path": {
|
||||
"name": "thumbnail_path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"total_size": {
|
||||
"name": "total_size",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"video_id": {
|
||||
"name": "video_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"downloaded_at": {
|
||||
"name": "downloaded_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"downloads": {
|
||||
"name": "downloads",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"timestamp": {
|
||||
"name": "timestamp",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"filename": {
|
||||
"name": "filename",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"total_size": {
|
||||
"name": "total_size",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"downloaded_size": {
|
||||
"name": "downloaded_size",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"progress": {
|
||||
"name": "progress",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"speed": {
|
||||
"name": "speed",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'active'"
|
||||
},
|
||||
"source_url": {
|
||||
"name": "source_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"type": {
|
||||
"name": "type",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"settings": {
|
||||
"name": "settings",
|
||||
"columns": {
|
||||
"key": {
|
||||
"name": "key",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"value": {
|
||||
"name": "value",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"subscriptions": {
|
||||
"name": "subscriptions",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"author": {
|
||||
"name": "author",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"author_url": {
|
||||
"name": "author_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"interval": {
|
||||
"name": "interval",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"last_video_link": {
|
||||
"name": "last_video_link",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"last_check": {
|
||||
"name": "last_check",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"download_count": {
|
||||
"name": "download_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 0
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"platform": {
|
||||
"name": "platform",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": "'YouTube'"
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"video_downloads": {
|
||||
"name": "video_downloads",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"source_video_id": {
|
||||
"name": "source_video_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"source_url": {
|
||||
"name": "source_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"platform": {
|
||||
"name": "platform",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"video_id": {
|
||||
"name": "video_id",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"author": {
|
||||
"name": "author",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"status": {
|
||||
"name": "status",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false,
|
||||
"default": "'exists'"
|
||||
},
|
||||
"downloaded_at": {
|
||||
"name": "downloaded_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"deleted_at": {
|
||||
"name": "deleted_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
},
|
||||
"videos": {
|
||||
"name": "videos",
|
||||
"columns": {
|
||||
"id": {
|
||||
"name": "id",
|
||||
"type": "text",
|
||||
"primaryKey": true,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"title": {
|
||||
"name": "title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"author": {
|
||||
"name": "author",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"date": {
|
||||
"name": "date",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"source": {
|
||||
"name": "source",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"source_url": {
|
||||
"name": "source_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"video_filename": {
|
||||
"name": "video_filename",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"thumbnail_filename": {
|
||||
"name": "thumbnail_filename",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"video_path": {
|
||||
"name": "video_path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"thumbnail_path": {
|
||||
"name": "thumbnail_path",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"thumbnail_url": {
|
||||
"name": "thumbnail_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"added_at": {
|
||||
"name": "added_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"created_at": {
|
||||
"name": "created_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": true,
|
||||
"autoincrement": false
|
||||
},
|
||||
"updated_at": {
|
||||
"name": "updated_at",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"part_number": {
|
||||
"name": "part_number",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"total_parts": {
|
||||
"name": "total_parts",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"series_title": {
|
||||
"name": "series_title",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"rating": {
|
||||
"name": "rating",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"description": {
|
||||
"name": "description",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"view_count": {
|
||||
"name": "view_count",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"duration": {
|
||||
"name": "duration",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"tags": {
|
||||
"name": "tags",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"progress": {
|
||||
"name": "progress",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"file_size": {
|
||||
"name": "file_size",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"last_played_at": {
|
||||
"name": "last_played_at",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"subtitles": {
|
||||
"name": "subtitles",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"channel_url": {
|
||||
"name": "channel_url",
|
||||
"type": "text",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false
|
||||
},
|
||||
"visibility": {
|
||||
"name": "visibility",
|
||||
"type": "integer",
|
||||
"primaryKey": false,
|
||||
"notNull": false,
|
||||
"autoincrement": false,
|
||||
"default": 1
|
||||
}
|
||||
},
|
||||
"indexes": {},
|
||||
"foreignKeys": {},
|
||||
"compositePrimaryKeys": {},
|
||||
"uniqueConstraints": {},
|
||||
"checkConstraints": {}
|
||||
}
|
||||
},
|
||||
"views": {},
|
||||
"enums": {},
|
||||
"_meta": {
|
||||
"schemas": {},
|
||||
"tables": {},
|
||||
"columns": {}
|
||||
},
|
||||
"internal": {
|
||||
"indexes": {}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,13 @@
|
||||
"when": 1766528513707,
|
||||
"tag": "0006_bright_swordsman",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"version": "6",
|
||||
"when": 1766548244908,
|
||||
"tag": "0007_broad_jasper_sitwell",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -203,6 +203,7 @@ export const updateVideoDetails = async (
|
||||
const allowedUpdates: any = {};
|
||||
if (updates.title !== undefined) allowedUpdates.title = updates.title;
|
||||
if (updates.tags !== undefined) allowedUpdates.tags = updates.tags;
|
||||
if (updates.visibility !== undefined) allowedUpdates.visibility = updates.visibility;
|
||||
// Add other allowed fields here if needed in the future
|
||||
|
||||
if (Object.keys(allowedUpdates).length === 0) {
|
||||
|
||||
@@ -30,6 +30,7 @@ export const videos = sqliteTable('videos', {
|
||||
lastPlayedAt: integer('last_played_at'), // Timestamp when video was last played
|
||||
subtitles: text('subtitles'), // JSON stringified array of subtitle objects
|
||||
channelUrl: text('channel_url'), // Author channel URL for subscriptions
|
||||
visibility: integer('visibility').default(1), // 1 = visible, 0 = hidden
|
||||
});
|
||||
|
||||
export const collections = sqliteTable('collections', {
|
||||
|
||||
@@ -231,15 +231,20 @@ const GeneralSettings: React.FC<GeneralSettingsProps> = (props) => {
|
||||
</>
|
||||
)}
|
||||
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={visitorMode ?? false}
|
||||
onChange={(e) => handleVisitorModeChange(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label={t('visitorMode') || "Visitor Mode (Read-only)"}
|
||||
/>
|
||||
<Box>
|
||||
<FormControlLabel
|
||||
control={
|
||||
<Switch
|
||||
checked={visitorMode ?? false}
|
||||
onChange={(e) => handleVisitorModeChange(e.target.checked)}
|
||||
/>
|
||||
}
|
||||
label={t('visitorMode') || "Visitor Mode (Read-only)"}
|
||||
/>
|
||||
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', mt: 0.5, ml: 4.5 }}>
|
||||
{t('visitorModeDescription') || "Read-only mode. Hidden videos will not be visible to visitors."}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<PasswordModal
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useCollection } from '../contexts/CollectionContext';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { useSnackbar } from '../contexts/SnackbarContext'; // Added
|
||||
import { useVideo } from '../contexts/VideoContext';
|
||||
import { useCloudStorageUrl } from '../hooks/useCloudStorageUrl';
|
||||
import { useShareVideo } from '../hooks/useShareVideo'; // Added
|
||||
import { Collection, Video } from '../types';
|
||||
@@ -60,6 +61,7 @@ const VideoCard: React.FC<VideoCardProps> = ({
|
||||
// Hooks for share and snackbar
|
||||
const { handleShare } = useShareVideo(video);
|
||||
const { showSnackbar } = useSnackbar();
|
||||
const { updateVideo } = useVideo();
|
||||
|
||||
|
||||
|
||||
@@ -299,6 +301,18 @@ const VideoCard: React.FC<VideoCardProps> = ({
|
||||
await removeFromCollection(video.id);
|
||||
};
|
||||
|
||||
// Handle visibility toggle
|
||||
const handleToggleVisibility = async () => {
|
||||
if (!video.id) return;
|
||||
const newVisibility = (video.visibility ?? 1) === 0 ? 1 : 0;
|
||||
const result = await updateVideo(video.id, { visibility: newVisibility });
|
||||
if (result.success) {
|
||||
showSnackbar(newVisibility === 1 ? t('showVideo') : t('hideVideo'), 'success');
|
||||
} else {
|
||||
showSnackbar(t('error'), 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// Calculate collections that contain THIS video
|
||||
const currentVideoCollections = allCollections.filter(c => c.videos.includes(video.id));
|
||||
|
||||
@@ -544,6 +558,8 @@ const VideoCard: React.FC<VideoCardProps> = ({
|
||||
onAddToCollection={() => setShowCollectionModal(true)}
|
||||
onDelete={(showDeleteButton && onDeleteVideo) ? () => setShowDeleteModal(true) : undefined}
|
||||
isDeleting={isDeleting}
|
||||
onToggleVisibility={handleToggleVisibility}
|
||||
video={video}
|
||||
sx={{
|
||||
color: 'white',
|
||||
bgcolor: 'rgba(0,0,0,0.6)',
|
||||
|
||||
@@ -29,6 +29,7 @@ interface VideoInfoProps {
|
||||
isSubscribed?: boolean;
|
||||
onSubscribe?: () => void;
|
||||
onUnsubscribe?: () => void;
|
||||
onToggleVisibility?: () => void;
|
||||
}
|
||||
|
||||
const VideoInfo: React.FC<VideoInfoProps> = ({
|
||||
@@ -46,7 +47,8 @@ const VideoInfo: React.FC<VideoInfoProps> = ({
|
||||
onTagsUpdate,
|
||||
isSubscribed,
|
||||
onSubscribe,
|
||||
onUnsubscribe
|
||||
onUnsubscribe,
|
||||
onToggleVisibility
|
||||
}) => {
|
||||
const { videoRef, videoResolution } = useVideoResolution(video);
|
||||
const videoUrl = useCloudStorageUrl(video.videoPath, 'video');
|
||||
@@ -108,6 +110,7 @@ const VideoInfo: React.FC<VideoInfoProps> = ({
|
||||
onAddToCollection={onAddToCollection}
|
||||
onDelete={onDelete}
|
||||
isDeleting={isDeleting}
|
||||
onToggleVisibility={onToggleVisibility}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Add, Cast, Delete, Share } from '@mui/icons-material';
|
||||
import { Add, Cast, Delete, Share, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { Button, Menu, MenuItem, Stack, Tooltip, useMediaQuery, useTheme } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||
@@ -14,13 +14,15 @@ interface VideoActionButtonsProps {
|
||||
onAddToCollection: () => void;
|
||||
onDelete: () => void;
|
||||
isDeleting: boolean;
|
||||
onToggleVisibility?: () => void;
|
||||
}
|
||||
|
||||
const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
|
||||
video,
|
||||
onAddToCollection,
|
||||
onDelete,
|
||||
isDeleting
|
||||
isDeleting,
|
||||
onToggleVisibility
|
||||
}) => {
|
||||
const { t } = useLanguage();
|
||||
const { handleShare } = useShareVideo(video);
|
||||
@@ -195,6 +197,18 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
|
||||
</Tooltip>
|
||||
{!visitorMode && (
|
||||
<>
|
||||
{onToggleVisibility && (
|
||||
<Tooltip title={video.visibility === 0 ? t('showVideo') : t('hideVideo')} disableHoverListener={isTouch}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
onClick={onToggleVisibility}
|
||||
sx={{ minWidth: 'auto', p: 1, color: 'text.secondary', borderColor: 'text.secondary', '&:hover': { color: 'primary.main', borderColor: 'primary.main' } }}
|
||||
>
|
||||
{video.visibility === 0 ? <Visibility /> : <VisibilityOff />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={t('addToCollection')} disableHoverListener={isTouch}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
@@ -230,6 +244,8 @@ const VideoActionButtons: React.FC<VideoActionButtonsProps> = ({
|
||||
onAddToCollection={onAddToCollection}
|
||||
onDelete={onDelete}
|
||||
isDeleting={isDeleting}
|
||||
onToggleVisibility={onToggleVisibility}
|
||||
video={video}
|
||||
/>
|
||||
<Menu
|
||||
anchorEl={playerMenuAnchor}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Add, Cast, Delete, MoreVert, Share } from '@mui/icons-material';
|
||||
import { Add, Cast, Delete, MoreVert, Share, Visibility, VisibilityOff } from '@mui/icons-material';
|
||||
import { Button, IconButton, Menu, Stack, Tooltip, useMediaQuery } from '@mui/material';
|
||||
import React, { useState } from 'react';
|
||||
import { useLanguage } from '../../../contexts/LanguageContext';
|
||||
@@ -10,6 +10,8 @@ interface VideoKebabMenuButtonsProps {
|
||||
onAddToCollection: () => void;
|
||||
onDelete?: () => void;
|
||||
isDeleting?: boolean;
|
||||
onToggleVisibility?: () => void;
|
||||
video?: { visibility?: number };
|
||||
sx?: any;
|
||||
}
|
||||
|
||||
@@ -19,6 +21,8 @@ const VideoKebabMenuButtons: React.FC<VideoKebabMenuButtonsProps> = ({
|
||||
onAddToCollection,
|
||||
onDelete,
|
||||
isDeleting = false,
|
||||
onToggleVisibility,
|
||||
video,
|
||||
sx
|
||||
}) => {
|
||||
const { t } = useLanguage();
|
||||
@@ -59,6 +63,11 @@ const VideoKebabMenuButtons: React.FC<VideoKebabMenuButtonsProps> = ({
|
||||
if (onDelete) onDelete();
|
||||
};
|
||||
|
||||
const handleToggleVisibility = () => {
|
||||
handleKebabMenuClose();
|
||||
if (onToggleVisibility) onToggleVisibility();
|
||||
};
|
||||
|
||||
// Close menu on scroll
|
||||
React.useEffect(() => {
|
||||
if (Boolean(kebabMenuAnchor)) {
|
||||
@@ -133,6 +142,18 @@ const VideoKebabMenuButtons: React.FC<VideoKebabMenuButtonsProps> = ({
|
||||
</Tooltip>
|
||||
{!visitorMode && (
|
||||
<>
|
||||
{onToggleVisibility && (
|
||||
<Tooltip title={video?.visibility === 0 ? t('showVideo') : t('hideVideo')} disableHoverListener={isTouch}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="inherit"
|
||||
onClick={handleToggleVisibility}
|
||||
sx={{ minWidth: 'auto', p: 1, color: 'text.secondary', borderColor: 'text.secondary', '&:hover': { color: 'primary.main', borderColor: 'primary.main' } }}
|
||||
>
|
||||
{video?.visibility === 0 ? <Visibility /> : <VisibilityOff />}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={t('addToCollection')} disableHoverListener={isTouch}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import axios from 'axios';
|
||||
import React, { createContext, useContext, useEffect, useRef, useState } from 'react';
|
||||
import React, { createContext, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { Video } from '../types';
|
||||
import { useLanguage } from './LanguageContext';
|
||||
import { useSnackbar } from './SnackbarContext';
|
||||
import { useVisitorMode } from './VisitorModeContext';
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL;
|
||||
|
||||
@@ -48,9 +49,10 @@ export const VideoProvider: React.FC<{ children: React.ReactNode }> = ({ childre
|
||||
const { showSnackbar } = useSnackbar();
|
||||
const { t } = useLanguage();
|
||||
const queryClient = useQueryClient();
|
||||
const { visitorMode } = useVisitorMode();
|
||||
|
||||
// Videos Query
|
||||
const { data: videos = [], isLoading: videosLoading, error: videosError, refetch: refetchVideos } = useQuery({
|
||||
const { data: videosRaw = [], isLoading: videosLoading, error: videosError, refetch: refetchVideos } = useQuery({
|
||||
queryKey: ['videos'],
|
||||
queryFn: async () => {
|
||||
console.log('Fetching videos from:', `${API_URL}/videos`);
|
||||
@@ -67,6 +69,14 @@ export const VideoProvider: React.FC<{ children: React.ReactNode }> = ({ childre
|
||||
retryDelay: 1000,
|
||||
});
|
||||
|
||||
// Filter invisible videos when in visitor mode
|
||||
const videos = useMemo(() => {
|
||||
if (visitorMode) {
|
||||
return videosRaw.filter(video => (video.visibility ?? 1) === 1);
|
||||
}
|
||||
return videosRaw;
|
||||
}, [videosRaw, visitorMode]);
|
||||
|
||||
// Settings Query (tags and showYoutubeSearch)
|
||||
const { data: settingsData } = useQuery({
|
||||
queryKey: ['settings'],
|
||||
|
||||
@@ -78,25 +78,8 @@ const AuthorVideos: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
// Filter videos to only show the first video from each collection
|
||||
const filteredVideos = authorVideos.filter(video => {
|
||||
// If the video is not in any collection, show it
|
||||
const videoCollections = collections.filter(collection =>
|
||||
collection.videos.includes(video.id)
|
||||
);
|
||||
|
||||
if (videoCollections.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For each collection this video is in, check if it's the first video
|
||||
return videoCollections.some(collection => {
|
||||
// Get the first video ID in this collection
|
||||
const firstVideoId = collection.videos[0];
|
||||
// Show this video if it's the first in at least one collection
|
||||
return video.id === firstVideoId;
|
||||
});
|
||||
});
|
||||
// Show all videos for the author (no collection filtering)
|
||||
const filteredVideos = authorVideos;
|
||||
|
||||
return (
|
||||
<Container maxWidth="xl" sx={{ py: 4 }}>
|
||||
|
||||
@@ -21,6 +21,7 @@ import { useCollection } from '../contexts/CollectionContext';
|
||||
import { useLanguage } from '../contexts/LanguageContext';
|
||||
import { useSnackbar } from '../contexts/SnackbarContext';
|
||||
import { useVideo } from '../contexts/VideoContext';
|
||||
import { useVisitorMode } from '../contexts/VisitorModeContext';
|
||||
import { useCloudStorageUrl } from '../hooks/useCloudStorageUrl';
|
||||
import { Collection, Video } from '../types';
|
||||
import { getRecommendations } from '../utils/recommendations';
|
||||
@@ -36,6 +37,7 @@ const VideoPlayer: React.FC = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const { videos, deleteVideo } = useVideo();
|
||||
const { visitorMode } = useVisitorMode();
|
||||
const {
|
||||
collections,
|
||||
addToCollection,
|
||||
@@ -83,7 +85,7 @@ const VideoPlayer: React.FC = () => {
|
||||
retry: false
|
||||
});
|
||||
|
||||
// Handle error redirect
|
||||
// Handle error redirect and invisible videos in visitor mode
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
const timer = setTimeout(() => {
|
||||
@@ -91,7 +93,14 @@ const VideoPlayer: React.FC = () => {
|
||||
}, 3000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [error, navigate]);
|
||||
// In visitor mode, redirect if video is invisible
|
||||
if (visitorMode && video && (video.visibility ?? 1) === 0) {
|
||||
const timer = setTimeout(() => {
|
||||
navigate('/');
|
||||
}, 3000);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [error, navigate, visitorMode, video]);
|
||||
|
||||
// Fetch settings
|
||||
const { data: settings } = useQuery({
|
||||
@@ -454,6 +463,32 @@ const VideoPlayer: React.FC = () => {
|
||||
await tagsMutation.mutateAsync(newTags);
|
||||
};
|
||||
|
||||
// Visibility mutation
|
||||
const visibilityMutation = useMutation({
|
||||
mutationFn: async (visibility: number) => {
|
||||
const response = await axios.put(`${API_URL}/videos/${id}`, { visibility });
|
||||
return response.data;
|
||||
},
|
||||
onSuccess: (data, visibility) => {
|
||||
if (data.success) {
|
||||
queryClient.setQueryData(['video', id], (old: Video | undefined) => old ? { ...old, visibility } : old);
|
||||
queryClient.setQueryData(['videos'], (old: Video[] | undefined) =>
|
||||
old ? old.map(v => v.id === id ? { ...v, visibility } : v) : []
|
||||
);
|
||||
showSnackbar(visibility === 1 ? t('showVideo') : t('hideVideo'), 'success');
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
showSnackbar(t('error'), 'error');
|
||||
}
|
||||
});
|
||||
|
||||
const handleToggleVisibility = async () => {
|
||||
if (!id || !video) return;
|
||||
const newVisibility = video.visibility === 0 ? 1 : 0;
|
||||
await visibilityMutation.mutateAsync(newVisibility);
|
||||
};
|
||||
|
||||
// Subtitle preference mutation
|
||||
const subtitlePreferenceMutation = useMutation({
|
||||
mutationFn: async (enabled: boolean) => {
|
||||
@@ -613,6 +648,7 @@ const VideoPlayer: React.FC = () => {
|
||||
isSubscribed={isSubscribed}
|
||||
onSubscribe={handleSubscribe}
|
||||
onUnsubscribe={handleUnsubscribe}
|
||||
onToggleVisibility={handleToggleVisibility}
|
||||
/>
|
||||
|
||||
{(video.source === 'youtube' || video.source === 'bilibili') && (
|
||||
|
||||
@@ -23,6 +23,7 @@ export interface Video {
|
||||
lastPlayedAt?: number;
|
||||
subtitles?: Array<{ language: string; filename: string; path: string }>;
|
||||
description?: string;
|
||||
visibility?: number; // 1 = visible, 0 = hidden
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -119,6 +119,7 @@ export const ar = {
|
||||
showYoutubeSearch: "عرض نتائج بحث YouTube",
|
||||
visitorMode: "وضع الزائر (للقراءة فقط)",
|
||||
visitorModeReadOnly: "وضع الزائر: للقراءة فقط",
|
||||
visitorModeDescription: "وضع القراءة فقط. لن تكون مقاطع الفيديو المخفية مرئية للزوار.",
|
||||
visitorModePasswordPrompt: "يرجى إدخال كلمة مرور الموقع لتغيير إعدادات وضع الزائر.",
|
||||
cleanupTempFilesSuccess: "تم حذف {count} ملف (ملفات) مؤقت بنجاح.",
|
||||
cleanupTempFilesFailed: "فشل تنظيف الملفات المؤقتة",
|
||||
@@ -243,6 +244,9 @@ export const ar = {
|
||||
exitFullscreen: "خروج من ملء الشاشة",
|
||||
share: "مشاركة",
|
||||
editTitle: "تعديل العنوان",
|
||||
hideVideo: "جعل الفيديو مخفيًا في وضع الزائر",
|
||||
showVideo: "جعل الفيديو مرئيًا في وضع الزائر",
|
||||
toggleVisibility: "تبديل الرؤية",
|
||||
titleUpdated: "تم تحديث العنوان بنجاح",
|
||||
titleUpdateFailed: "فشل تحديث العنوان",
|
||||
refreshThumbnail: "تحديث الصورة المصغرة",
|
||||
@@ -436,8 +440,6 @@ export const ar = {
|
||||
deleteTask: "حذف المهمة",
|
||||
confirmDeleteTask: "هل أنت متأكد أنك تريد حذف سجل المهمة لـ {author}؟ لا يمكن التراجع عن هذا الإجراء.",
|
||||
taskDeleted: "تم حذف المهمة بنجاح",
|
||||
minutes: "دقائق",
|
||||
never: "أبداً",
|
||||
// Instruction Page
|
||||
instructionSection1Title: "1. التنزيل وإدارة المهام",
|
||||
instructionSection1Desc:
|
||||
|
||||
@@ -115,6 +115,7 @@ export const de = {
|
||||
showYoutubeSearch: "YouTube-Suchergebnisse anzeigen",
|
||||
visitorMode: "Besuchermodus (Nur-Lesen)",
|
||||
visitorModeReadOnly: "Besuchermodus: Nur-Lesen",
|
||||
visitorModeDescription: "Nur-Lese-Modus. Ausgeblendete Videos sind für Besucher nicht sichtbar.",
|
||||
visitorModePasswordPrompt: "Bitte geben Sie das Website-Passwort ein, um die Besuchermodus-Einstellungen zu ändern.",
|
||||
cleanupTempFilesSuccess: "Erfolgreich {count} temporäre Datei(en) gelöscht.",
|
||||
cleanupTempFilesFailed: "Fehler beim Bereinigen temporärer Dateien",
|
||||
@@ -238,6 +239,9 @@ export const de = {
|
||||
exitFullscreen: "Vollbild Verlassen",
|
||||
share: "Teilen",
|
||||
editTitle: "Titel Bearbeiten",
|
||||
hideVideo: "Video für Besuchermodus ausblenden",
|
||||
showVideo: "Video für Besuchermodus sichtbar machen",
|
||||
toggleVisibility: "Sichtbarkeit Umschalten",
|
||||
titleUpdated: "Titel erfolgreich aktualisiert",
|
||||
titleUpdateFailed: "Fehler beim Aktualisieren des Titels",
|
||||
refreshThumbnail: "Vorschaubild aktualisieren",
|
||||
@@ -409,8 +413,6 @@ export const de = {
|
||||
deleteTask: "Aufgabe löschen",
|
||||
confirmDeleteTask: "Sind Sie sicher, dass Sie den Aufgaben-Datensatz für {author} löschen möchten? Diese Aktion kann nicht rückgängig gemacht werden.",
|
||||
taskDeleted: "Aufgabe erfolgreich gelöscht",
|
||||
minutes: "Minuten",
|
||||
never: "Nie",
|
||||
// Instruction Page
|
||||
instructionSection1Title: "1. Download & Aufgabenverwaltung",
|
||||
instructionSection1Desc:
|
||||
|
||||
@@ -116,7 +116,10 @@ export const en = {
|
||||
showYoutubeSearch: "Show YouTube Search Results",
|
||||
visitorMode: "Visitor Mode (Read-only)",
|
||||
visitorModeReadOnly: "Visitor mode: Read-only",
|
||||
visitorModePasswordPrompt: "Please enter the website password to change Visitor Mode settings.",
|
||||
visitorModeDescription:
|
||||
"Read-only mode. Hidden videos will not be visible to visitors.",
|
||||
visitorModePasswordPrompt:
|
||||
"Please enter the website password to change Visitor Mode settings.",
|
||||
cleanupTempFilesSuccess: "Successfully deleted {count} temporary file(s).",
|
||||
cleanupTempFilesFailed: "Failed to clean up temporary files",
|
||||
|
||||
@@ -145,18 +148,21 @@ export const en = {
|
||||
apiUrlHelper: "e.g. https://your-alist-instance.com/api/fs/put",
|
||||
token: "Token",
|
||||
publicUrl: "Public URL",
|
||||
publicUrlHelper: "Public domain for accessing files (e.g., https://your-cloudflare-tunnel-domain.com). If set, this will be used instead of the API URL for file access.",
|
||||
publicUrlHelper:
|
||||
"Public domain for accessing files (e.g., https://your-cloudflare-tunnel-domain.com). If set, this will be used instead of the API URL for file access.",
|
||||
uploadPath: "Upload Path",
|
||||
cloudDrivePathHelper: "Directory path in cloud drive, e.g. /mytube-uploads",
|
||||
scanPaths: "Scan Paths",
|
||||
scanPathsHelper: "One path per line. Videos will be scanned from these paths. If empty, will use upload path. Example:\n/a/Movies\n/b/Documentaries",
|
||||
scanPathsHelper:
|
||||
"One path per line. Videos will be scanned from these paths. If empty, will use upload path. Example:\n/a/Movies\n/b/Documentaries",
|
||||
cloudDriveNote:
|
||||
"After enabling this feature, newly downloaded videos will be automatically uploaded to cloud storage and local files will be deleted. Videos will be played from cloud storage via proxy.",
|
||||
testing: "Testing...",
|
||||
testConnection: "Test Connection",
|
||||
sync: "Sync",
|
||||
syncToCloud: "Two-way Sync",
|
||||
syncWarning: "This operation will upload local videos to cloud and scan cloud storage for new files. Local files will be deleted after upload.",
|
||||
syncWarning:
|
||||
"This operation will upload local videos to cloud and scan cloud storage for new files. Local files will be deleted after upload.",
|
||||
syncing: "Syncing...",
|
||||
syncCompleted: "Sync Completed",
|
||||
syncFailed: "Sync failed",
|
||||
@@ -241,6 +247,9 @@ export const en = {
|
||||
exitFullscreen: "Exit Fullscreen",
|
||||
share: "Share",
|
||||
editTitle: "Edit Title",
|
||||
hideVideo: "Make Video Hidden for Visitor Mode",
|
||||
showVideo: "Make Video Visible for Visitor Mode",
|
||||
toggleVisibility: "Toggle Visibility",
|
||||
titleUpdated: "Title updated successfully",
|
||||
titleUpdateFailed: "Failed to update title",
|
||||
thumbnailRefreshed: "Thumbnail refreshed successfully",
|
||||
@@ -256,7 +265,8 @@ export const en = {
|
||||
openInExternalPlayer: "Open in external player",
|
||||
playWith: "Play with...",
|
||||
deleteAllFilteredVideos: "Delete All Filtered Videos",
|
||||
confirmDeleteFilteredVideos: "Are you sure you want to delete {count} videos filtered by the selected tags?",
|
||||
confirmDeleteFilteredVideos:
|
||||
"Are you sure you want to delete {count} videos filtered by the selected tags?",
|
||||
deleteFilteredVideosSuccess: "Successfully deleted {count} videos.",
|
||||
deletingVideos: "Deleting videos...",
|
||||
|
||||
@@ -295,7 +305,8 @@ export const en = {
|
||||
unknownAuthor: "Unknown",
|
||||
noVideosForAuthor: "No videos found for this author.",
|
||||
deleteAuthor: "Delete Author",
|
||||
deleteAuthorConfirmation: "Are you sure you want to delete author {author}? This will delete all videos associated with this author.",
|
||||
deleteAuthorConfirmation:
|
||||
"Are you sure you want to delete author {author}? This will delete all videos associated with this author.",
|
||||
authorDeletedSuccessfully: "Author deleted successfully",
|
||||
failedToDeleteAuthor: "Failed to delete author",
|
||||
|
||||
@@ -423,10 +434,12 @@ export const en = {
|
||||
taskStatusCancelled: "Cancelled",
|
||||
downloaded: "Downloaded",
|
||||
cancelTask: "Cancel Task",
|
||||
confirmCancelTask: "Are you sure you want to cancel the download task for {author}?",
|
||||
confirmCancelTask:
|
||||
"Are you sure you want to cancel the download task for {author}?",
|
||||
taskCancelled: "Task cancelled successfully",
|
||||
deleteTask: "Delete Task",
|
||||
confirmDeleteTask: "Are you sure you want to delete the task record for {author}? This action cannot be undone.",
|
||||
confirmDeleteTask:
|
||||
"Are you sure you want to delete the task record for {author}? This action cannot be undone.",
|
||||
taskDeleted: "Task deleted successfully",
|
||||
// Instruction Page
|
||||
instructionSection1Title: "1. Download & Task Management",
|
||||
|
||||
@@ -129,6 +129,7 @@ export const es = {
|
||||
showYoutubeSearch: "Mostrar resultados de búsqueda de YouTube",
|
||||
visitorMode: "Modo Visitante (Solo lectura)",
|
||||
visitorModeReadOnly: "Modo visitante: Solo lectura",
|
||||
visitorModeDescription: "Modo de solo lectura. Los videos ocultos no serán visibles para los visitantes.",
|
||||
visitorModePasswordPrompt: "Por favor, introduzca la contraseña del sitio web para cambiar la configuración del modo visitante.",
|
||||
cleanupTempFilesSuccess:
|
||||
"Se eliminaron exitosamente {count} archivo(s) temporal(es).",
|
||||
@@ -261,6 +262,9 @@ export const es = {
|
||||
exitFullscreen: "Salir de Pantalla Completa",
|
||||
share: "Compartir",
|
||||
editTitle: "Editar Título",
|
||||
hideVideo: "Hacer Video Oculto para Modo Visitante",
|
||||
showVideo: "Hacer Video Visible para Modo Visitante",
|
||||
toggleVisibility: "Alternar Visibilidad",
|
||||
titleUpdated: "Título actualizado exitosamente",
|
||||
titleUpdateFailed: "Error al actualizar el título",
|
||||
refreshThumbnail: "Actualizar miniatura",
|
||||
@@ -424,8 +428,6 @@ export const es = {
|
||||
deleteTask: "Eliminar tarea",
|
||||
confirmDeleteTask: "¿Estás seguro de que quieres eliminar el registro de tarea para {author}? Esta acción no se puede deshacer.",
|
||||
taskDeleted: "Tarea eliminada exitosamente",
|
||||
minutes: "minutos",
|
||||
never: "Nunca",
|
||||
// Instruction Page
|
||||
instructionSection1Title: "1. Descarga y Gestión de Tareas",
|
||||
instructionSection1Desc:
|
||||
|
||||
@@ -128,7 +128,10 @@ export const fr = {
|
||||
showYoutubeSearch: "Afficher les résultats de recherche YouTube",
|
||||
visitorMode: "Mode Visiteur (Lecture seule)",
|
||||
visitorModeReadOnly: "Mode visiteur : Lecture seule",
|
||||
visitorModePasswordPrompt: "Veuillez entrer le mot de passe du site web pour modifier les paramètres du mode visiteur.",
|
||||
visitorModeDescription:
|
||||
"Mode lecture seule. Les vidéos masquées ne seront pas visibles pour les visiteurs.",
|
||||
visitorModePasswordPrompt:
|
||||
"Veuillez entrer le mot de passe du site web pour modifier les paramètres du mode visiteur.",
|
||||
cleanupTempFilesSuccess:
|
||||
"{count} fichier(s) temporaire(s) supprimé(s) avec succès.",
|
||||
cleanupTempFilesFailed: "Échec du nettoyage des fichiers temporaires",
|
||||
@@ -158,32 +161,39 @@ export const fr = {
|
||||
apiUrlHelper: "ex. https://your-alist-instance.com/api/fs/put",
|
||||
token: "Jeton (Token)",
|
||||
publicUrl: "URL Publique",
|
||||
publicUrlHelper: "Domaine public pour accéder aux fichiers (ex. https://your-cloudflare-tunnel-domain.com). S'il est défini, il sera utilisé à la place de l'URL de l'API pour accéder aux fichiers.",
|
||||
publicUrlHelper:
|
||||
"Domaine public pour accéder aux fichiers (ex. https://your-cloudflare-tunnel-domain.com). S'il est défini, il sera utilisé à la place de l'URL de l'API pour accéder aux fichiers.",
|
||||
uploadPath: "Chemin de téléchargement",
|
||||
cloudDrivePathHelper:
|
||||
"Chemin du répertoire dans le cloud, ex. /mytube-uploads",
|
||||
scanPaths: "Chemins d'analyse",
|
||||
scanPathsHelper: "Un chemin par ligne. Les vidéos seront analysées à partir de ces chemins. Si vide, le chemin de téléchargement sera utilisé. Exemple :\n/a/Films\n/b/Documentaires",
|
||||
scanPathsHelper:
|
||||
"Un chemin par ligne. Les vidéos seront analysées à partir de ces chemins. Si vide, le chemin de téléchargement sera utilisé. Exemple :\n/a/Films\n/b/Documentaires",
|
||||
cloudDriveNote:
|
||||
"Après avoir activé cette fonctionnalité, les vidéos nouvellement téléchargées seront automatiquement téléchargées vers le stockage cloud et les fichiers locaux seront supprimés. Les vidéos seront lues depuis le stockage cloud via un proxy.",
|
||||
testing: "Test en cours...",
|
||||
testConnection: "Tester la connexion",
|
||||
sync: "Synchroniser",
|
||||
syncToCloud: "Synchronisation bidirectionnelle",
|
||||
syncWarning: "Cette opération téléchargera les vidéos locales vers le cloud et recherchera les nouveaux fichiers dans le stockage cloud. Les fichiers locaux seront supprimés après le téléchargement.",
|
||||
syncWarning:
|
||||
"Cette opération téléchargera les vidéos locales vers le cloud et recherchera les nouveaux fichiers dans le stockage cloud. Les fichiers locaux seront supprimés après le téléchargement.",
|
||||
syncing: "Synchronisation...",
|
||||
syncCompleted: "Synchronisation Terminée",
|
||||
syncFailed: "Échec de la Synchronisation",
|
||||
syncReport: "Total : {total} | Téléchargés : {uploaded} | Échoués : {failed}",
|
||||
syncErrors: "Erreurs :",
|
||||
fillApiUrlToken: "Veuillez d'abord remplir l'URL de l'API et le jeton",
|
||||
connectionTestSuccess: "Test de connexion réussi ! Les paramètres sont valides.",
|
||||
connectionFailedStatus: "Échec de la connexion : Le serveur a renvoyé le statut {status}",
|
||||
connectionFailedUrl: "Impossible de se connecter au serveur. Veuillez vérifier l'URL de l'API.",
|
||||
connectionTestSuccess:
|
||||
"Test de connexion réussi ! Les paramètres sont valides.",
|
||||
connectionFailedStatus:
|
||||
"Échec de la connexion : Le serveur a renvoyé le statut {status}",
|
||||
connectionFailedUrl:
|
||||
"Impossible de se connecter au serveur. Veuillez vérifier l'URL de l'API.",
|
||||
authFailed: "Échec de l'authentification. Veuillez vérifier votre jeton.",
|
||||
connectionTestFailed: "Échec du test de connexion : {error}",
|
||||
syncFailedMessage: "Échec de la synchronisation. Veuillez réessayer.",
|
||||
foundVideosToSync: "{count} vidéos avec des fichiers locaux à synchroniser trouvées",
|
||||
foundVideosToSync:
|
||||
"{count} vidéos avec des fichiers locaux à synchroniser trouvées",
|
||||
uploadingVideo: "Téléversement : {title}",
|
||||
|
||||
// Manage
|
||||
@@ -263,6 +273,9 @@ export const fr = {
|
||||
exitFullscreen: "Quitter le plein écran",
|
||||
share: "Partager",
|
||||
editTitle: "Modifier le titre",
|
||||
hideVideo: "Rendre la vidéo cachée pour le mode visiteur",
|
||||
showVideo: "Rendre la vidéo visible pour le mode visiteur",
|
||||
toggleVisibility: "Basculer la visibilité",
|
||||
titleUpdated: "Titre mis à jour avec succès",
|
||||
titleUpdateFailed: "Échec de la mise à jour du titre",
|
||||
thumbnailRefreshed: "Miniature actualisée avec succès",
|
||||
@@ -279,7 +292,8 @@ export const fr = {
|
||||
openInExternalPlayer: "Ouvrir dans un lecteur externe",
|
||||
playWith: "Lire avec...",
|
||||
deleteAllFilteredVideos: "Supprimer toutes les vidéos filtrées",
|
||||
confirmDeleteFilteredVideos: "Êtes-vous sûr de vouloir supprimer {count} vidéos filtrées par les tags sélectionnés ?",
|
||||
confirmDeleteFilteredVideos:
|
||||
"Êtes-vous sûr de vouloir supprimer {count} vidéos filtrées par les tags sélectionnés ?",
|
||||
deleteFilteredVideosSuccess: "{count} vidéos supprimées avec succès.",
|
||||
deletingVideos: "Suppression des vidéos...",
|
||||
|
||||
@@ -318,7 +332,8 @@ export const fr = {
|
||||
unknownAuthor: "Inconnu",
|
||||
noVideosForAuthor: "Aucune vidéo trouvée pour cet auteur.",
|
||||
deleteAuthor: "Supprimer l'auteur",
|
||||
deleteAuthorConfirmation: "Êtes-vous sûr de vouloir supprimer l'auteur {author} ? Cela supprimera toutes les vidéos associées à cet auteur.",
|
||||
deleteAuthorConfirmation:
|
||||
"Êtes-vous sûr de vouloir supprimer l'auteur {author} ? Cela supprimera toutes les vidéos associées à cet auteur.",
|
||||
authorDeletedSuccessfully: "Auteur supprimé avec succès",
|
||||
failedToDeleteAuthor: "Échec de la suppression de l'auteur",
|
||||
|
||||
@@ -429,7 +444,8 @@ export const fr = {
|
||||
subscriptionAlreadyExists: "Vous êtes déjà abonné à cet auteur.",
|
||||
minutes: "minutes",
|
||||
never: "Jamais",
|
||||
downloadAllPreviousVideos: "Télécharger toutes les vidéos précédentes de cet auteur",
|
||||
downloadAllPreviousVideos:
|
||||
"Télécharger toutes les vidéos précédentes de cet auteur",
|
||||
downloadAllPreviousWarning:
|
||||
"Avertissement : Cela téléchargera toutes les vidéos précédentes de cet auteur. Cela peut consommer un espace de stockage important et pourrait déclencher des mécanismes de détection de bots qui peuvent entraîner des interdictions temporaires ou permanentes de la plateforme. Utilisez à vos propres risques.",
|
||||
continuousDownloadTasks: "Tâches de téléchargement continu",
|
||||
@@ -439,14 +455,13 @@ export const fr = {
|
||||
taskStatusCancelled: "Annulé",
|
||||
downloaded: "Téléchargé",
|
||||
cancelTask: "Annuler la tâche",
|
||||
confirmCancelTask: "Êtes-vous sûr de vouloir annuler la tâche de téléchargement pour {author} ?",
|
||||
confirmCancelTask:
|
||||
"Êtes-vous sûr de vouloir annuler la tâche de téléchargement pour {author} ?",
|
||||
taskCancelled: "Tâche annulée avec succès",
|
||||
deleteTask: "Supprimer la tâche",
|
||||
confirmDeleteTask: "Êtes-vous sûr de vouloir supprimer l'enregistrement de tâche pour {author} ? Cette action ne peut pas être annulée.",
|
||||
confirmDeleteTask:
|
||||
"Êtes-vous sûr de vouloir supprimer l'enregistrement de tâche pour {author} ? Cette action ne peut pas être annulée.",
|
||||
taskDeleted: "Tâche supprimée avec succès",
|
||||
subscriptionAlreadyExists: "Vous êtes déjà abonné à cet auteur.",
|
||||
minutes: "minutes",
|
||||
never: "Jamais",
|
||||
// Instruction Page
|
||||
instructionSection1Title: "1. Téléchargement et Gestion des Tâches",
|
||||
instructionSection1Desc:
|
||||
|
||||
@@ -124,6 +124,7 @@ export const ja = {
|
||||
showYoutubeSearch: "YouTube検索結果を表示",
|
||||
visitorMode: "ビジターモード(読み取り専用)",
|
||||
visitorModeReadOnly: "ビジターモード:読み取り専用",
|
||||
visitorModeDescription: "読み取り専用モード。非表示の動画は訪問者には表示されません。",
|
||||
visitorModePasswordPrompt: "ビジターモードの設定を変更するには、ウェブサイトのパスワードを入力してください。",
|
||||
cleanupTempFilesSuccess: "{count}個の一時ファイルを正常に削除しました。",
|
||||
cleanupTempFilesFailed: "一時ファイルのクリーンアップに失敗しました",
|
||||
@@ -248,6 +249,9 @@ export const ja = {
|
||||
exitFullscreen: "全画面表示を終了",
|
||||
share: "共有",
|
||||
editTitle: "タイトルを編集",
|
||||
hideVideo: "ビジターモードで動画を非表示にする",
|
||||
showVideo: "ビジターモードで動画を表示する",
|
||||
toggleVisibility: "表示/非表示を切り替え",
|
||||
titleUpdated: "タイトルが正常に更新されました",
|
||||
titleUpdateFailed: "タイトルの更新に失敗しました",
|
||||
refreshThumbnail: "サムネイルを更新",
|
||||
@@ -430,8 +434,6 @@ export const ja = {
|
||||
deleteTask: "タスクを削除",
|
||||
confirmDeleteTask: "{author} のタスクレコードを削除してもよろしいですか?この操作は元に戻せません。",
|
||||
taskDeleted: "タスクが正常に削除されました",
|
||||
minutes: "分",
|
||||
never: "なし",
|
||||
// Instruction Page
|
||||
instructionSection1Title: "1. ダウンロードとタスク管理",
|
||||
instructionSection1Desc:
|
||||
|
||||
@@ -121,6 +121,7 @@ export const ko = {
|
||||
showYoutubeSearch: "YouTube 검색 결과 표시",
|
||||
visitorMode: "방문자 모드 (읽기 전용)",
|
||||
visitorModeReadOnly: "방문자 모드: 읽기 전용",
|
||||
visitorModeDescription: "읽기 전용 모드. 숨겨진 동영상은 방문자에게 표시되지 않습니다.",
|
||||
visitorModePasswordPrompt: "방문자 모드 설정을 변경하려면 웹사이트 비밀번호를 입력하세요.",
|
||||
cleanupTempFilesSuccess: "{count}개의 임시 파일을 성공적으로 삭제했습니다.",
|
||||
cleanupTempFilesFailed: "임시 파일 정리 실패",
|
||||
@@ -244,6 +245,9 @@ export const ko = {
|
||||
exitFullscreen: "전체 화면 종료",
|
||||
share: "공유",
|
||||
editTitle: "제목 편집",
|
||||
hideVideo: "방문자 모드에서 동영상 숨기기",
|
||||
showVideo: "방문자 모드에서 동영상 표시",
|
||||
toggleVisibility: "표시 여부 전환",
|
||||
titleUpdated: "제목이 성공적으로 업데이트됨",
|
||||
titleUpdateFailed: "제목 업데이트 실패",
|
||||
refreshThumbnail: "썸네일 새로고침",
|
||||
@@ -426,8 +430,6 @@ export const ko = {
|
||||
deleteTask: "작업 삭제",
|
||||
confirmDeleteTask: "{author}님의 작업 기록을 삭제하시겠습니까? 이 작업은 취소할 수 없습니다.",
|
||||
taskDeleted: "작업이 성공적으로 삭제되었습니다",
|
||||
minutes: "분",
|
||||
never: "없음",
|
||||
// Instruction Page
|
||||
instructionSection1Title: "1. 다운로드 및 작업 관리",
|
||||
instructionSection1Desc:
|
||||
|
||||
@@ -124,6 +124,7 @@ export const pt = {
|
||||
showYoutubeSearch: "Mostrar resultados de pesquisa do YouTube",
|
||||
visitorMode: "Modo Visitante (Somente leitura)",
|
||||
visitorModeReadOnly: "Modo visitante: Somente leitura",
|
||||
visitorModeDescription: "Modo somente leitura. Vídeos ocultos não serão visíveis para visitantes.",
|
||||
visitorModePasswordPrompt: "Por favor, digite a senha do site para alterar as configurações do modo visitante.",
|
||||
cleanupTempFilesSuccess:
|
||||
"{count} arquivo(s) temporário(s) excluído(s) com sucesso.",
|
||||
@@ -249,6 +250,9 @@ export const pt = {
|
||||
exitFullscreen: "Sair da Tela Cheia",
|
||||
share: "Compartilhar",
|
||||
editTitle: "Editar Título",
|
||||
hideVideo: "Tornar Vídeo Oculto para Modo Visitante",
|
||||
showVideo: "Tornar Vídeo Visível para Modo Visitante",
|
||||
toggleVisibility: "Alternar Visibilidade",
|
||||
titleUpdated: "Título atualizado com sucesso",
|
||||
titleUpdateFailed: "Falha ao atualizar título",
|
||||
refreshThumbnail: "Atualizar miniatura",
|
||||
@@ -439,8 +443,6 @@ export const pt = {
|
||||
deleteTask: "Excluir tarefa",
|
||||
confirmDeleteTask: "Tem certeza de que deseja excluir o registro da tarefa para {author}? Esta ação não pode ser desfeita.",
|
||||
taskDeleted: "Tarefa excluída com sucesso",
|
||||
minutes: "minutos",
|
||||
never: "Nunca",
|
||||
// Instruction Page
|
||||
instructionSection1Title: "1. Download e Gerenciamento de Tarefas",
|
||||
instructionSection1Desc:
|
||||
|
||||
@@ -133,6 +133,7 @@ export const ru = {
|
||||
showYoutubeSearch: "Показать результаты поиска YouTube",
|
||||
visitorMode: "Режим посетителя (Только чтение)",
|
||||
visitorModeReadOnly: "Режим посетителя: Только чтение",
|
||||
visitorModeDescription: "Режим только чтения. Скрытые видео не будут видны посетителям.",
|
||||
visitorModePasswordPrompt: "Пожалуйста, введите пароль веб-сайта для изменения настроек режима посетителя.",
|
||||
cleanupTempFilesSuccess: "Успешно удалено {count} временных файлов.",
|
||||
cleanupTempFilesFailed: "Не удалось очистить временные файлы",
|
||||
@@ -259,6 +260,9 @@ export const ru = {
|
||||
exitFullscreen: "Выйти из полноэкранного режима",
|
||||
share: "Поделиться",
|
||||
editTitle: "Редактировать название",
|
||||
hideVideo: "Скрыть видео для режима посетителя",
|
||||
showVideo: "Сделать видео видимым для режима посетителя",
|
||||
toggleVisibility: "Переключить видимость",
|
||||
titleUpdated: "Название успешно обновлено",
|
||||
titleUpdateFailed: "Не удалось обновить название",
|
||||
refreshThumbnail: "Обновить миниатюру",
|
||||
@@ -444,8 +448,6 @@ export const ru = {
|
||||
deleteTask: "Удалить задачу",
|
||||
confirmDeleteTask: "Вы уверены, что хотите удалить запись задачи для {author}? Это действие нельзя отменить.",
|
||||
taskDeleted: "Задача успешно удалена",
|
||||
minutes: "минуты",
|
||||
never: "Никогда",
|
||||
// Instruction Page
|
||||
instructionSection1Title: "1. Загрузка и управление задачами",
|
||||
instructionSection1Desc:
|
||||
|
||||
@@ -116,6 +116,7 @@ export const zh = {
|
||||
showYoutubeSearch: "显示 YouTube 搜索结果",
|
||||
visitorMode: "访客模式(只读)",
|
||||
visitorModeReadOnly: "访客模式:只读",
|
||||
visitorModeDescription: "只读模式。隐藏的视频对访客不可见。",
|
||||
visitorModePasswordPrompt: "请输入网站密码以更改访客模式设置。",
|
||||
cleanupTempFilesSuccess: "成功删除了 {count} 个临时文件。",
|
||||
cleanupTempFilesFailed: "清理临时文件失败",
|
||||
@@ -145,18 +146,21 @@ export const zh = {
|
||||
apiUrlHelper: "例如:https://your-alist-instance.com/api/fs/put",
|
||||
token: "Token",
|
||||
publicUrl: "公开访问域名",
|
||||
publicUrlHelper: "用于访问文件的公开域名(例如:https://your-cloudflare-tunnel-domain.com)。如果设置,将使用此域名而不是 API 地址来访问文件。",
|
||||
publicUrlHelper:
|
||||
"用于访问文件的公开域名(例如:https://your-cloudflare-tunnel-domain.com)。如果设置,将使用此域名而不是 API 地址来访问文件。",
|
||||
uploadPath: "上传路径",
|
||||
cloudDrivePathHelper: "云端存储中的目录路径,例如:/mytube-uploads",
|
||||
scanPaths: "扫描路径",
|
||||
scanPathsHelper: "每行一个路径。系统将扫描这些路径下的视频。留空则使用默认上传路径。示例:\n/a/电影\n/b/纪录片",
|
||||
scanPathsHelper:
|
||||
"每行一个路径。系统将扫描这些路径下的视频。留空则使用默认上传路径。示例:\n/a/电影\n/b/纪录片",
|
||||
cloudDriveNote:
|
||||
"启用此功能后,新下载的视频将自动上传到云端存储,本地文件将被删除。视频将通过代理从云端存储播放。",
|
||||
testing: "测试中...",
|
||||
testConnection: "测试连接",
|
||||
sync: "同步",
|
||||
syncToCloud: "双向同步",
|
||||
syncWarning: "此操作将上传本地视频到云端并扫描云端新文件。上传后,本地文件将被删除。",
|
||||
syncWarning:
|
||||
"此操作将上传本地视频到云端并扫描云端新文件。上传后,本地文件将被删除。",
|
||||
syncing: "正在同步...",
|
||||
syncCompleted: "同步完成",
|
||||
syncFailed: "同步失败",
|
||||
@@ -238,6 +242,9 @@ export const zh = {
|
||||
exitFullscreen: "退出全屏",
|
||||
share: "分享",
|
||||
editTitle: "编辑标题",
|
||||
hideVideo: "使视频在访客模式下隐藏",
|
||||
showVideo: "使视频在访客模式下可见",
|
||||
toggleVisibility: "切换可见性",
|
||||
titleUpdated: "标题更新成功",
|
||||
titleUpdateFailed: "更新标题失败",
|
||||
refreshThumbnail: "刷新缩略图",
|
||||
@@ -254,7 +261,8 @@ export const zh = {
|
||||
openInExternalPlayer: "在外部播放器中打开",
|
||||
playWith: "使用此应用播放...",
|
||||
deleteAllFilteredVideos: "删除所有过滤后的视频",
|
||||
confirmDeleteFilteredVideos: "您确定要删除通过选定标签过滤的 {count} 个视频吗?",
|
||||
confirmDeleteFilteredVideos:
|
||||
"您确定要删除通过选定标签过滤的 {count} 个视频吗?",
|
||||
deleteFilteredVideosSuccess: "成功删除 {count} 个视频。",
|
||||
deletingVideos: "正在删除视频...",
|
||||
|
||||
@@ -300,7 +308,8 @@ export const zh = {
|
||||
unknownAuthor: "未知",
|
||||
noVideosForAuthor: "未找到该作者的视频。",
|
||||
deleteAuthor: "删除作者",
|
||||
deleteAuthorConfirmation: "您确定要删除作者 {author} 吗?这将删除该作者的所有视频。",
|
||||
deleteAuthorConfirmation:
|
||||
"您确定要删除作者 {author} 吗?这将删除该作者的所有视频。",
|
||||
authorDeletedSuccessfully: "作者删除成功",
|
||||
failedToDeleteAuthor: "删除作者失败",
|
||||
|
||||
|
||||
Reference in New Issue
Block a user