feat: add video card open mode config && open video in drawer (#984)

* feat: video card open mode

* update

* update

* fix: non-homepage show the bewly homepage

* fix(video-card): click the more icon will trigger opening drawer effect

* feat(video-drawer): improve ui and functionally

* feat(i18n): add i18n support for video card link opening behavior setting

* chore: reorder settings
This commit is contained in:
Hakadao
2024-09-08 13:36:42 +08:00
committed by GitHub
parent 35bd528524
commit bb3c6b5e2c
13 changed files with 313 additions and 43 deletions

View File

@@ -40,6 +40,10 @@ common:
remark: 备注
operations: 操作
iframe_drawer:
open_in_new_tab: 在新标签页打开
close: 关闭
settings:
title: 设置
@@ -88,6 +92,11 @@ settings:
enable_grid_layout_switcher: 启用页面网格布局切换器
enable_horizontal_scrolling: 启用横向滚动
enable_horizontal_scrolling_desc: 启用后,鼠标滚动时将直接滚动横向滚动列表。
video_card_link_opening_behavior: 视频卡片和番剧卡片链接打开行为
video_card_link_opening_behavior_opt:
drawer: 抽屉打开
new_tab: 新索引标籤
enable_video_preview: 启用视频预览功能
enable_video_ctrl_bar_on_video_card: 在视频卡片上显示视频控制栏
hover_video_card_delayed: 将鼠标悬停在视频卡片上时延迟视频预览

View File

@@ -40,6 +40,10 @@ common:
remark: 備註
operations: 操作
iframe_drawer:
open_in_new_tab: 在新索引標籤開啟連結
close: 關閉
settings:
title: 設定
@@ -88,6 +92,11 @@ settings:
enable_grid_layout_switcher: 啟用版面格線佈局切換器
enable_horizontal_scrolling: 啟用橫向捲動
enable_horizontal_scrolling_desc: 啟用後,滑鼠捲動時將直接捲動橫向捲軸列表。
video_card_link_opening_behavior: 影片和番劇卡片連結開啟行為
video_card_link_opening_behavior_opt:
drawer: 抽屜開啟
new_tab: 新索引標籤
enable_video_preview: 啟用影片預覽功能
enable_video_ctrl_bar_on_video_card: 在影片卡片上顯示影片控制欄
hover_video_card_delayed: 將滑鼠懸停在影片卡片上時延遲影片預覽

View File

@@ -40,6 +40,10 @@ common:
remark: Remark
operations: Operations
iframe_drawer:
open_in_new_tab: Open in new tab
close: Close
settings:
title: Settings
@@ -88,6 +92,11 @@ settings:
enable_grid_layout_switcher: Enable grid layout switcher
enable_horizontal_scrolling: Enable horizontal scrolling
enable_horizontal_scrolling_desc: After enabling, mouse scrolling will directly scroll the horizontal scroll list.
video_card_link_opening_behavior: Video card and bangumi card link opening behavior
video_card_link_opening_behavior_opt:
drawer: Open in drawer
new_tab: New tab
enable_video_preview: Enable video preview feature
enable_video_ctrl_bar_on_video_card: Display the video control bar on the video card
hover_video_card_delayed: Delayed video preview on hover over the video card

View File

@@ -40,6 +40,10 @@ common:
remark: 備註
operations: 操作
iframe_drawer:
open_in_new_tab: 喺新嘅分頁度打開連結
close: 關閉
settings:
title: 設定
@@ -88,6 +92,11 @@ settings:
enable_grid_layout_switcher: 使用版面格線佈局切換器
enable_horizontal_scrolling: 使用橫向捲動
enable_horizontal_scrolling_desc: 用咗之後,轆緊滑鼠就可以直接打橫掃水平捲軸列表。
video_card_link_opening_behavior: 影片同番劇卡片連結開啓行爲
video_card_link_opening_behavior_opt:
drawer: 喺櫃桶度開啓
new_tab: 新開分頁
enable_video_preview: 使用影片預覽功能
enable_video_ctrl_bar_on_video_card: 喺影片卡片上顯示影片控制欄
hover_video_card_delayed: 滑鼠停留喺影片卡片嗰陣遲啲進行影片預覽

View File

@@ -1,11 +1,13 @@
<script setup lang="ts">
import { useBewlyApp } from '~/composables/useAppProvider'
import { useDark } from '~/composables/useDark'
import { settings } from '~/logic'
import { numFormatter } from '~/utils/dataFormatter'
import { removeHttpFromUrl } from '~/utils/main'
import BangumiCardSkeleton from './BangumiCardSkeleton.vue'
defineProps<{
const props = defineProps<{
skeleton?: boolean
bangumi: Bangumi
horizontal?: boolean
@@ -31,6 +33,15 @@ interface Bangumi {
}
const { isDark } = useDark()
const { openIframeDrawer } = useBewlyApp()
function handleClick(event: MouseEvent) {
if (settings.value.videoCardLinkOpenMode === 'drawer' && props.bangumi.url) {
event.preventDefault()
openIframeDrawer(props.bangumi.url)
}
}
</script>
<template>
@@ -46,6 +57,7 @@ const { isDark } = useDark()
content-visibility-auto intrinsic-size-400px
transition="all ease-in-out 300"
rounded="$bew-radius" h-fit
@click="handleClick"
>
<!-- Cover -->
<div

View File

@@ -0,0 +1,175 @@
<script setup lang="ts">
import { onKeyStroke } from '@vueuse/core'
import { isHomePage } from '~/utils/main'
// TODO: support shortcuts like `Ctrl+Alt+T` to open in new tab, `Esc` to close
const props = defineProps<{
url: string
title?: string
}>()
const emit = defineEmits<{
(e: 'close'): void
}>()
const show = ref(false)
const iframeRef = ref<HTMLIFrameElement | null>(null)
const currentUrl = ref<string>(props.url)
const delayCloseTimer = ref<NodeJS.Timeout | null>(null)
onMounted(async () => {
history.pushState({}, '', props.url)
show.value = true
await nextTick()
iframeRef.value?.contentWindow?.addEventListener('pushstate', updateCurrentUrl)
window.addEventListener('popstate', updateIframeUrl)
})
onUnmounted(() => {
history.pushState({}, '', 'https://www.bilibili.com')
iframeRef.value?.contentWindow?.removeEventListener('pushstate', updateCurrentUrl)
window.removeEventListener('popstate', updateIframeUrl)
})
function updateCurrentUrl() {
if (iframeRef.value?.contentWindow) {
try {
currentUrl.value = iframeRef.value.contentWindow.location.href
history.pushState({}, '', currentUrl.value)
}
catch (error) {
console.error('Unable to access iframe URL:', error)
}
}
}
function updateIframeUrl() {
if (isHomePage()) {
handleClose()
return
}
if (iframeRef.value?.contentWindow) {
iframeRef.value.contentWindow.location.replace(location.href)
// iframeRef.value.contentWindow.location.reload()
}
}
function handleClose() {
if (delayCloseTimer.value) {
clearTimeout(delayCloseTimer.value)
}
show.value = false
delayCloseTimer.value = setTimeout(() => {
emit('close')
}, 300)
}
function handleOpenInNewTab() {
window.open(props.url, '_blank')
}
onKeyStroke('Escape', (e: KeyboardEvent) => {
e.preventDefault()
handleClose()
})
// const keys = useMagicKeys()
// const ctrlAltT = keys['Ctrl+Alt+T']
// watch(() => ctrlAltT, (value) => {
// if (value) {
// console.log('ctrlAltT', value)
// handleOpenInNewTab()
// }
// })
</script>
<template>
<div
pos="absolute 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="show"
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
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>
</div>
</Transition>
<!-- Iframe -->
<Transition name="drawer">
<div
v-if="show"
pos="absolute top-$bew-top-bar-height left-0" of-hidden bg="$bew-bg"
rounded="t-$bew-radius" w-full h-full
>
<iframe
ref="iframeRef"
:src="url"
frameborder="0"
pointer-events-auto
pos="absolute bottom-$bew-top-bar-height 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>

View File

@@ -11,20 +11,33 @@ const { t, locale } = useI18n()
const langOptions = computed(() => {
return [
{
value: 'en',
label: t('settings.select_language_opt.english'),
value: 'en',
},
{
value: 'cmn-CN',
label: t('settings.select_language_opt.mandarin_cn'),
value: 'cmn-CN',
},
{
value: 'cmn-TW',
label: t('settings.select_language_opt.mandarin_tw'),
value: 'cmn-TW',
},
{
value: 'jyut',
label: t('settings.select_language_opt.jyut'),
value: 'jyut',
},
]
})
const videoCardOpenModeOptions = computed(() => {
return [
{
label: t('settings.video_card_link_opening_behavior_opt.drawer'),
value: 'drawer',
},
{
label: t('settings.video_card_link_opening_behavior_opt.new_tab'),
value: 'new_tab',
},
]
})
@@ -52,10 +65,6 @@ watch(() => settings.value.language, (newValue) => {
<SettingsItem :title="$t('settings.enable_horizontal_scrolling')" :desc="$t('settings.enable_horizontal_scrolling_desc')">
<Radio v-model="settings.enableHorizontalScrolling" />
</SettingsItem>
<!-- <SettingsItem title="Open link in current tab">
<Radio v-model="settings.openLinkInCurrentTab" />
</SettingsItem> -->
</SettingsItemGroup>
<SettingsItemGroup :title="$t('settings.group_performance')">
@@ -77,6 +86,13 @@ watch(() => settings.value.language, (newValue) => {
</SettingsItemGroup>
<SettingsItemGroup :title="$t('settings.group_video_card')">
<SettingsItem :title="$t('settings.video_card_link_opening_behavior')">
<Select
v-model="settings.videoCardLinkOpenMode"
:options="videoCardOpenModeOptions"
w="full"
/>
</SettingsItem>
<SettingsItem :title="$t('settings.enable_video_preview')">
<Radio v-model="settings.enableVideoPreview" />
</SettingsItem>

View File

@@ -3,6 +3,7 @@ import { Icon } from '@iconify/vue'
import Button from '~/components/Button.vue'
import { useApiClient } from '~/composables/api'
import { useBewlyApp } from '~/composables/useAppProvider'
import { settings } from '~/logic'
import type { VideoPreviewResult } from '~/models/video/videoPreview'
import { calcCurrentTime, calcTimeSince, numFormatter } from '~/utils/dataFormatter'
@@ -64,9 +65,7 @@ const emit = defineEmits<{
}>()
const api = useApiClient()
// Used to click and control herf attribute
const isClick = ref<boolean>(false)
const { openIframeDrawer } = useBewlyApp()
function getCurrentVideoUrl(video: Video) {
const baseUrl = `https://www.bilibili.com/video/${video.bvid ?? `av${video.aid}`}`
@@ -75,7 +74,7 @@ function getCurrentVideoUrl(video: Video) {
}
const videoUrl = computed(() => {
if (props.removed || !isClick.value || !props.video)
if (props.removed || !props.video)
return undefined
if (props.video.url)
@@ -189,14 +188,11 @@ function handelMouseLeave() {
clearTimeout(mouseLeaveTimeOut.value)
}
function switchClickState(flag: boolean) {
if (flag) {
isClick.value = flag
}
else {
setTimeout(() => {
isClick.value = flag
})
function handleClick(event: MouseEvent) {
if (settings.value.videoCardLinkOpenMode === 'drawer' && videoUrl.value) {
event.preventDefault()
openIframeDrawer(videoUrl.value)
}
}
@@ -235,9 +231,7 @@ function handleUndo() {
:href="videoUrl" target="_blank" rel="noopener noreferrer"
@mouseenter="handleMouseEnter"
@mouseleave="handelMouseLeave"
@mousedown="switchClickState(true)"
@mouseup="switchClickState(false)"
@dragend="switchClickState(false)"
@click="handleClick"
>
<!-- Cover -->
<div
@@ -424,7 +418,7 @@ function handleUndo() {
:class="{ 'more-active': moreBtnActive }"
shrink-0 w-30px h-30px m="t--3px r--8px" translate-x--8px
grid place-items-center cursor-pointer rounded="50%" duration-300
@click.prevent="handleMoreBtnClick"
@click.stop.prevent="handleMoreBtnClick"
>
<div i-mingcute:more-2-line text="lg" />
</div>

View File

@@ -11,6 +11,7 @@ export interface BewlyAppProvider {
handlePageRefresh: Ref<(() => void) | undefined>
handleBackToTop: (targetScrollTop?: number) => void
haveScrollbar: () => boolean
openIframeDrawer: (url: string) => void
}
export function useBewlyApp(): BewlyAppProvider {

View File

@@ -13,9 +13,10 @@ import { getUserID, isHomePage, scrollToTop } from '~/utils/main'
import emitter from '~/utils/mitt'
const { isDark } = useDark()
const activatedPage = ref<AppPage>(settings.value.dockItemVisibilityList.find(e => e.visible === true)?.page ?? AppPage.Home)
const { locale } = useI18n()
const [showSettings, toggleSettings] = useToggle(false)
const activatedPage = ref<AppPage>(settings.value.dockItemVisibilityList.find(e => e.visible === true)?.page ?? AppPage.Home)
const pages = {
[AppPage.Home]: defineAsyncComponent(() => import('./Home/Home.vue')),
[AppPage.Search]: defineAsyncComponent(() => import('./Search/Search.vue')),
@@ -34,6 +35,22 @@ const handleThrottledBackToTop = useThrottleFn(() => handleBackToTop(), 1000)
const topBarRef = ref()
const reachTop = ref<boolean>(true)
const iframeDrawerUrl = ref<string>('')
const showIframeDrawer = ref<boolean>(false)
const inIframe = computed((): boolean => {
return window.self !== window.top
})
const showBewlyPage = computed((): boolean => {
if (inIframe.value) {
return false
}
else {
return isHomePage() && !inIframe.value && !settings.value.useOriginalBilibiliHomepage
}
})
watch(
() => activatedPage.value,
() => {
@@ -242,6 +259,11 @@ function handleReduceFrostedGlassBlur() {
}
}
function openIframeDrawer(url: string) {
iframeDrawerUrl.value = url
showIframeDrawer.value = true
}
/**
* Checks if the current viewport has a scrollbar.
* @returns {boolean} Returns true if the viewport has a scrollbar, false otherwise.
@@ -261,6 +283,7 @@ provide<BewlyAppProvider>('BEWLY_APP', {
handleBackToTop,
handlePageRefresh,
handleReachBottom,
openIframeDrawer,
haveScrollbar,
})
</script>
@@ -274,7 +297,7 @@ provide<BewlyAppProvider>('BEWLY_APP', {
text="$bew-text-1"
>
<!-- Background -->
<template v-if="isHomePage() && !settings.useOriginalBilibiliHomepage">
<template v-if="showBewlyPage">
<AppBackground :activated-page="activatedPage" />
</template>
@@ -284,9 +307,13 @@ provide<BewlyAppProvider>('BEWLY_APP', {
</KeepAlive>
<!-- Dock & RightSideButtons -->
<div pos="absolute top-0 left-0" w-full h-full overflow-hidden pointer-events-none>
<div
v-if="!inIframe"
pos="absolute top-0 left-0" w-full h-full overflow-hidden
pointer-events-none
>
<Dock
v-if="isHomePage() && !settings.useOriginalBilibiliHomepage"
v-if="showBewlyPage"
pointer-events-auto
:activated-page="activatedPage"
@change-page="(page: AppPage) => changeActivatePage(page)"
@@ -302,7 +329,7 @@ provide<BewlyAppProvider>('BEWLY_APP', {
</div>
<!-- TopBar -->
<div m-auto max-w="$bew-page-max-width">
<div v-if="!inIframe" m-auto max-w="$bew-page-max-width">
<OldTopBar
v-if="settings.useOldTopBar"
pos="top-0 left-0" z="99 hover:1001" w-full
@@ -315,9 +342,9 @@ provide<BewlyAppProvider>('BEWLY_APP', {
<div
pos="absolute top-0 left-0" w-full h-full
:style="{ height: isHomePage() && !settings.useOriginalBilibiliHomepage ? '100dvh' : '0' }"
:style="{ height: showBewlyPage ? '100dvh' : '0' }"
>
<template v-if="isHomePage() && !settings.useOriginalBilibiliHomepage">
<template v-if="showBewlyPage">
<OverlayScrollbarsComponent ref="scrollbarRef" element="div" h-inherit defer @os-scroll="handleOsScroll">
<main m-auto max-w="$bew-page-max-width">
<div
@@ -339,6 +366,12 @@ provide<BewlyAppProvider>('BEWLY_APP', {
</OverlayScrollbarsComponent>
</template>
</div>
<IframeDrawer
v-if="settings.videoCardLinkOpenMode === 'drawer' && showIframeDrawer"
:url="iframeDrawerUrl"
@close="showIframeDrawer = false"
/>
</div>
</template>

View File

@@ -12,7 +12,7 @@ export interface Settings {
language: string
enableGridLayoutSwitcher: boolean
enableHorizontalScrolling: boolean
openLinkInCurrentTab: boolean
videoCardLinkOpenMode: 'drawer' | 'newTab'
enableVideoPreview: boolean
enableVideoCtrlBarOnVideoCard: boolean
hoverVideoCardDelayed: boolean
@@ -81,13 +81,16 @@ export const settings = useStorageLocal('settings', ref<Settings>({
language: '',
enableGridLayoutSwitcher: true,
enableHorizontalScrolling: false,
openLinkInCurrentTab: false,
disableFrostedGlass: true,
reduceFrostedGlassBlur: false,
blockAds: false,
videoCardLinkOpenMode: 'newTab',
enableVideoPreview: true,
enableVideoCtrlBarOnVideoCard: false,
hoverVideoCardDelayed: false,
blockAds: false,
disableFrostedGlass: true,
reduceFrostedGlassBlur: false,
// Desktop & Dock
useOldTopBar: false,

View File

@@ -65,6 +65,7 @@ export async function getManifest() {
css: ['./dist/contentScripts/style.css'],
run_at: 'document_start',
match_about_blank: true,
all_frames: true,
},
],
web_accessible_resources: [

View File

@@ -146,15 +146,14 @@ export function delay(ms: number) {
/**
* Check if the current page is the home page
* @param url the url to check
* @returns true if the current page is the home page
*/
export function isHomePage(): boolean {
export function isHomePage(url: string = location.href): boolean {
if (
/https?:\/\/(?:www\.)?bilibili.com\/?(?:#\/?)?$/.test(location.href)
// https://github.com/hakadao/BewlyBewly/issues/525 #525
|| /https?:\/\/(?:www\.)?bilibili.com\/?(?:\?.*)?$/.test(location.href)
|| /https?:\/\/(?:www\.)?bilibili.com\/index\.html$/.test(location.href)
|| /https?:\/\/(?:www\.)?bilibili.com\/\?spm_id_from=.*/.test(location.href)
/https?:\/\/(?:www\.)?bilibili.com\/?(?:#\/?)?$/.test(url)
|| /https?:\/\/(?:www\.)?bilibili.com\/index\.html$/.test(url)
|| /https?:\/\/(?:www\.)?bilibili.com\/\?spm_id_from=.*/.test(url)
) {
return true
}