Files
BewlyBewly/src/contentScripts/index.ts
2024-12-16 01:52:25 +08:00

324 lines
11 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 (
// 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)
// favorite playlist
|| /https?:\/\/(?:www\.)?bilibili\.com\/list\/ml.*/.test(currentUrl)
// search page
|| /https?:\/\/search\.bilibili\.com\.*/.test(currentUrl)
// moments
|| /https?:\/\/t\.bilibili\.com\.*/.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
// www.bilibili.com/read/pcpreview 是专栏浏览页, 因布局问题不做适配 #846
|| /https?:\/\/(?:www\.)?bilibili\.com\/read\/(?!pcpreview).*/.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
}
}
let beforeLoadedStyleEl: HTMLStyleElement | undefined
if (isSupportedPages()) {
if (settings.value.adaptToOtherPageStyles)
useDark()
if (settings.value.adaptToOtherPageStyles)
document.documentElement.classList.add('bewly-design')
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
// DO NOT change the home page when in iframe because it will cause nested calls to the homepage
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 = ''
if (originalTopBarInnerUselessContents)
originalTopBarInnerUselessContents.forEach(item => (item as HTMLElement).style.display = 'none')
if (originalTopBar)
document.body.appendChild(originalTopBar)
}
if (isSupportedPages()) {
// Then inject the app
if (changeHomePage) {
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())
// return
// if (!isSupportedPages())
// return
// // Create a MutationObserver to watch for Shadow DOM additions
// const observer = new MutationObserver((mutations) => {
// mutations.forEach((mutation) => {
// if (mutation.type === 'childList') {
// mutation.addedNodes.forEach((node) => {
// if (node instanceof HTMLElement && node.shadowRoot) {
// injectStyleToShadowDOM(node.shadowRoot)
// // Observe nested Shadow DOMs recursively
// observeShadowDOMRecursively(node.shadowRoot)
// }
// })
// }
// })
// })
// // Observe the entire document for new Shadow DOMs
// observer.observe(document.body, {
// childList: true,
// subtree: true,
// })
// // Inject styles into existing Shadow DOMs on initial load
// injectStylesRecursively(document)
// function observeShadowDOMRecursively(shadowRoot: ShadowRoot) {
// observer.observe(shadowRoot, {
// childList: true,
// subtree: true,
// })
// // Recursively observe nested Shadow DOMs within this Shadow DOM
// shadowRoot.querySelectorAll('*').forEach((el) => {
// if (el.shadowRoot) {
// observeShadowDOMRecursively(el.shadowRoot)
// }
// })
// }
// function injectStylesRecursively(root: Document | ShadowRoot) {
// if (root instanceof ShadowRoot) {
// injectStyleToShadowDOM(root)
// }
// root.querySelectorAll('*').forEach((element) => {
// if (element.shadowRoot) {
// injectStylesRecursively(element.shadowRoot)
// }
// })
// }
// function injectStyleToShadowDOM(shadowRoot: ShadowRoot) {
// if (!shadowRoot.querySelector('style[data-bewly-style]')) {
// const styleEl = document.createElement('style')
// styleEl.setAttribute('data-bewly-style', 'true')
// styleEl.textContent = `
// @import url(${browser.runtime.getURL('dist/contentScripts/style.css')});
// `
// if (settings.value.adaptToOtherPageStyles) {
// // Reset the theme color to ensure the theme color is updated
// styleEl.textContent += `
// * {
// --bew-theme-color: ${settings.value.themeColor};
// }
// `
// }
// shadowRoot.appendChild(styleEl)
// }
// }
// }