Files
BewlyBewly/src/contentScripts/index.ts
Hakadao 659e859af6
Some checks are pending
CI / Test (lts/*, ubuntu-latest) (push) Waiting to run
CI / Test (lts/*, windows-latest) (push) Waiting to run
CI / Test (lts/-1, ubuntu-latest) (push) Waiting to run
CI / Test (lts/-1, windows-latest) (push) Waiting to run
fix: cannot adapted another watcher later url (#1286)
close #1286
2025-01-26 04:16:34 +08:00

399 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 } 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 page
|| /https?:\/\/(?:www\.)?bilibili\.com\/(?:video|list)\/.*/.test(currentUrl)
// anime playback & movie page
|| /https?:\/\/(?:www\.)?bilibili\.com\/bangumi\/play\/.*/.test(currentUrl)
// watch later playlist
|| /https?:\/\/(?:www\.)?bilibili\.com\/list\/watchlater.*/.test(currentUrl)
|| /https?:\/\/(?:www\.)?bilibili\.com\/watchlater\/list.*/.test(currentUrl)
// favorite playlist
|| /https?:\/\/(?:www\.)?bilibili\.com\/list\/ml.*/.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)
) {
return true
}
else {
return false
}
}
export function isSupportedIframePages(): boolean {
if (
isInIframe()
&& (
// supports Bilibili page URLs recorded in the dock
isHomePage()
|| /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)
// Since `Open in drawer` will open the video page within an iframe, so we need to support the following pages
// video page
|| /https?:\/\/(?:www\.)?bilibili\.com\/(?:video|list)\/.*/.test(currentUrl)
// anime playback & movie page
|| /https?:\/\/(?:www\.)?bilibili\.com\/bangumi\/play\/.*/.test(currentUrl)
// watch later playlist
|| /https?:\/\/(?:www\.)?bilibili\.com\/list\/watchlater.*/.test(currentUrl)
// favorite playlist
|| /https?:\/\/(?:www\.)?bilibili\.com\/list\/ml.*/.test(currentUrl)
)
) {
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
let removeOriginalTopBar: HTMLStyleElement | null = null
if (!settings.value.useOriginalBilibiliTopBar && isSupportedPages())
removeOriginalTopBar = injectCSS(`.bili-header { 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
injectCSS(`
.home-redesign-base {
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)
// }
// })
// }