mirror of
https://github.com/jxxghp/MoviePilot-Frontend.git
synced 2026-08-28 19:47:49 +08:00
Merge pull request #281 from wintsa123/v2
This commit is contained in:
@@ -6,7 +6,7 @@ import { formatSeason } from '@/@core/utils/formatters'
|
|||||||
import api from '@/api'
|
import api from '@/api'
|
||||||
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
import { doneNProgress, startNProgress } from '@/api/nprogress'
|
||||||
import type { MediaInfo, NotExistMediaInfo, Subscribe, TmdbSeason } from '@/api/types'
|
import type { MediaInfo, NotExistMediaInfo, Subscribe, TmdbSeason } from '@/api/types'
|
||||||
import router from '@/router'
|
import router, { registerAbortController } from '@/router'
|
||||||
import noImage from '@images/no-image.jpeg'
|
import noImage from '@images/no-image.jpeg'
|
||||||
import tmdbImage from '@images/logos/tmdb.png'
|
import tmdbImage from '@images/logos/tmdb.png'
|
||||||
import doubanImage from '@images/logos/douban-black.png'
|
import doubanImage from '@images/logos/douban-black.png'
|
||||||
@@ -59,7 +59,11 @@ const seasonInfos = ref<TmdbSeason[]>([])
|
|||||||
|
|
||||||
// 选中的订阅季
|
// 选中的订阅季
|
||||||
const seasonsSelected = ref<TmdbSeason[]>([])
|
const seasonsSelected = ref<TmdbSeason[]>([])
|
||||||
|
let abortController: AbortController | null = null;
|
||||||
|
|
||||||
|
abortController = new AbortController();
|
||||||
|
registerAbortController(abortController);
|
||||||
|
const { signal } = abortController;
|
||||||
// 来源角标字典
|
// 来源角标字典
|
||||||
const sourceIconDict: { [key: string]: any } = {
|
const sourceIconDict: { [key: string]: any } = {
|
||||||
themoviedb: tmdbImage,
|
themoviedb: tmdbImage,
|
||||||
@@ -215,6 +219,7 @@ async function removeSubscribe() {
|
|||||||
// 查询当前媒体是否已订阅
|
// 查询当前媒体是否已订阅
|
||||||
async function handleCheckSubscribe() {
|
async function handleCheckSubscribe() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const result = await checkSubscribe(props.media?.season)
|
const result = await checkSubscribe(props.media?.season)
|
||||||
if (result) isSubscribed.value = true
|
if (result) isSubscribed.value = true
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -225,6 +230,7 @@ async function handleCheckSubscribe() {
|
|||||||
// 查询当前媒体是否已入库
|
// 查询当前媒体是否已入库
|
||||||
async function handleCheckExists() {
|
async function handleCheckExists() {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const result: { [key: string]: any } = await api.get('mediaserver/exists', {
|
const result: { [key: string]: any } = await api.get('mediaserver/exists', {
|
||||||
params: {
|
params: {
|
||||||
tmdbid: props.media?.tmdb_id,
|
tmdbid: props.media?.tmdb_id,
|
||||||
@@ -233,6 +239,7 @@ async function handleCheckExists() {
|
|||||||
season: props.media?.season,
|
season: props.media?.season,
|
||||||
mtype: props.media?.type,
|
mtype: props.media?.type,
|
||||||
},
|
},
|
||||||
|
signal
|
||||||
})
|
})
|
||||||
|
|
||||||
if (result.success) isExists.value = true
|
if (result.success) isExists.value = true
|
||||||
@@ -244,6 +251,7 @@ async function handleCheckExists() {
|
|||||||
// 调用API检查是否已订阅,电视剧需要指定季
|
// 调用API检查是否已订阅,电视剧需要指定季
|
||||||
async function checkSubscribe(season = 0) {
|
async function checkSubscribe(season = 0) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const mediaid = getMediaId()
|
const mediaid = getMediaId()
|
||||||
|
|
||||||
const result: Subscribe = await api.get(`subscribe/media/${mediaid}`, {
|
const result: Subscribe = await api.get(`subscribe/media/${mediaid}`, {
|
||||||
@@ -251,6 +259,7 @@ async function checkSubscribe(season = 0) {
|
|||||||
season,
|
season,
|
||||||
title: props.media?.title,
|
title: props.media?.title,
|
||||||
},
|
},
|
||||||
|
signal
|
||||||
})
|
})
|
||||||
|
|
||||||
return result.id || null
|
return result.id || null
|
||||||
@@ -444,25 +453,13 @@ function onRemoveSubscribe() {
|
|||||||
<VHover>
|
<VHover>
|
||||||
<template #default="hover">
|
<template #default="hover">
|
||||||
<div ref="mediaCardRef">
|
<div ref="mediaCardRef">
|
||||||
<VCard
|
<VCard v-bind="hover.props" :height="props.height" :width="props.width"
|
||||||
v-bind="hover.props"
|
class="outline-none shadow ring-gray-500 rounded-lg" :class="{
|
||||||
:height="props.height"
|
|
||||||
:width="props.width"
|
|
||||||
class="outline-none shadow ring-gray-500 rounded-lg"
|
|
||||||
:class="{
|
|
||||||
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
|
'transition transform-cpu duration-300 scale-105 shadow-lg': hover.isHovering,
|
||||||
'ring-1': isImageLoaded,
|
'ring-1': isImageLoaded,
|
||||||
}"
|
}" @click.stop="goMediaDetail(hover.isHovering ?? false)">
|
||||||
@click.stop="goMediaDetail(hover.isHovering ?? false)"
|
<VImg aspect-ratio="2/3" :src="getImgUrl" class="object-cover aspect-w-2 aspect-h-3" cover
|
||||||
>
|
@load="isImageLoaded = true" @error="imageLoadError = true">
|
||||||
<VImg
|
|
||||||
aspect-ratio="2/3"
|
|
||||||
:src="getImgUrl"
|
|
||||||
class="object-cover aspect-w-2 aspect-h-3"
|
|
||||||
cover
|
|
||||||
@load="isImageLoaded = true"
|
|
||||||
@error="imageLoadError = true"
|
|
||||||
>
|
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
||||||
@@ -470,11 +467,9 @@ function onRemoveSubscribe() {
|
|||||||
</template>
|
</template>
|
||||||
</VImg>
|
</VImg>
|
||||||
<!-- 详情 -->
|
<!-- 详情 -->
|
||||||
<VCardText
|
<VCardText v-show="hover.isHovering || imageLoadError"
|
||||||
v-show="hover.isHovering || imageLoadError"
|
|
||||||
class="w-full h-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 cursor-pointer pa-2"
|
class="w-full h-full flex flex-col flex-wrap justify-end align-left text-white absolute bottom-0 cursor-pointer pa-2"
|
||||||
style="background: linear-gradient(rgba(45, 55, 72, 40%) 0%, rgba(45, 55, 72, 90%) 100%)"
|
style="background: linear-gradient(rgba(45, 55, 72, 40%) 0%, rgba(45, 55, 72, 90%) 100%)">
|
||||||
>
|
|
||||||
<span class="font-bold">{{ props.media?.year }}</span>
|
<span class="font-bold">{{ props.media?.year }}</span>
|
||||||
<h1 class="mb-1 text-white font-extrabold text-xl line-clamp-2 overflow-hidden text-ellipsis ...">
|
<h1 class="mb-1 text-white font-extrabold text-xl line-clamp-2 overflow-hidden text-ellipsis ...">
|
||||||
{{ props.media?.title }}
|
{{ props.media?.title }}
|
||||||
@@ -488,35 +483,21 @@ function onRemoveSubscribe() {
|
|||||||
</div>
|
</div>
|
||||||
</VCardText>
|
</VCardText>
|
||||||
<!-- 类型角标 -->
|
<!-- 类型角标 -->
|
||||||
<VChip
|
<VChip v-show="isImageLoaded" variant="elevated" size="small" :class="getChipColor(props.media?.type || '')"
|
||||||
v-show="isImageLoaded"
|
class="absolute left-2 top-2 bg-opacity-80 shadow-md text-white font-bold">
|
||||||
variant="elevated"
|
|
||||||
size="small"
|
|
||||||
:class="getChipColor(props.media?.type || '')"
|
|
||||||
class="absolute left-2 top-2 bg-opacity-80 shadow-md text-white font-bold"
|
|
||||||
>
|
|
||||||
{{ props.media?.type }}
|
{{ props.media?.type }}
|
||||||
</VChip>
|
</VChip>
|
||||||
<!-- 本地存在标识 -->
|
<!-- 本地存在标识 -->
|
||||||
<ExistIcon v-if="isExists && !hover.isHovering" />
|
<ExistIcon v-if="isExists && !hover.isHovering" />
|
||||||
<!-- 评分角标 -->
|
<!-- 评分角标 -->
|
||||||
<VChip
|
<VChip v-if="isImageLoaded && props.media?.vote_average && !(isExists && !hover.isHovering)"
|
||||||
v-if="isImageLoaded && props.media?.vote_average && !(isExists && !hover.isHovering)"
|
variant="elevated" size="small" :class="getChipColor('rating')"
|
||||||
variant="elevated"
|
class="absolute right-2 top-2 bg-opacity-80 shadow-md text-white font-bold">
|
||||||
size="small"
|
|
||||||
:class="getChipColor('rating')"
|
|
||||||
class="absolute right-2 top-2 bg-opacity-80 shadow-md text-white font-bold"
|
|
||||||
>
|
|
||||||
{{ props.media?.vote_average }}
|
{{ props.media?.vote_average }}
|
||||||
</VChip>
|
</VChip>
|
||||||
<!--来源图标-->
|
<!--来源图标-->
|
||||||
<VAvatar
|
<VAvatar size="24" density="compact" class="absolute bottom-1 right-1" tile
|
||||||
size="24"
|
v-if="!hover.isHovering && isImageLoaded && props.media?.source">
|
||||||
density="compact"
|
|
||||||
class="absolute bottom-1 right-1"
|
|
||||||
tile
|
|
||||||
v-if="!hover.isHovering && isImageLoaded && props.media?.source"
|
|
||||||
>
|
|
||||||
<VImg cover :src="sourceIconDict[props.media?.source]" class="shadow-lg" />
|
<VImg cover :src="sourceIconDict[props.media?.source]" class="shadow-lg" />
|
||||||
</VAvatar>
|
</VAvatar>
|
||||||
</VCard>
|
</VCard>
|
||||||
@@ -535,14 +516,8 @@ function onRemoveSubscribe() {
|
|||||||
<VList v-model:selected="seasonsSelected" lines="three" select-strategy="classic">
|
<VList v-model:selected="seasonsSelected" lines="three" select-strategy="classic">
|
||||||
<VListItem v-for="(item, i) in seasonInfos" :key="i" :value="item">
|
<VListItem v-for="(item, i) in seasonInfos" :key="i" :value="item">
|
||||||
<template #prepend>
|
<template #prepend>
|
||||||
<VImg
|
<VImg height="90" width="60" :src="getSeasonPoster(item.poster_path || '')" aspect-ratio="2/3"
|
||||||
height="90"
|
class="object-cover rounded shadow ring-gray-500 me-3" cover>
|
||||||
width="60"
|
|
||||||
:src="getSeasonPoster(item.poster_path || '')"
|
|
||||||
aspect-ratio="2/3"
|
|
||||||
class="object-cover rounded shadow ring-gray-500 me-3"
|
|
||||||
cover
|
|
||||||
>
|
|
||||||
<template #placeholder>
|
<template #placeholder>
|
||||||
<div class="w-full h-full">
|
<div class="w-full h-full">
|
||||||
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
<VSkeletonLoader class="object-cover aspect-w-2 aspect-h-3" />
|
||||||
@@ -581,12 +556,6 @@ function onRemoveSubscribe() {
|
|||||||
</VCard>
|
</VCard>
|
||||||
</VBottomSheet>
|
</VBottomSheet>
|
||||||
<!-- 订阅编辑弹窗 -->
|
<!-- 订阅编辑弹窗 -->
|
||||||
<SubscribeEditDialog
|
<SubscribeEditDialog v-if="subscribeEditDialog" v-model="subscribeEditDialog" :subid="subscribeId"
|
||||||
v-if="subscribeEditDialog"
|
@close="subscribeEditDialog = false" @save="subscribeEditDialog = false" @remove="onRemoveSubscribe" />
|
||||||
v-model="subscribeEditDialog"
|
|
||||||
:subid="subscribeId"
|
|
||||||
@close="subscribeEditDialog = false"
|
|
||||||
@save="subscribeEditDialog = false"
|
|
||||||
@remove="onRemoveSubscribe"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ const viewList = reactive<{ apipath: string; linkurl: string; title: string }[]>
|
|||||||
title: '豆瓣全球剧集榜',
|
title: '豆瓣全球剧集榜',
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
+15
-1
@@ -189,7 +189,18 @@ const router = createRouter({
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
const abortControllers = new Set<AbortController>()
|
||||||
|
|
||||||
|
function registerAbortController(controller: AbortController) {
|
||||||
|
abortControllers.add(controller)
|
||||||
|
}
|
||||||
|
|
||||||
|
function abortAllControllers() {
|
||||||
|
for (const controller of abortControllers) {
|
||||||
|
controller.abort()
|
||||||
|
}
|
||||||
|
abortControllers.clear()
|
||||||
|
}
|
||||||
// 路由导航守卫
|
// 路由导航守卫
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
// 总是记录非login路由
|
// 总是记录非login路由
|
||||||
@@ -198,8 +209,11 @@ router.beforeEach((to, from, next) => {
|
|||||||
if (to.meta.requiresAuth && !isAuthenticated) {
|
if (to.meta.requiresAuth && !isAuthenticated) {
|
||||||
next('/login')
|
next('/login')
|
||||||
} else {
|
} else {
|
||||||
|
abortAllControllers() // 中止所有组件的任务
|
||||||
|
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router // 导出默认对象
|
||||||
|
export { registerAbortController } // 另行导出其他功能
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ const isRefreshed = ref(false)
|
|||||||
// 数据列表
|
// 数据列表
|
||||||
const dataList = ref<MediaInfo[]>([])
|
const dataList = ref<MediaInfo[]>([])
|
||||||
const currData = ref<MediaInfo[]>([])
|
const currData = ref<MediaInfo[]>([])
|
||||||
|
|
||||||
// 拼装参数
|
// 拼装参数
|
||||||
function getParams() {
|
function getParams() {
|
||||||
let params = {
|
let params = {
|
||||||
@@ -78,6 +77,7 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
} else {
|
} else {
|
||||||
// 加载一次
|
// 加载一次
|
||||||
// 设置加载中
|
// 设置加载中
|
||||||
|
|
||||||
loading.value = true
|
loading.value = true
|
||||||
// 请求API
|
// 请求API
|
||||||
currData.value = await api.get(props.apipath, {
|
currData.value = await api.get(props.apipath, {
|
||||||
@@ -115,15 +115,7 @@ async function fetchData({ done }: { done: any }) {
|
|||||||
<div v-if="dataList.length > 0" class="grid gap-4 grid-media-card mx-3" tabindex="0">
|
<div v-if="dataList.length > 0" class="grid gap-4 grid-media-card mx-3" tabindex="0">
|
||||||
<MediaCard v-for="data in dataList" :key="data.tmdb_id || data.douban_id" :media="data" />
|
<MediaCard v-for="data in dataList" :key="data.tmdb_id || data.douban_id" :media="data" />
|
||||||
</div>
|
</div>
|
||||||
<NoDataFound
|
<NoDataFound v-if="dataList.length === 0 && isRefreshed" error-code="404" error-title="没有数据"
|
||||||
v-if="dataList.length === 0 && isRefreshed"
|
error-description="无法获取到媒体信息。" />
|
||||||
error-code="404"
|
|
||||||
error-title="没有数据"
|
|
||||||
error-description="无法获取到媒体信息。"
|
|
||||||
/>
|
|
||||||
</VInfiniteScroll>
|
</VInfiniteScroll>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import api from '@/api'
|
|||||||
import type { MediaInfo } from '@/api/types'
|
import type { MediaInfo } from '@/api/types'
|
||||||
import MediaCard from '@/components/cards/MediaCard.vue'
|
import MediaCard from '@/components/cards/MediaCard.vue'
|
||||||
import SlideView from '@/components/slide/SlideView.vue'
|
import SlideView from '@/components/slide/SlideView.vue'
|
||||||
|
import { registerAbortController } from "@/router";
|
||||||
|
|
||||||
// 输入参数
|
// 输入参数
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -10,8 +11,9 @@ const props = defineProps({
|
|||||||
linkurl: String,
|
linkurl: String,
|
||||||
title: String,
|
title: String,
|
||||||
})
|
})
|
||||||
|
let abortController: AbortController | null = null;
|
||||||
|
|
||||||
provide('rankingPropsKey', reactive({...props}))
|
provide('rankingPropsKey', reactive({ ...props }))
|
||||||
|
|
||||||
// 组件加载完成
|
// 组件加载完成
|
||||||
const componentLoaded = ref(false)
|
const componentLoaded = ref(false)
|
||||||
@@ -24,8 +26,10 @@ async function fetchData() {
|
|||||||
try {
|
try {
|
||||||
if (!props.apipath)
|
if (!props.apipath)
|
||||||
return
|
return
|
||||||
|
abortController = new AbortController();
|
||||||
dataList.value = await api.get(props.apipath)
|
registerAbortController(abortController);
|
||||||
|
const { signal } = abortController;
|
||||||
|
dataList.value = await api.get(props.apipath, { signal })
|
||||||
if (dataList.value.length > 0)
|
if (dataList.value.length > 0)
|
||||||
componentLoaded.value = true
|
componentLoaded.value = true
|
||||||
}
|
}
|
||||||
@@ -35,23 +39,22 @@ async function fetchData() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 加载时获取数据
|
// 加载时获取数据
|
||||||
onMounted(fetchData)
|
onMounted(() => {
|
||||||
|
fetchData();
|
||||||
|
});
|
||||||
|
onActivated(() => {
|
||||||
|
if (dataList.value.length == 0) {
|
||||||
|
fetchData();
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<SlideView
|
<SlideView v-if="componentLoaded">
|
||||||
v-if="componentLoaded"
|
|
||||||
>
|
|
||||||
<template #content>
|
<template #content>
|
||||||
<template
|
<template v-for="data in dataList" :key="data.tmdb_id || data.douban_id || data.bangumi_id">
|
||||||
v-for="data in dataList"
|
<MediaCard :media="data" height="15rem" width="10rem" />
|
||||||
:key="data.tmdb_id || data.douban_id || data.bangumi_id"
|
|
||||||
>
|
|
||||||
<MediaCard
|
|
||||||
:media="data"
|
|
||||||
height="15rem"
|
|
||||||
width="10rem"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
</template>
|
</template>
|
||||||
</SlideView>
|
</SlideView>
|
||||||
|
|||||||
@@ -6618,8 +6618,16 @@ stop-iteration-iterator@^1.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
internal-slot "^1.0.4"
|
internal-slot "^1.0.4"
|
||||||
|
|
||||||
"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.3:
|
"string-width-cjs@npm:string-width@^4.2.0":
|
||||||
name string-width-cjs
|
version "4.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
|
dependencies:
|
||||||
|
emoji-regex "^8.0.0"
|
||||||
|
is-fullwidth-code-point "^3.0.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
|
||||||
|
string-width@^4.1.0, string-width@^4.2.3:
|
||||||
version "4.2.3"
|
version "4.2.3"
|
||||||
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
|
||||||
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
|
||||||
@@ -6692,8 +6700,14 @@ stringify-object@^3.3.0:
|
|||||||
is-obj "^1.0.1"
|
is-obj "^1.0.1"
|
||||||
is-regexp "^1.0.0"
|
is-regexp "^1.0.0"
|
||||||
|
|
||||||
"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
"strip-ansi-cjs@npm:strip-ansi@^6.0.1":
|
||||||
name strip-ansi-cjs
|
version "6.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^5.0.1"
|
||||||
|
|
||||||
|
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||||
version "6.0.1"
|
version "6.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
|
||||||
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
|
||||||
|
|||||||
Reference in New Issue
Block a user