Files
BewlyBewly/src/contentScripts/index.ts
Hakadao 3d3f57bfc3 feat: notifications drawer
Squashed commit of the following:

commit e0d503ef38c5b8d4b19f81368b53b656e6935ff7
Author: Hakadao <a578457889743@gmail.com>
Date:   Thu Feb 20 16:24:53 2025 +0800

    feat(locales): add translation for notifications page drawer option

commit 5c4e5aab811c3bf73d2cfc983e646edca66dff55
Author: Hakadao <a578457889743@gmail.com>
Date:   Thu Feb 20 16:24:32 2025 +0800

    chore: udpate

commit dd7ff6dfeadd514746e1a10773702387cea3c550
Author: Hakadao <a578457889743@gmail.com>
Date:   Thu Feb 20 16:13:54 2025 +0800

    fix(a-link): disable click event when using modifier key click

commit f4801a040b26d6b084168859f2a1e5b9b32ae178
Author: Hakadao <a578457889743@gmail.com>
Date:   Thu Feb 20 16:06:13 2025 +0800

    feat: enhance notifications drawer functionality with custom URL and click handling

commit 7e63cd4417572a5455c8dbccc447f8fe37d92c02
Author: Hakadao <a578457889743@gmail.com>
Date:   Thu Feb 20 15:36:53 2025 +0800

    chore: update

commit 077c416e1ead6cd9ed249375c6c0c4625b2339ec
Author: Hakadao <a578457889743@gmail.com>
Date:   Thu Feb 20 14:32:28 2025 +0800

    fix: add Teleport to NotificationsDrawer for better app integration

commit 5e17ed34f68677bfc8423ad3e24ea3cc3afa038e
Author: Hakadao <a578457889743@gmail.com>
Date:   Thu Feb 20 14:26:36 2025 +0800

    chore: tweak styles

commit 4536347b12ef9e3d0c2ce06c1395ba89fe44fc51
Author: Hakadao <a578457889743@gmail.com>
Date:   Thu Feb 20 13:52:29 2025 +0800

    feat: improve notifications drawer styles

commit 022ea89eed4ae5c6953445118dec97acb15cf38e
Merge: 17f882e7 7a513921
Author: Hakadao <a578457889743@gmail.com>
Date:   Thu Feb 20 12:30:04 2025 +0800

    Merge branch 'main' into feat/notications-drawer

commit 17f882e7cda0b4d74b916592d526f4b1ad5f921d
Merge: 4d615462 6dd838a8
Author: Hakadao <a578457889743@gmail.com>
Date:   Thu Feb 20 12:18:44 2025 +0800

    feat: update notifications drawer style

commit 4d6154625a
Author: Hakadao <a578457889743@gmail.com>
Date:   Wed Feb 19 18:58:24 2025 +0800

    feat: notifications drawer
2025-02-20 16:27:13 +08:00

