This commit is contained in:
Hakadao
2022-12-24 01:28:26 +08:00
parent 519238d493
commit 1635ca7925
4 changed files with 474 additions and 29 deletions

View File

@@ -0,0 +1,14 @@
import browser from 'webextension-polyfill'
import { API_URL } from '.'
export const setupFavoritesAPIs = () => {
browser.runtime.onMessage.addListener((message) => {
if (message.contentScriptQuery === 'getHistoryList') {
const url = `${API_URL}/x/v3/fav/folder/created/list-all?up_mid=${message.mid}&jsonp=jsonp`
return fetch(url)
.then(response => response.json())
.then(data => (data))
.catch(error => console.error(error))
}
})
}

View File

@@ -5,6 +5,7 @@ import { setupSearchAPIs } from './search'
import { setupNotificationsAPIs } from './notifications'
import { setupMomentsAPIs } from './moments'
import { setupHistoryAPIs } from './history'
import { setupFavoritesAPIs } from './favorites'
export const APP_URL = 'https://app.bilibili.com'
export const API_URL = 'https://api.bilibili.com'
@@ -17,4 +18,5 @@ export const setupAllAPIs = () => {
setupNotificationsAPIs()
setupMomentsAPIs()
setupHistoryAPIs()
setupFavoritesAPIs()
}

View File

