mirror of
https://github.com/BewlyBewly/BewlyBewly.git
synced 2025-04-14 13:15:29 +00:00
feat: notifications drawer
This commit is contained in:
@@ -107,6 +107,9 @@ function handleToggleDockItem(dockItem: any) {
|
|||||||
<SettingsItem :title="$t('settings.top_bar_icon_badges')">
|
<SettingsItem :title="$t('settings.top_bar_icon_badges')">
|
||||||
<Select v-model="settings.topBarIconBadges" :options="topBarIconBadgesOptions" w="full" />
|
<Select v-model="settings.topBarIconBadges" :options="topBarIconBadgesOptions" w="full" />
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
<SettingsItem :title="$t('settings.open_notifications_page_as_drawer')">
|
||||||
|
<Radio v-model="settings.openNotificationsPageAsDrawer" />
|
||||||
|
</SettingsItem>
|
||||||
</SettingsItemGroup>
|
</SettingsItemGroup>
|
||||||
|
|
||||||
<SettingsItemGroup :title="$t('settings.group_dock')">
|
<SettingsItemGroup :title="$t('settings.group_dock')">
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import FavoritesPop from './components/FavoritesPop.vue'
|
|||||||
import HistoryPop from './components/HistoryPop.vue'
|
import HistoryPop from './components/HistoryPop.vue'
|
||||||
import MomentsPop from './components/MomentsPop.vue'
|
import MomentsPop from './components/MomentsPop.vue'
|
||||||
import MorePop from './components/MorePop.vue'
|
import MorePop from './components/MorePop.vue'
|
||||||
|
import NotificationsDrawer from './components/NotificationsDrawer.vue'
|
||||||
import NotificationsPop from './components/NotificationsPop.vue'
|
import NotificationsPop from './components/NotificationsPop.vue'
|
||||||
import UploadPop from './components/UploadPop.vue'
|
import UploadPop from './components/UploadPop.vue'
|
||||||
import UserPanelPop from './components/UserPanelPop.vue'
|
import UserPanelPop from './components/UserPanelPop.vue'
|
||||||
@@ -55,6 +56,10 @@ const avatarShadow = ref<HTMLImageElement>() as Ref<HTMLImageElement>
|
|||||||
const scrollTop = ref<number>(0)
|
const scrollTop = ref<number>(0)
|
||||||
const oldScrollTop = ref<number>(0)
|
const oldScrollTop = ref<number>(0)
|
||||||
|
|
||||||
|
const drawerVisible = reactive({
|
||||||
|
notifications: false,
|
||||||
|
})
|
||||||
|
|
||||||
const isSearchPage = computed((): boolean => {
|
const isSearchPage = computed((): boolean => {
|
||||||
if (/https?:\/\/search.bilibili.com\/.*$/.test(location.href))
|
if (/https?:\/\/search.bilibili.com\/.*$/.test(location.href))
|
||||||
return true
|
return true
|
||||||
@@ -823,11 +828,14 @@ defineExpose({
|
|||||||
class="unread-dot"
|
class="unread-dot"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<ALink
|
<ALink
|
||||||
|
:href="settings.openNotificationsPageAsDrawer ? undefined : 'https://message.bilibili.com'"
|
||||||
:class="{ 'white-icon': forceWhiteIcon }"
|
:class="{ 'white-icon': forceWhiteIcon }"
|
||||||
href="https://message.bilibili.com"
|
|
||||||
:title="$t('topbar.notifications')"
|
:title="$t('topbar.notifications')"
|
||||||
type="topBar"
|
type="topBar"
|
||||||
|
:custom-click-event="settings.openNotificationsPageAsDrawer"
|
||||||
|
@click="drawerVisible.notifications = true"
|
||||||
>
|
>
|
||||||
<div i-tabler:bell />
|
<div i-tabler:bell />
|
||||||
</ALink>
|
</ALink>
|
||||||
@@ -901,6 +909,8 @@ defineExpose({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<NotificationsDrawer v-if="drawerVisible.notifications" @close="drawerVisible.notifications = false" />
|
||||||
</header>
|
</header>
|
||||||
</Transition>
|
</Transition>
|
||||||
</template>
|
</template>
|
||||||
@@ -1033,19 +1043,24 @@ defineExpose({
|
|||||||
.right-side-item {
|
.right-side-item {
|
||||||
--uno: "relative text-$bew-text-1 flex items-center";
|
--uno: "relative text-$bew-text-1 flex items-center";
|
||||||
|
|
||||||
&:not(.avatar) a {
|
&:not(.avatar) a,
|
||||||
|
& .notifications {
|
||||||
--uno: "text-lg grid place-items-center rounded-40px duration-300 relative z-5";
|
--uno: "text-lg grid place-items-center rounded-40px duration-300 relative z-5";
|
||||||
--uno: "h-34px w-34px";
|
--uno: "h-34px w-34px";
|
||||||
filter: drop-shadow(0 0 4px var(--bew-bg));
|
filter: drop-shadow(0 0 4px var(--bew-bg));
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active a,
|
&.active a,
|
||||||
& a:hover {
|
& a:hover,
|
||||||
|
& .notifications:hover,
|
||||||
|
& .notifications:active {
|
||||||
--uno: "bg-$bew-fill-2";
|
--uno: "bg-$bew-fill-2";
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active a.white-icon,
|
&.active a.white-icon,
|
||||||
& a:hover.white-icon {
|
& a:hover.white-icon,
|
||||||
|
& .notifications:hover.white-icon,
|
||||||
|
& .notifications:active.white-icon {
|
||||||
--uno: "bg-white bg-opacity-20";
|
--uno: "bg-white bg-opacity-20";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
206
src/components/TopBar/components/NotificationsDrawer.vue
Normal file
206
src/components/TopBar/components/NotificationsDrawer.vue
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { onKeyStroke } from '@vueuse/core'
|
||||||
|
|
||||||
|
import { settings } from '~/logic'
|
||||||
|
|
||||||
|
// TODO: support shortcuts like `Ctrl+Alt+T` to open in new tab, `Esc` to close
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'close'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const headerShow = ref(false)
|
||||||
|
const iframeRef = ref<HTMLIFrameElement | null>(null)
|
||||||
|
const currentUrl = ref<string>('https://message.bilibili.com/')
|
||||||
|
const delayCloseTimer = ref<NodeJS.Timeout | null>(null)
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
show.value = true
|
||||||
|
headerShow.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
iframeRef.value?.focus()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
releaseIframeResources()
|
||||||
|
})
|
||||||
|
|
||||||
|
async function handleClose() {
|
||||||
|
if (delayCloseTimer.value) {
|
||||||
|
clearTimeout(delayCloseTimer.value)
|
||||||
|
}
|
||||||
|
await releaseIframeResources()
|
||||||
|
show.value = false
|
||||||
|
headerShow.value = false
|
||||||
|
delayCloseTimer.value = setTimeout(() => {
|
||||||
|
emit('close')
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function releaseIframeResources() {
|
||||||
|
// Clear iframe content
|
||||||
|
currentUrl.value = 'about:blank'
|
||||||
|
/**
|
||||||
|
* eg: When use 'iframeRef.value?.contentWindow?.document' of t.bilibili.com iframe on bilibili.com, there may be cross domain issues
|
||||||
|
* set the src to 'about:blank' to avoid this issue, it also can release the memory
|
||||||
|
*/
|
||||||
|
if (iframeRef.value) {
|
||||||
|
iframeRef.value.src = 'about:blank'
|
||||||
|
}
|
||||||
|
await nextTick()
|
||||||
|
iframeRef.value?.contentWindow?.close()
|
||||||
|
|
||||||
|
// Remove iframe from the DOM
|
||||||
|
iframeRef.value?.parentNode?.removeChild(iframeRef.value)
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
// Nullify the reference
|
||||||
|
iframeRef.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOpenInNewTab() {
|
||||||
|
if (iframeRef.value) {
|
||||||
|
window.open(iframeRef.value.contentWindow?.location.href.replace(/\/$/, ''), '_blank')
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isEscPressed = ref<boolean>(false)
|
||||||
|
const escPressedTimer = ref<NodeJS.Timeout | null>(null)
|
||||||
|
const disableEscPress = ref<boolean>(false)
|
||||||
|
|
||||||
|
nextTick(() => {
|
||||||
|
onKeyStroke('Escape', (e: KeyboardEvent) => {
|
||||||
|
e.preventDefault()
|
||||||
|
if (settings.value.closeDrawerWithoutPressingEscAgain) {
|
||||||
|
clearTimeout(escPressedTimer.value!)
|
||||||
|
handleClose()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (disableEscPress.value)
|
||||||
|
return
|
||||||
|
if (isEscPressed.value) {
|
||||||
|
handleClose()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isEscPressed.value = true
|
||||||
|
if (escPressedTimer.value) {
|
||||||
|
clearTimeout(escPressedTimer.value)
|
||||||
|
}
|
||||||
|
escPressedTimer.value = setTimeout(() => {
|
||||||
|
isEscPressed.value = false
|
||||||
|
}, 1300)
|
||||||
|
}
|
||||||
|
}, { target: iframeRef.value?.contentWindow })
|
||||||
|
})
|
||||||
|
|
||||||
|
// const keys = useMagicKeys()
|
||||||
|
// const ctrlAltT = keys['Ctrl+Alt+T']
|
||||||
|
|
||||||
|
// watch(() => ctrlAltT, (value) => {
|
||||||
|
// if (value) {
|
||||||
|
// handleOpenInNewTab()
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
pos="fixed top-0 left-0" of-hidden w-full h-full
|
||||||
|
z-999999
|
||||||
|
>
|
||||||
|
<!-- Mask -->
|
||||||
|
<Transition name="fade">
|
||||||
|
<div
|
||||||
|
v-if="show"
|
||||||
|
pos="absolute bottom-0 left-0" w-full h-full bg="black opacity-60"
|
||||||
|
@click="handleClose"
|
||||||
|
/>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<Transition name="fade">
|
||||||
|
<div
|
||||||
|
v-if="headerShow"
|
||||||
|
pos="relative top-0" flex="~ items-center justify-end gap-2"
|
||||||
|
max-w="$bew-page-max-width" w-full h="$bew-top-bar-height"
|
||||||
|
m-auto px-4
|
||||||
|
pointer-events-none
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
style="
|
||||||
|
--b-button-color: var(--bew-elevated-solid);
|
||||||
|
--b-button-color-hover: var(--bew-elevated-solid-hover);
|
||||||
|
"
|
||||||
|
pointer-events-auto
|
||||||
|
@click="handleOpenInNewTab"
|
||||||
|
>
|
||||||
|
<template #left>
|
||||||
|
<i i-mingcute:external-link-line />
|
||||||
|
</template>
|
||||||
|
{{ $t('iframe_drawer.open_in_new_tab') }}
|
||||||
|
<!-- <div flex="~">
|
||||||
|
<kbd>Ctrl</kbd><kbd>Alt</kbd><kbd>T</kbd>
|
||||||
|
</div> -->
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-if="!isEscPressed"
|
||||||
|
style="
|
||||||
|
--b-button-color: var(--bew-elevated-solid);
|
||||||
|
--b-button-color-hover: var(--bew-elevated-solid-hover);
|
||||||
|
"
|
||||||
|
pointer-events-auto
|
||||||
|
@click="handleClose"
|
||||||
|
>
|
||||||
|
<template #left>
|
||||||
|
<i i-mingcute:close-line />
|
||||||
|
</template>
|
||||||
|
{{ $t('iframe_drawer.close') }}
|
||||||
|
<kbd>Esc</kbd>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
v-else
|
||||||
|
type="error"
|
||||||
|
@click="handleClose"
|
||||||
|
>
|
||||||
|
<template #left>
|
||||||
|
<i i-mingcute:close-line />
|
||||||
|
</template>
|
||||||
|
{{ $t('iframe_drawer.press_esc_again_to_close') }}
|
||||||
|
<kbd>Esc</kbd>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
|
||||||
|
<!-- Iframe -->
|
||||||
|
<Transition name="drawer">
|
||||||
|
<div
|
||||||
|
v-if="show"
|
||||||
|
:pos="`absolute ${headerShow ? 'top-$bew-top-bar-height' : 'top-0'} left-0`" of-hidden bg="$bew-bg"
|
||||||
|
rounded="t-$bew-radius" w-full h-full
|
||||||
|
>
|
||||||
|
<iframe
|
||||||
|
ref="iframeRef"
|
||||||
|
:src="currentUrl"
|
||||||
|
frameborder="0"
|
||||||
|
pointer-events-auto
|
||||||
|
pos="relative left-0"
|
||||||
|
w-full h-full
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.drawer-enter-active,
|
||||||
|
.drawer-leave-active {
|
||||||
|
transition: transform 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drawer-enter-from,
|
||||||
|
.drawer-leave-to {
|
||||||
|
transform: translateY(100%);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -117,6 +117,8 @@ export function isSupportedIframePages(): boolean {
|
|||||||
|| /https?:\/\/(?:www\.)?bilibili\.com\/list\/watchlater.*/.test(currentUrl)
|
|| /https?:\/\/(?:www\.)?bilibili\.com\/list\/watchlater.*/.test(currentUrl)
|
||||||
// favorite playlist
|
// favorite playlist
|
||||||
|| /https?:\/\/(?:www\.)?bilibili\.com\/list\/ml.*/.test(currentUrl)
|
|| /https?:\/\/(?:www\.)?bilibili\.com\/list\/ml.*/.test(currentUrl)
|
||||||
|
// notifications page, for `Open the notifications page as a drawer`
|
||||||
|
|| /https?:\/\/message\.bilibili\.com\.*/.test(currentUrl)
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return true
|
return true
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ export interface Settings {
|
|||||||
showBewlyOrBiliTopBarSwitcher: boolean
|
showBewlyOrBiliTopBarSwitcher: boolean
|
||||||
showBewlyOrBiliPageSwitcher: boolean
|
showBewlyOrBiliPageSwitcher: boolean
|
||||||
topBarIconBadges: 'number' | 'dot' | 'none'
|
topBarIconBadges: 'number' | 'dot' | 'none'
|
||||||
|
openNotificationsPageAsDrawer: boolean
|
||||||
|
|
||||||
alwaysUseDock: boolean
|
alwaysUseDock: boolean
|
||||||
autoHideDock: boolean
|
autoHideDock: boolean
|
||||||
dockPosition: 'left' | 'right' | 'bottom'
|
dockPosition: 'left' | 'right' | 'bottom'
|
||||||
@@ -137,6 +139,8 @@ export const originalSettings: Settings = {
|
|||||||
showBewlyOrBiliTopBarSwitcher: true,
|
showBewlyOrBiliTopBarSwitcher: true,
|
||||||
showBewlyOrBiliPageSwitcher: true,
|
showBewlyOrBiliPageSwitcher: true,
|
||||||
topBarIconBadges: 'number',
|
topBarIconBadges: 'number',
|
||||||
|
openNotificationsPageAsDrawer: true,
|
||||||
|
|
||||||
alwaysUseDock: false,
|
alwaysUseDock: false,
|
||||||
autoHideDock: false,
|
autoHideDock: false,
|
||||||
dockPosition: 'right',
|
dockPosition: 'right',
|
||||||
|
|||||||
Reference in New Issue
Block a user