Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/entries/content-script/app/pages/SiteDetailPage.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import { ref } from "vue";
import { inject } from "vue";

import { sendMessage } from "@/messages.ts";
Expand All @@ -9,6 +10,10 @@ import type { IRemoteDownloadDialogData } from "../types.ts";
import { copyTextToClipboard, doKeywordSearch, siteInstance } from "../utils.ts";

import SpeedDialBtn from "../components/SpeedDialBtn.vue";
import CollectionAddDialog from "@/options/components/CollectionAddDialog.vue";

import type { ITorrent } from "@ptd/site";
import { buildTorrentCollectionKey, DEFAULT_COLLECTION_ID } from "@/shared/types.ts";

const metadataStore = useMetadataStore();
const runtimeStore = useRuntimeStore();
Expand Down Expand Up @@ -54,6 +59,32 @@ function handleSearch() {
doKeywordSearch(torrent.title || "");
});
}

// ===================== 收藏功能 =====================
const showCollectionDialog = ref(false);
const collectionDialogTorrent = ref<ITorrent | null>(null);

async function handleCollect() {
const torrent = await parseDetailPage().catch(() => null);
if (!torrent) return;

const hasCustom = metadataStore.getCustomCollections().length > 0;
const key = buildTorrentCollectionKey(torrent);
const collected = metadataStore.isTorrentCollected(key);

if (!hasCustom) {
if (collected) {
await metadataStore.updateTorrentCollections(torrent, []);
runtimeStore.showSnakebar("已从收藏中移除", { color: "info" });
} else {
await metadataStore.addTorrentToCollections(torrent, [DEFAULT_COLLECTION_ID]);
runtimeStore.showSnakebar("已添加到默认收藏夹", { color: "success" });
}
} else {
collectionDialogTorrent.value = torrent;
showCollectionDialog.value = true;
}
}
</script>