@@ -2,20 +2,19 @@
import type { Ref, UnwrapNestedRefs } from 'vue'
import { Transition, onMounted, watch } from 'vue'
import type { UnReadDm, UnReadMessage, UserInfo } from '../topbar/types'
import MomentsDropdown from './TopbarMomentsPop.vue'
import HistoryDropdown from './TopbarHistoryPop.vue'
import { updateInterval } from './notify'
import { getUserID } from '~/utils'
const mid = getUserID() || ''
const userInfo = reactive<UserInfo | {}>({}) as UnwrapNestedRefs<UserInfo>
const showLogoDropDown = ref<boolean>(false)
const showUserPanel = ref<boolean>(false)
const showChannelsPop = ref<boolean>(false)
const showUserPanelPop = ref<boolean>(false)
const showTopbarMask = ref<boolean>(false)
const showNotificationsDropDown = ref<boolean>(false)
const showMomentsDropDown = ref<boolean>(false)
const showUploadDropDown = ref<boolean>(false)
const showHistoryDropDown = ref<boolean>(false)
const showNotificationsPop = ref<boolean>(false)
const showMomentsPop = ref<boolean>(false)
const showFavoritesPop = ref<boolean>(false)
const showUploadPop = ref<boolean>(false)
const showHistoryPop = ref<boolean>(false)
const isLogin = ref<boolean>(!!getUserID())
const unReadMessage = reactive<UnReadMessage | {}>({}) as UnwrapNestedRefs<UnReadMessage>
const unReadDm = reactive<UnReadDm | {}>({} as UnwrapNestedRefs<UnReadDm>)
@@ -27,14 +26,14 @@ const avatarImg = ref<HTMLImageElement>() as Ref<HTMLImageElement>
const avatarShadow = ref<HTMLImageElement>() as Ref<HTMLImageElement>
watch(
() => showNotificationsDropDown,
() => showNotificationsPop,
(newValue, oldValue) => {
getUnreadMessageCount()
},
)
watch(
() => showMomentsDropDown,
() => showMomentsPop,
(newValue, oldValue) => {
getNewMomentsCount()
},
@@ -64,22 +63,22 @@ function initUserPanel() {
function showLogoMenuDropdown() {
logo.value.classList.add('hover')
showLogoDropDown.value = true
showChannelsPop.value = true
}
function closeLogoMenuDropdown() {
logo.value.classList.remove('hover')
showLogoDropDown.value = false
showChannelsPop.value = false
}
function openUserPanel() {
showUserPanel.value = true
showUserPanelPop.value = true
avatarImg.value.classList.add('hover')
avatarShadow.value.classList.add('hover')
}
function closeUserPanel() {
showUserPanel.value = false
showUserPanelPop.value = false
avatarImg.value.classList.remove('hover')
avatarShadow.value.classList.remove('hover')
}
@@ -190,7 +189,7 @@ function getNewMomentsCount() {
</div>
<Transition name="slide">
<TopbarChannelsPop
v-if="showLogoDropDown"
v-if="showChannelsPop"
class="bew-popover"
pos="!left-0 !top-70px"
transform="!translate-x-0"
@@ -254,7 +253,7 @@ function getNewMomentsCount() {
/>
<Transition name="slide">
<TopbarUserPanelPop
v-if="showUserPanel"
v-if="showUserPanelPop"
ref="userPanelDropdown"
:user-info="userInfo"
after:h="!0"
@@ -266,8 +265,8 @@ function getNewMomentsCount() {
<!-- Notifications -->
<div
class="right-side-item"
@mouseenter="showNotificationsDropDown = true"
@mouseleave="showNotificationsDropDown = false"
@mouseenter="showNotificationsPop = true"
@mouseleave="showNotificationsPop = false"
>
<div v-if="unReadMessageCount !== 0" class="unread-message">
{{ unReadMessageCount > 999 ? '999+' : unReadMessageCount }}
@@ -282,7 +281,7 @@ function getNewMomentsCount() {
<Transition name="slide">
<TopbarNotificationsPop
v-if="showNotificationsDropDown"
v-if="showNotificationsPop"
ref="notificationsDropdown"
class="bew-popover"
/>
@@ -292,8 +291,8 @@ function getNewMomentsCount() {
<!-- Moments -->
<div
class="right-side-item"
@mouseenter="showMomentsDropDown = true"
@mouseleave="showMomentsDropDown = false"
@mouseenter="showMomentsPop = true"
@mouseleave="showMomentsPop = false"
>
<div v-if="newMomentsCount !== 0" class="unread-message">
{{ newMomentsCount > 999 ? '999+' : newMomentsCount }}
@@ -307,12 +306,16 @@ function getNewMomentsCount() {
</a>
<Transition name="slide">
<TopbarMomentsPop v-if="showMomentsDropDown" class="bew-popover" />
<TopbarMomentsPop v-if="showMomentsPop" class="bew-popover" />
</Transition>
</div>
<!-- Favorites -->
<div class="right-side-item">
<div
class="right-side-item"
@mouseenter="showFavoritesPop = true"
@mouseleave="showFavoritesPop = false"
>
<a
:href="`https://space.bilibili.com/${mid}/favlist`"
target="_blank"
@@ -320,13 +323,17 @@ function getNewMomentsCount() {
>
<tabler:star />
</a>
<Transition name="slide">
<TopbarFavoritesPop v-if="showFavoritesPop" class="bew-popover" />
</Transition>
</div>
<!-- History -->
<div
class="right-side-item"
@mouseenter="showHistoryDropDown = true"
@mouseleave="showHistoryDropDown = false"
@mouseenter="showHistoryPop = true"
@mouseleave="showHistoryPop = false"
>
<a
href="https://www.bilibili.com/account/history"
@@ -337,7 +344,7 @@ function getNewMomentsCount() {
</a>
<Transition name="slide">
<TopbarHistoryPop v-if="showHistoryDropDown" class="bew-popover" />
<TopbarHistoryPop v-if="showHistoryPop" class="bew-popover" />
</Transition>
</div>
@@ -353,8 +360,8 @@ function getNewMomentsCount() {
</div>
<div
class="right-side-item"
@mouseenter="showUploadDropDown = true"
@mouseleave="showUploadDropDown = false"
@mouseenter="showUploadPop = true"
@mouseleave="showUploadPop = false"
>
<a
href="https://member.bilibili.com/platform/upload/video/frame"
@@ -374,7 +381,7 @@ function getNewMomentsCount() {
<Transition name="slide">
<TopbarUploadPop
v-if="showUploadDropDown"
v-if="showUploadPop"
class="bew-popover"
pos="!left-auto !-right-2"
transform="!translate-x-0"

View File

@@ -0,0 +1,422 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import type { Ref } from 'vue'
import { onMounted, reactive, ref, watch } from 'vue'
import { useDateFormat } from '@vueuse/core'
import type { HistoryItem } from './types'
import { HistoryType } from './types'
import { calcCurrentTime } from '~/utils'
const { t } = useI18n()
const historys = reactive<Array<HistoryItem>>([])
const historyTabs = reactive([
{
id: 0,
name: t('topbar.moments_dropdown.tabs.videos'),
isSelected: true,
},
{
id: 1,
name: t('topbar.moments_dropdown.tabs.live'),
isSelected: false,
},
{
id: 2,
name: t('topbar.moments_dropdown.tabs.articles'),
isSelected: false,
},
])
/**
* Active tab (0: archive, 1: live, 2: article)
*/
const activatedTab = ref<number>(0)
const isLoading = ref<boolean>(false)
// when noMoreContent is true, the user can't scroll down to load more content
const noMoreContent = ref<boolean>(false)
const livePage = ref<number>(1)
const historysWrap = ref<HTMLElement>() as Ref<HTMLElement>
watch(activatedTab, (newVal: number, oldVal: number) => {
if (newVal === oldVal)
return
historys.length = 0
if (historysWrap.value)
scrollToTop(historysWrap.value, 300)
if (newVal === 0) {
getHistoryList(HistoryType.Archive)
}
else if (newVal === 1) {
livePage.value = 1
getHistoryList(HistoryType.Live)
}
else if (newVal === 2) {
getHistoryList(HistoryType.Article)
}
})
onMounted(() => {
// getHistoryList(HistoryType.Archive)
// if (historysWrap.value) {
// historysWrap.value.addEventListener('scroll', () => {
// // When you scroll to the bottom, they will automatically
// // add the next page of data to the history list
// if (
// historysWrap.value.clientHeight + historysWrap.value.scrollTop
// >= historysWrap.value.scrollHeight - 20
// && historys.length > 0
// && !isLoading.value
// ) {
// if (activatedTab.value === 0 && !noMoreContent.value) {
// getHistoryList(
// HistoryType.Archive,
// historys[historys.length - 1].view_at,
// )
// }
// else if (activatedTab.value === 1 && !noMoreContent.value) {
// getHistoryList(
// HistoryType.Live,
// historys[historys.length - 1].view_at,
// )
// }
// else if (activatedTab.value === 2 && !noMoreContent.value) {
// getHistoryList(
// HistoryType.Article,
// historys[historys.length - 1].view_at,
// )
// }
// }
// })
// }
})
function onClickTab(tabId: number) {
// Prevent changing tab when loading, cuz it will cause a bug
if (isLoading.value)
return
activatedTab.value = tabId
historyTabs.forEach((tab) => {
tab.isSelected = tab.id === tabId
})
}
/**
* Return the URL of the history item
* @param item history item
* @return {string} url
*/
function getHistoryUrl(item: HistoryItem) {
// Video
if (activatedTab.value === 0)
return item.history.bvid
// Live
else if (activatedTab.value === 1)
return `//live.bilibili.com/${item.history.oid}`
// Article
else if (activatedTab.value === 2)
return `/read/cv${item.history.oid}`
return ''
}
/**
* Get history list
* @param {HistoryType} type
* @param {number} viewAt Last viewed timestamp
*/
function getHistoryList(type: HistoryType, viewAt = 0 as number) {
isLoading.value = true
browser.runtime
.sendMessage({
contentScriptQuery: 'getHistoryList',
type,
viewAt,
})
.then((res) => {
if (res.code === 0) {
if (historys.length !== 0 && res.data.list.length < 20) {
isLoading.value = false
noMoreContent.value = true
return
}
res.data.list.forEach((item: HistoryItem) => {
historys.push(item)
})
noMoreContent.value = false
}
isLoading.value = false
})
}
/**
* smooth scroll to the top of the html element
*/
function scrollToTop(element: HTMLElement, duration: number) {
// cancel if already on top
if (element.scrollTop === 0)
return
const cosParameter = element.scrollTop / 2
let scrollCount = 0
let oldTimestamp = 0
function step(newTimestamp: number) {
if (oldTimestamp !== 0) {
// if duration is 0 scrollCount will be Infinity
scrollCount += (Math.PI * (newTimestamp - oldTimestamp)) / duration
if (scrollCount >= Math.PI)
return (element.scrollTop = 0)
element.scrollTop = cosParameter + cosParameter * Math.cos(scrollCount)
}
oldTimestamp = newTimestamp
window.requestAnimationFrame(step)
}
window.requestAnimationFrame(step)
}
</script>
<template>
<div
bg="$bew-content-solid-1"
w="380px"
rounded="$bew-radius"
pos="relative"
style="box-shadow: var(--bew-shadow-2)"
>
<!-- top bar -->
<div
flex="~"
justify="between"
p="y-4 x-6"
pos="fixed top-0 left-0"
w="full"
bg="$bew-content-1"
z="2"
border="!rounded-t-$bew-radius"
style="backdrop-filter: var(--bew-filter-glass)"
>
<div flex="~">
<div
v-for="tab in historyTabs"
:key="tab.id"
m="r-4"
transition="all duration-300"
class="tab"
:class="tab.isSelected ? 'tab-selected' : ''"
cursor="pointer"
@click="onClickTab(tab.id)"
>
{{ tab.name }}
</div>
</div>
<a href="https://t.bilibili.com/" target="_blank" flex="~" items="center">
<span text="sm">{{ $t('common.view_all') }}</span>
</a>
</div>
<!-- historys wrapper -->
<div
ref="historysWrap"
flex="~ col gap-4"
h="430px"
overflow="y-scroll"
p="x-4"
>
<!-- loading -->
<Loading
v-if="isLoading && historys.length === 0"
pos="absolute left-0"
bg="$bew-content-1"
z="1"
w="full"
h="full"
flex="~"
items="center"
border="rounded-$bew-radius"
/>
<!-- empty -->
<Empty v-if="!isLoading && historys.length === 0" w="full" h="full" />
<!-- historys -->
<transition-group name="list">
<a
v-for="historyItem in historys"
:key="historyItem.kid"
:href="getHistoryUrl(historyItem)"
target="_blank"
hover:bg="$bew-fill-2"
border="rounded-$bew-radius"
p="2"
m="first:t-50px last:b-4"
class="group"
transition="duration"
>
<section flex="~ gap-4" align="item-start">
<!-- Video cover, live cover, ariticle cover -->
<div
bg="$bew-fill-1"
w="150px"
flex="shrink-0"
border="rounded-$bew-radius-half"
overflow="hidden"
>
<!-- Video -->
<template v-if="activatedTab === 0">
<div pos="relative">
<img
w="150px"
class="aspect-video"
:src="`${historyItem.cover}@256w_144h_1c`"
:alt="historyItem.title"
bg="contain"
>
<div
pos="absolute bottom-0 right-0"
bg="black opacity-60"
m="1"
p="x-2 y-1"
text="white xs"
border="rounded-full"
opacity="0"
group-hover:opacity="100"
>
{{
`${calcCurrentTime(historyItem.progress)} /
${calcCurrentTime(historyItem.duration)}`
}}
</div>
</div>
<Progress
:percentage="
(historyItem.progress / historyItem.duration) * 100
"
/>
</template>
<!-- Live -->
<template v-else-if="activatedTab === 1">
<div pos="relative">
<img
w="150px"
class="aspect-video"
:src="`${historyItem.cover}@256w_144h_1c`"
:alt="historyItem.title"
bg="contain"
>
<div
v-if="historyItem.live_status === 1"
pos="absolute top-0 left-0"
bg="rose-600"
text="xs white"
p="x-2 y-1"
m="1"
border="rounded-$bew-radius-half"
font="semibold"
>
LIVE
</div>
<div
v-else
pos="absolute top-0 left-0"
bg="gray-500 opacity-55"
text="xs white"
p="x-2 y-1"
m="1"
border="rounded-$bew-radius-half"
>
Offline
</div>
</div>
</template>
<!-- Article -->
<div v-else-if="activatedTab === 2">
<img
w="150px"
class="aspect-video"
:src="`${
Array.isArray(historyItem.covers)
? historyItem.covers[0]
: ''
}@256w_144h_1c`"
:alt="historyItem.title"
bg="contain"
>
</div>
</div>
<!-- Description -->
<div>
<h3
style="
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
display: -webkit-box;
"
overflow="hidden"
text="overflow-ellipsis"
>
{{ historyItem.title }}
</h3>
<div text="$bew-text-2 sm" m="t-4" flex="~" align="items-center">
{{ historyItem.author_name }}
<span
v-if="historyItem.live_status === 1"
text="$bew-theme-color"
m="l-2"
><tabler:live-photo />
Live
</span>
</div>
<p text="$bew-text-2 sm">
{{
useDateFormat(
historyItem.view_at * 1000,
'YYYY-MM-DD HH:mm:ss',
).value
}}
</p>
</div>
</section>
</a>
</transition-group>
<!-- loading -->
<loading v-if="isLoading && historys.length !== 0" m="-t-4" />
</div>
</div>
</template>
<style lang="scss" scoped>
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease;
}
.list-enter-from,
.list-leave-to {
@apply opacity-0 transform translate-y-2 transform-gpu;
}
.tab {
@apply relative text-$bew-text-2;
&::after {
@apply absolute bottom-0 left-0 w-full h-12px bg-$bew-theme-color opacity-0 transform scale-x-0 -z-1 transition-all duration-300;
content: '';
}
}
.tab-selected {
@apply font-bold text-$bew-text-1;
&::after {
@apply scale-x-80 opacity-40;
}
}
</style>