388 lines
14 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import '~/styles'
import 'uno.css'
import { createApp } from 'vue'
import { useDark } from '~/composables/useDark'
import { BEWLY_MOUNTED } from '~/constants/globalEvents'
import { settings } from '~/logic'
import { setupApp } from '~/logic/common-setup'
import RESET_BEWLY_CSS from '~/styles/reset.css?raw'
import { runWhenIdle } from '~/utils/lazyLoad'
import { compareVersions, injectCSS, isHomePage, isInIframe, isNotificationPage, isVideoOrBangumiPage } from '~/utils/main'
import { SVG_ICONS } from '~/utils/svgIcons'
import { version } from '../../package.json'
import App from './views/App.vue'
const isFirefox: boolean = /Firefox/i.test(navigator.userAgent)
// Fix `OverlayScrollbars` not working in Firefox
// https://github.com/fingerprintjs/fingerprintjs/issues/683#issuecomment-881210244
if (isFirefox) {
window.requestIdleCallback = window.requestIdleCallback.bind(window)
window.cancelIdleCallback = window.cancelIdleCallback.bind(window)
window.requestAnimationFrame = window.requestAnimationFrame.bind(window)
window.cancelAnimationFrame = window.cancelAnimationFrame.bind(window)
window.setTimeout = window.setTimeout.bind(window)
window.clearTimeout = window.clearTimeout.bind(window)
}
const currentUrl = document.URL
function isSupportedPages(): boolean {
if (isInIframe())
return false
if (
// homepage
isHomePage()
// video or bangumi page
|| isVideoOrBangumiPage()
// popular page https://www.bilibili.com/v/popular/all
|| /https?:\/\/(?:www\.)?bilibili\.com\/v\/popular\/all.*/.test(currentUrl)
// search page
|| /https?:\/\/search\.bilibili\.com\.*/.test(currentUrl)
// moments page
// https://github.com/BewlyBewly/BewlyBewly/issues/1246
// https://github.com/BewlyBewly/BewlyBewly/issues/1256
// https://github.com/BewlyBewly/BewlyBewly/issues/1266
|| /https?:\/\/t\.bilibili\.com(?!\/vote|\/share).*/.test(currentUrl)
// moment detail
|| /https?:\/\/(?:www\.)?bilibili\.com\/opus\/.*/.test(currentUrl)
// history page
|| /https?:\/\/(?:www\.)?bilibili\.com\/history.*/.test(currentUrl)
|| /https?:\/\/(?:www\.)?bilibili\.com\/account\/history.*/.test(currentUrl)
// watcher later page
|| /https?:\/\/(?:www\.)?bilibili\.com\/watchlater\/#\/list.*/.test(currentUrl)
// user space page
|| /https?:\/\/space\.bilibili\.com\.*/.test(currentUrl)
// notifications page
|| /https?:\/\/message\.bilibili\.com\.*/.test(currentUrl)
// bilibili channel page b站分区页面
|| /https?:\/\/(?:www\.)?bilibili\.com\/v\/(?!popular).*/.test(currentUrl)
// anime page & chinese anime page
|| /https?:\/\/(?:www\.)?bilibili\.com\/(?:anime|guochuang).*/.test(currentUrl)
// channel page e.g. tv shows, movie, variety shows, mooc page
|| /https?:\/\/(?:www\.)?bilibili\.com\/(?:tv|movie|variety|mooc|documentary).*/.test(currentUrl)
// article page
|| /https?:\/\/(?:www\.)?bilibili\.com\/read\/.*/.test(currentUrl)
// 404 page
|| /^https?:\/\/(?:www\.)?bilibili\.com\/404.*$/.test(currentUrl)
// creative center page 創作中心頁
|| /^https?:\/\/member\.bilibili\.com\/platform.*$/.test(currentUrl)
// account settings page 帳號設定頁
|| /^https?:\/\/account\.bilibili\.com\/.*$/.test(currentUrl)
// login page
|| /^https?:\/\/passport\.bilibili\.com\/login.*$/.test(currentUrl)
// music center page 新歌熱榜 https://music.bilibili.com/pc/music-center/
|| /https?:\/\/music\.bilibili\.com\/pc\/music-center.*$/.test(currentUrl)
) {
return true
}
else {
return false
}
}
export function isSupportedIframePages(): boolean {
if (
isInIframe()
&& (
// supports Bilibili page URLs recorded in the dock
isHomePage()
// Since `Open in drawer` will open the video page within an iframe, so we need to support the following pages
|| isVideoOrBangumiPage()
|| /https?:\/\/search\.bilibili\.com\/all.*/.test(currentUrl)
|| /https?:\/\/www\.bilibili\.com\/anime.*/.test(currentUrl)
|| /https?:\/\/space\.bilibili\.com\/\d+\/favlist.*/.test(currentUrl)
|| /https?:\/\/www\.bilibili\.com\/history.*/.test(currentUrl)
|| /https?:\/\/www\.bilibili\.com\/watchlater\/#\/list.*/.test(currentUrl)
// moments page
// https://github.com/BewlyBewly/BewlyBewly/issues/1246
// https://github.com/BewlyBewly/BewlyBewly/issues/1256
// https://github.com/BewlyBewly/BewlyBewly/issues/1266
|| /https?:\/\/t\.bilibili\.com(?!\/vote|\/share).*/.test(currentUrl)
// notifications page, for `Open the notifications page as a drawer`
|| isNotificationPage()
)
) {
return true
}
else {
return false
}
}
let beforeLoadedStyleEl: HTMLStyleElement | undefined
if (isSupportedPages() || isSupportedIframePages()) {
if (settings.value.adaptToOtherPageStyles)
useDark()
if (settings.value.adaptToOtherPageStyles) {
document.documentElement.classList.add('bewly-design')
// Remove the Bilibili Evolved's dark mode style
runWhenIdle(async () => {
const darkModeStyle = document.head.querySelector('#dark-mode')
if (darkModeStyle)
document.head.removeChild(darkModeStyle)
})
}
else {
document.documentElement.classList.remove('bewly-design')
}
}
if (settings.value.adaptToOtherPageStyles && isHomePage()) {
beforeLoadedStyleEl = injectCSS(`
html.bewly-design {
background-color: var(--bew-bg);
transition: background-color 0.2s ease-in;
}
body {
display: none;
}
`)
// Add opacity transition effect for page loaded
injectCSS(`
body {
transition: opacity 0.5s;
}
`)
}
window.addEventListener(BEWLY_MOUNTED, () => {
if (beforeLoadedStyleEl)
document.documentElement.removeChild(beforeLoadedStyleEl)
})
// Set the original Bilibili top bar to `display: none` to prevent it from showing before the load
// see: https://github.com/BewlyBewly/BewlyBewly/issues/967
const removeOriginalTopBar = injectCSS(`.bili-header, #biliMainHeader { visibility: hidden !important; }`)
async function onDOMLoaded() {
let originalTopBar: HTMLElement | null = null
const changeHomePage = !isInIframe() && !settings.value.useOriginalBilibiliHomepage && isHomePage()
// Remove the original Bilibili homepage if in Bilibili homepage & useOriginalBilibiliHomepage is enabled
if (changeHomePage) {
originalTopBar = document.querySelector<HTMLElement>('.bili-header')
const originalTopBarInnerUselessContents = document.querySelectorAll<HTMLElement>('.bili-header > *:not(.bili-header__bar)')
if (originalTopBar) {
// always show the background on the original bilibili top bar
originalTopBar.querySelector('.bili-header__bar')?.classList.add('slide-down')
}
// Remove the original Bilibili homepage if in Bilibili homepage & useOriginalBilibiliHomepage is enabled
document.body.innerHTML = ''
// Remove the Bilibili Evolved homepage & Bilibili-Gate homepage
injectCSS(`
.home-redesign-base, .bilibili-gate-root {
display: none !important;
}
`)
if (originalTopBarInnerUselessContents)
originalTopBarInnerUselessContents.forEach(item => (item as HTMLElement).style.display = 'none')
if (originalTopBar)
document.body.appendChild(originalTopBar)
}
if (isSupportedPages() || isSupportedIframePages()) {
// Then inject the app
if (isHomePage()) {
injectApp()
}
else {
await injectAppWhenIdle()
}
}
// Reset the original Bilibili top bar display style
if (removeOriginalTopBar)
document.documentElement.removeChild(removeOriginalTopBar)
}
if (document.readyState !== 'loading')
onDOMLoaded()
else
document.addEventListener('DOMContentLoaded', () => onDOMLoaded())
function injectAppWhenIdle() {
return new Promise<void>((resolve) => {
// Inject app when idle
runWhenIdle(async () => {
injectApp()
resolve()
})
})
}
function injectApp() {
// Remove bewly element if it already exists and the version is less than the current version
// Only the development mode bewly element remains
const bewlyElArr: NodeListOf<Element> = document.querySelectorAll('#bewly')
if (bewlyElArr.length > 0) {
// alert(`
// You have multiple versions of BewlyBewly installed. Please retain only one to avoid conflicts and issues!
// 您安装了多个版本的 BewlyBewly。请只保留一个版本以避免冲突和问题
// 您安裝了多個版本的 BewlyBewly。請只保留一個版本以避免衝突和問題
// 你單咗幾個版本嘅 BewlyBewly。請淨係留一個版本嚟避免衝突同問題
// `)
bewlyElArr.forEach((el: Element) => {
const elVersion = el.getAttribute('data-version') || '0.0.0'
const elIsDev = el.getAttribute('data-dev') === 'true'
// Remove bewly element if the version is less than the current version
if (compareVersions(elVersion, version) < 0)
el.remove()
// Only the development mode element remains
else if (!elIsDev)
el.remove()
})
}
// mount component to context window
const container = document.createElement('div')
container.id = 'bewly'
container.setAttribute('data-version', version)
container.setAttribute('data-dev', import.meta.env.DEV ? 'true' : 'false')
const root = document.createElement('div')
const styleEl = document.createElement('link')
// Fix #69 https://github.com/hakadao/BewlyBewly/issues/69
// https://medium.com/@emilio_martinez/shadow-dom-open-vs-closed-1a8cf286088a - open shadow dom
const shadowDOM = container.attachShadow?.({ mode: 'open' }) || container
const resetStyleEl = document.createElement('style')
resetStyleEl.textContent = `${RESET_BEWLY_CSS}`
styleEl.setAttribute('rel', 'stylesheet')
styleEl.setAttribute('href', browser.runtime.getURL('dist/contentScripts/style.css'))
shadowDOM.appendChild(resetStyleEl)
shadowDOM.appendChild(styleEl)
shadowDOM.appendChild(root)
container.style.opacity = '0'
container.style.transition = 'opacity 0.5s'
styleEl.onload = () => {
// To prevent abrupt style transitions caused by sudden style changes
setTimeout(() => {
container.style.opacity = '1'
}, 500)
}
// startShadowDOMStyleInjection()
// inject svg icons
const svgDiv = document.createElement('div')
svgDiv.innerHTML = SVG_ICONS
shadowDOM.appendChild(svgDiv)
document.body.appendChild(container)
const app = createApp(App)
setupApp(app)
app.mount(root)
}
// 實際使用實在太卡,註釋了先
// function startShadowDOMStyleInjection() {
// if (isHomePage() || !isSupportedPages())
// return
// const styleCache = new WeakSet() // Track which shadow roots have been processed
// function injectStyleToShadowDOM(shadowRoot: ShadowRoot) {
// if (styleCache.has(shadowRoot))
// return
// const styleEl = document.createElement('style')
// styleEl.setAttribute('data-bewly-style', 'true')
// styleEl.textContent = `
// @import url(${browser.runtime.getURL('dist/contentScripts/style.css')});
// ${settings.value.adaptToOtherPageStyles
// ? `
// * {
// --bew-theme-color: ${settings.value.themeColor};
// --bew-theme-color-10: color-mix(in oklab, var(--bew-theme-color), transparent 90%);
// --bew-theme-color-20: color-mix(in oklab, var(--bew-theme-color), transparent 80%);
// --bew-theme-color-30: color-mix(in oklab, var(--bew-theme-color), transparent 70%);
// --bew-theme-color-40: color-mix(in oklab, var(--bew-theme-color), transparent 60%);
// --bew-theme-color-50: color-mix(in oklab, var(--bew-theme-color), transparent 50%);
// --bew-theme-color-60: color-mix(in oklab, var(--bew-theme-color), transparent 40%);
// --bew-theme-color-70: color-mix(in oklab, var(--bew-theme-color), transparent 30%);
// --bew-theme-color-80: color-mix(in oklab, var(--bew-theme-color), transparent 20%);
// --bew-theme-color-90: color-mix(in oklab, var(--bew-theme-color), transparent 10%);
// }
// `
// : ''}
// `
// shadowRoot.appendChild(styleEl)
// styleCache.add(shadowRoot)
// }
// function processShadowDOM(node: HTMLElement) {
// if (node.shadowRoot) {
// injectStyleToShadowDOM(node.shadowRoot)
// observeShadowRoot(node.shadowRoot)
// }
// // Process child elements with shadow roots
// node.querySelectorAll('*').forEach((el) => {
// if (el instanceof HTMLElement && el.shadowRoot) {
// injectStyleToShadowDOM(el.shadowRoot)
// observeShadowRoot(el.shadowRoot)
// }
// })
// }
// function observeShadowRoot(shadowRoot: ShadowRoot) {
// const observer = new MutationObserver((mutations) => {
// mutations.forEach((mutation) => {
// if (mutation.type === 'childList') {
// mutation.addedNodes.forEach((node) => {
// if (node instanceof HTMLElement)
// processShadowDOM(node)
// })
// }
// })
// })
// observer.observe(shadowRoot, {
// childList: true,
// subtree: true,
// })
// }
// // Initial setup
// const rootObserver = new MutationObserver((mutations) => {
// mutations.forEach((mutation) => {
// if (mutation.type === 'childList') {
// mutation.addedNodes.forEach((node) => {
// if (node instanceof HTMLElement)
// processShadowDOM(node)
// })
// }
// })
// })
// // Start observing document body
// rootObserver.observe(document.body, {
// childList: true,
// subtree: true,
// })
// // Process existing shadow DOMs
// document.querySelectorAll('*').forEach((el) => {
// if (el instanceof HTMLElement && el.shadowRoot) {
// injectStyleToShadowDOM(el.shadowRoot)
// observeShadowRoot(el.shadowRoot)
// }
// })
// }