<template>
Expand All @@ -76,6 +107,9 @@ function handleSearch() {
@click="handleRemoteDownload(true)"
/>
<SpeedDialBtn key="search" color="indigo" icon="mdi-home-search" title="快捷搜索" @click="handleSearch" />
<SpeedDialBtn key="collect" color="amber" icon="mdi-bookmark-plus" title="收藏种子" @click="handleCollect" />

<CollectionAddDialog v-model="showCollectionDialog" :torrent="collectionDialogTorrent" />
</template>

<style scoped lang="scss"></style>
48 changes: 48 additions & 0 deletions src/entries/content-script/app/pages/SiteListPage.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import { copyTextToClipboard, doKeywordSearch, siteInstance, wrapperConfirmFn }

import AdvanceListModuleDialog from "../components/AdvanceListModuleDialog.vue";
import SpeedDialBtn from "../components/SpeedDialBtn.vue";
import CollectionAddDialog from "@/options/components/CollectionAddDialog.vue";

import { DEFAULT_COLLECTION_ID, buildTorrentCollectionKey } from "@/shared/types.ts";

const metadataStore = useMetadataStore();
const runtimeStore = useRuntimeStore();
Expand Down Expand Up @@ -104,6 +107,43 @@ async function handleSearch() {

doKeywordSearch(keywords);
}

// ===================== 收藏功能 =====================
const showCollectionDialog = ref(false);
// 当页面只有一个种子时,直接用该种子作为收藏对象;
// 当页面有多个种子时,使用 collectionBatchTorrents 批量添加。
const collectionDialogTorrent = ref<ITorrent | null>(null);
const collectionBatchTorrents = shallowRef<ITorrent[]>([]);

async function handleCollectAll() {
const { torrents } = await parseListPage().catch(() => ({ torrents: [] as ITorrent[] }));
if (torrents.length === 0) return;

const hasCustom = metadataStore.getCustomCollections().length > 0;

if (!hasCustom) {
// 直接批量添加到默认收藏夹(一次 IO)
await metadataStore.addTorrentsToCollections(torrents, [DEFAULT_COLLECTION_ID]);
runtimeStore.showSnakebar(`已将 ${torrents.length} 个种子添加到默认收藏夹`, { color: "success" });
} else {
// 若只有一个种子,用单种子对话框;多个种子时用第一个代理(仅选择收藏夹)
collectionBatchTorrents.value = torrents;
collectionDialogTorrent.value = torrents[0] ?? null;
showCollectionDialog.value = true;
}
}

async function onCollectionSaved() {
// 批量模式:将所有解析到的种子同步到与第一个种子相同的收藏夹
const key0 = collectionDialogTorrent.value ? buildTorrentCollectionKey(collectionDialogTorrent.value) : null;
if (!key0 || collectionBatchTorrents.value.length <= 1) return;

const selectedIds = metadataStore.getTorrentCollectionIds(key0);
if (selectedIds.length === 0) return; // 用户未选择任何收藏夹,无需同步

// 将剩余种子批量加入相同的收藏夹
await metadataStore.addTorrentsToCollections(collectionBatchTorrents.value.slice(1), selectedIds);
}
</script>

<template>
Expand Down Expand Up @@ -149,8 +189,16 @@ async function handleSearch() {
@click="handleAdvanceListModule"
/>
<SpeedDialBtn key="search" color="indigo" icon="mdi-home-search" title="快捷搜索" @click="handleSearch" />
<SpeedDialBtn
key="collect"
color="amber"
icon="mdi-bookmark-multiple-outline"
title="收藏当前页种子"
@click="wrapperConfirmFn(handleCollectAll)"
/>

<AdvanceListModuleDialog v-model="showAdvanceListModuleDialog" :torrent-items="parsedTorrents" />
<CollectionAddDialog v-model="showCollectionDialog" :torrent="collectionDialogTorrent" @saved="onCollectionSaved" />
</template>

<style scoped lang="scss"></style>
93 changes: 93 additions & 0 deletions src/entries/options/components/CollectionAddDialog.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<script setup lang="ts">
/**
* 收藏夹选择对话框:
* 如果只有一个默认收藏夹,则直接添加,不显示对话框。
* 否则显示勾选框,让用户选择将种子加入哪些收藏夹。
*/
import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";

import type { ITorrent } from "@ptd/site";

import { useMetadataStore } from "@/options/stores/metadata.ts";
import { DEFAULT_COLLECTION_ID, type TCollectionId, buildTorrentCollectionKey } from "@/shared/types.ts";

const { t } = useI18n();

const showDialog = defineModel<boolean>();

const props = defineProps<{
torrent: ITorrent | null;
}>();

const emits = defineEmits<{
(e: "saved"): void;
}>();

const metadataStore = useMetadataStore();

const sortedCollections = computed(() => metadataStore.getSortedCollections());
const hasCustomCollections = computed(() => metadataStore.getCustomCollections().length > 0);

// 当前种子已在哪些收藏夹中
const selectedIds = ref<TCollectionId[]>([]);

watch(showDialog, (val) => {
if (val && props.torrent) {
const key = buildTorrentCollectionKey(props.torrent);
selectedIds.value = metadataStore.getTorrentCollectionIds(key);
}
});

async function save() {
if (!props.torrent) return;
await metadataStore.updateTorrentCollections(props.torrent, selectedIds.value);
showDialog.value = false;
emits("saved");
}
</script>

<template>
<v-dialog v-model="showDialog" width="400">
<v-card>
<v-card-title class="pa-0">
<v-toolbar color="primary">
<v-toolbar-title>{{ t("MyCollection.selectDialog.title") }}</v-toolbar-title>
<template #append>
<v-btn icon="mdi-close" :title="t('common.dialog.close')" @click="showDialog = false" />
</template>
</v-toolbar>
</v-card-title>

<v-divider />

<v-card-text class="pt-2">
<p class="text-body-2 text-grey mb-2 text-truncate" :title="torrent?.title">{{ torrent?.title }}</p>
<v-list density="compact">
<v-list-item v-for="collection in sortedCollections" :key="collection.id" :value="collection.id" class="px-1">
<template #prepend>
<v-checkbox-btn v-model="selectedIds" :value="collection.id" density="compact" />
</template>
<template #default>
<v-list-item-title class="d-flex align-center">
<v-icon :color="collection.color ?? 'primary'" icon="mdi-bookmark" size="small" class="mr-1" />
{{ collection.name }}
<span class="text-caption text-grey ml-2">
({{ t("MyCollection.folderPanel.torrentCount", [collection.torrentIds.length]) }})
</span>
</v-list-item-title>
</template>
</v-list-item>
</v-list>
</v-card-text>

<v-card-actions>
<v-spacer />
<v-btn variant="text" @click="showDialog = false">{{ t("common.dialog.cancel") }}</v-btn>
<v-btn color="primary" variant="tonal" @click="save">{{ t("common.dialog.ok") }}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</template>

<style scoped lang="scss"></style>
6 changes: 6 additions & 0 deletions src/entries/options/plugins/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,12 @@ export const routes: RouteRecordRaw[] = [
meta: { icon: "mdi-history" },
component: () => import("../views/Overview/DownloadHistory/Index.vue"),
},
{
path: "/my-collection",
name: "MyCollection",
meta: { icon: "mdi-bookmark-multiple" },
component: () => import("../views/Overview/MyCollection/Index.vue"),
},
],
},
{
Expand Down
Loading