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('.bili-header') const originalTopBarInnerUselessContents = document.querySelectorAll('.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((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 = 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) // } // } // }