diff --git a/components/NavigationButtons.css b/components/NavigationButtons.css new file mode 100644 index 0000000..fe0218d --- /dev/null +++ b/components/NavigationButtons.css @@ -0,0 +1,22 @@ +.wxt-nav-button { + position: fixed; + right: 0px; + z-index: 9999; + display: flex; + align-items: center; + gap: 5px; + background-color: #28a745; /* 导航按钮使用绿色以作区分 */ + color: white; + padding: 5px 10px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + font-size: 14px; + font-weight: 500; + text-decoration: none; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + cursor: pointer; + transition: transform 0.2s ease-in-out, background-color 0.2s ease; +} + +.wxt-nav-button:hover { + transform: scale(1.05); +} \ No newline at end of file diff --git a/components/NavigationButtons.ts b/components/NavigationButtons.ts new file mode 100644 index 0000000..2ebdf35 --- /dev/null +++ b/components/NavigationButtons.ts @@ -0,0 +1,55 @@ +import './NavigationButtons.css'; + +/** + * 创建或更新一个浮动导航按钮。 + * 此按钮在新标签页中打开链接。 + */ +function createOrUpdateNavButton(id: string, name: string, iconClass: string, url: string, topPosition: string) { + let button = document.getElementById(id) as HTMLAnchorElement | null; + + if (!button) { + button = document.createElement('a'); + button.id = id; + button.className = 'wxt-nav-button'; + button.innerHTML = ` + + ${name} + `; + + // 关键:设置在新标签页打开 + button.target = '_blank'; + // 安全性最佳实践 + button.rel = 'noopener noreferrer'; + + document.body.appendChild(button); + } + + button.href = url; + button.style.top = topPosition; + button.style.display = 'flex'; +} + +function hideButton(id: string) { + const button = document.getElementById(id); + if (button) { + button.style.display = 'none'; + } +} + +const MISSAV_BUTTON_ID = 'wxt-missav-nav-button'; + +/** + * 添加或更新 MissAV 导航按钮。 + * @param videoNumber - 视频番号 (例如, 'IPX-811')。 + */ +export function addOrUpdateNavigationButtons(videoNumber: string) { + const missavUrl = `https://missav.ws/${videoNumber.toLowerCase()}`; + createOrUpdateNavButton(MISSAV_BUTTON_ID, 'MissAV', 'icon-play', missavUrl, '200px'); +} + +/** + * 隐藏所有的导航按钮。 + */ +export function hideNavigationButtons() { + hideButton(MISSAV_BUTTON_ID); +} \ No newline at end of file diff --git a/components/PlayerButtons.css b/components/PlayerButtons.css new file mode 100644 index 0000000..ad5cda9 --- /dev/null +++ b/components/PlayerButtons.css @@ -0,0 +1,27 @@ +.wxt-player-button { + /* 定位和层级 */ + position: fixed; + right: 0px; + z-index: 9999; + + /* 外观和布局 */ + display: flex; + align-items: center; + gap: 5px; + background-color: #3173dc; /* 播放器按钮使用蓝色 */ + color: white; + padding: 5px 10px; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; + font-size: 14px; + font-weight: 500; + text-decoration: none; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); + + /* 交互效果 */ + cursor: pointer; + transition: transform 0.2s ease-in-out, background-color 0.2s ease; +} + +.wxt-player-button:hover { + transform: scale(1.05); +} \ No newline at end of file diff --git a/components/PlayerButtons.ts b/components/PlayerButtons.ts index 2b701c8..afff191 100644 --- a/components/PlayerButtons.ts +++ b/components/PlayerButtons.ts @@ -1,72 +1,66 @@ +import './PlayerButtons.css'; + /** * 创建或更新一个浮动播放器按钮。 - * @param id - 按钮元素的 ID。 - * @param name - 按钮上显示的文本。 - * @param iconClass - 按钮图标的 CSS 类。 - * @param url - 按钮点击后跳转的 URL。 - * @param topPosition - 按钮的 CSS 'top' 属性值。 */ function createOrUpdateButton(id: string, name:string, iconClass: string, url: string, topPosition: string) { - let button = document.getElementById(id) as HTMLAnchorElement | null; - - if (!button) { - button = document.createElement('a'); - button.id = id; - button.className = 'wxt-player-button'; // 为所有播放器按钮使用一个通用类名 - button.innerHTML = ` - - ${name} - `; - document.body.appendChild(button); - - // 添加点击事件监听器 - button.addEventListener('click', (e) => { - e.preventDefault(); // 阻止 标签的默认跳转行为 - window.location.href = button!.href; - }); - } - - // 更新按钮的链接和位置 - button.href = url; - button.style.top = topPosition; - button.style.display = 'flex'; // 确保按钮可见 + let button = document.getElementById(id) as HTMLAnchorElement | null; + + if (!button) { + button = document.createElement('a'); + button.id = id; + button.className = 'wxt-player-button'; + button.innerHTML = ` + + ${name} + `; + document.body.appendChild(button); + + button.addEventListener('click', (e) => { + e.preventDefault(); + window.location.href = button!.href; + }); } - - /** - * 隐藏指定的播放器按钮。 - * @param id - 要隐藏的按钮的 ID。 - */ - function hideButton(id: string) { - const button = document.getElementById(id); - if (button) { - button.style.display = 'none'; - } + + button.href = url; + button.style.top = topPosition; + button.style.display = 'flex'; +} + +/** + * 隐藏指定的播放器按钮。 + */ +function hideButton(id: string) { + const button = document.getElementById(id); + if (button) { + button.style.display = 'none'; } +} + +// 定义按钮的常量 ID +const IINA_BUTTON_ID = 'wxt-iina-floating-button'; +const POTPLAYER_BUTTON_ID = 'wxt-potplayer-floating-button'; + +/** + * 添加或更新播放器按钮。 + * @param missavUUID - 用于生成播放链接的 UUID。 + */ +export function addOrUpdatePlayerButtons(missavUUID: string) { + const playlistUrl = `https://surrit.com/${missavUUID}/playlist.m3u8`; - // 定义按钮的常量 ID - const IINA_BUTTON_ID = 'wxt-iina-floating-button'; - const POTPLAYER_BUTTON_ID = 'wxt-potplayer-floating-button'; - - /** - * 添加或更新 IINA 和 PotPlayer 的浮动按钮。 - * @param missavUUID - 用于生成播放链接的 UUID。 - */ - export function addOrUpdatePlayerButtons(missavUUID: string) { - const playlistUrl = `https://surrit.com/${missavUUID}/playlist.m3u8`; - - // IINA 按钮 - const iinaUrl = `iina://weblink?url=${playlistUrl}`; - createOrUpdateButton(IINA_BUTTON_ID, 'IINA', 'icon-play', iinaUrl, '100px'); - - // PotPlayer 按钮 - const potplayerUrl = `potplayer://${playlistUrl}`; - createOrUpdateButton(POTPLAYER_BUTTON_ID, 'PotPlayer', 'icon-play', potplayerUrl, '150px'); - } - - /** - * 隐藏所有的播放器按钮。 - */ - export function hidePlayerButtons() { - hideButton(IINA_BUTTON_ID); - hideButton(POTPLAYER_BUTTON_ID); - } \ No newline at end of file + // IINA 按钮 + const iinaUrl = `iina://weblink?url=${playlistUrl}`; + createOrUpdateButton(IINA_BUTTON_ID, 'IINA', 'icon-play', iinaUrl, '100px'); + + // PotPlayer 按钮 + const potplayerUrl = `potplayer://${playlistUrl}`; + createOrUpdateButton(POTPLAYER_BUTTON_ID, 'PotPlayer', 'icon-play', potplayerUrl, '140px'); +} + +/** + * 隐藏所有的播放器按钮。 + */ +export function hidePlayerButtons() { + hideButton(IINA_BUTTON_ID); + hideButton(POTPLAYER_BUTTON_ID); +} \ No newline at end of file diff --git a/entrypoints/content/index.ts b/entrypoints/content/index.ts index 9bbf3b7..5a05c2f 100644 --- a/entrypoints/content/index.ts +++ b/entrypoints/content/index.ts @@ -1,19 +1,18 @@ -import './style.css' -// 导入新的组件方法 +// 从两个组件中导入方法 import { addOrUpdatePlayerButtons, hidePlayerButtons } from '../../components/PlayerButtons'; +import { addOrUpdateNavigationButtons, hideNavigationButtons } from '../../components/NavigationButtons'; -// storage key, 与 popup 中保持一致 const STORAGE_KEY = 'feature_enabled'; export default defineContentScript({ matches: ['*://*.javdb.com/v/*'], async main() { - // 检查功能是否启用 const isEnabled = await storage.getItem(`sync:${STORAGE_KEY}`) ?? true; if (!isEnabled) { console.log('❌ [JavDB Helper] 功能已禁用。'); - hidePlayerButtons(); // 使用新的方法隐藏按钮 + hidePlayerButtons(); + hideNavigationButtons(); // 同时隐藏导航按钮 return; } @@ -23,18 +22,22 @@ export default defineContentScript({ if (window.location.pathname.startsWith('/v/')) { const videoNumber = getVideoNumber(); if (videoNumber) { + // 只要有番号,就显示导航按钮 + addOrUpdateNavigationButtons(videoNumber); + + // 异步获取 UUID 来显示播放器按钮 const missavUUID = await getMissavUUID(videoNumber); if (missavUUID) { - // 成功获取 UUID,创建或更新按钮 - addOrUpdatePlayerButtons(missavUUID); // 使用新的方法更新按钮 + addOrUpdatePlayerButtons(missavUUID); } else { - // 未获取到 UUID,隐藏按钮 + // 如果没有 UUID,则只隐藏播放器按钮 hidePlayerButtons(); } } } else { - // 如果不在视频详情页,隐藏按钮 + // 如果不在视频详情页,隐藏所有按钮 hidePlayerButtons(); + hideNavigationButtons(); } }; @@ -53,7 +56,7 @@ export default defineContentScript({ } }); -// 获取目标视频番号 (此函数保持不变) +// 获取目标视频番号 function getVideoNumber(): string { const targetElement = document.querySelector('a.button.is-white.copy-to-clipboard'); if (!targetElement) { @@ -69,7 +72,7 @@ function getVideoNumber(): string { return targetNumber; } -// 获取 missav UUID (此函数保持不变) +// 获取 missav UUID async function getMissavUUID(videoNumber: string): Promise { const lowerTargetNumber = videoNumber.toLowerCase(); const targetUrl = `https://missav.ws/dm1/en/${lowerTargetNumber}`; diff --git a/entrypoints/content/style.css b/entrypoints/content/style.css deleted file mode 100644 index 8616631..0000000 --- a/entrypoints/content/style.css +++ /dev/null @@ -1,28 +0,0 @@ -.wxt-player-button { - /* 定位和层级 */ - position: fixed; - /* 'top' 属性现在通过内联样式设置 */ - right: 0px; - z-index: 9999; - - /* 外观和布局 */ - display: flex; - align-items: center; - gap: 5px; - background-color: #3173dc; - color: white; - padding: 5px 10px; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; - font-size: 14px; - font-weight: 500; - text-decoration: none; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); - - /* 交互效果 */ - cursor: pointer; - transition: transform 0.2s ease-in-out, background-color 0.2s ease; -} - -.wxt-player-button:hover { - transform: scale(1.05); -} \ No newline at end of file diff --git a/structure.md b/structure.md index 5b43b39..1cfd774 100644 --- a/structure.md +++ b/structure.md @@ -1,38 +1,38 @@ -Here's a brief summary of each of these files and directories: +Here's a brief summary of each of these files and directories:
+
+📂 {rootDir}/
+ 📁 .output/
+ 📁 .wxt/
+ 📁 assets/
+ 📁 components/
+ 📁 composables/
+ 📁 entrypoints/
+ 📁 hooks/
+ 📁 modules/
+ 📁 public/
+ 📁 utils/
+ 📄 .env
+ 📄 .env.publish
+ 📄 app.config.ts
+ 📄 package.json
+ 📄 tsconfig.json
+ 📄 web-ext.config.ts
+ 📄 wxt.config.ts
-📂 {rootDir}/ - 📁 .output/ - 📁 .wxt/ - 📁 assets/ - 📁 components/ - 📁 composables/ - 📁 entrypoints/ - 📁 hooks/ - 📁 modules/ - 📁 public/ - 📁 utils/ - 📄 .env - 📄 .env.publish - 📄 app.config.ts - 📄 package.json - 📄 tsconfig.json - 📄 web-ext.config.ts - 📄 wxt.config.ts - -.output/: All build artifacts will go here -.wxt/: Generated by WXT, it contains TS config -assets/: Contains all CSS, images, and other assets that should be processed by WXT -components/: Auto-imported by default, contains UI components -composables/: Auto-imported by default, contains source code for your project's composable functions for Vue -entrypoints/: Contains all the entrypoints that get bundled into your extension -hooks/: Auto-imported by default, contains source code for your project's hooks for React and Solid -modules/: Contains local WXT Modules for your project -public/: Contains any files you want to copy into the output folder as-is, without being processed by WXT -utils/: Auto-imported by default, contains generic utilities used throughout your project -.env: Contains Environment Variables -.env.publish: Contains Environment Variables for publishing -app.config.ts: Contains Runtime Config -package.json: The standard file used by your package manager -tsconfig.json: Config telling TypeScript how to behave -web-ext.config.ts: Configure Browser Startup -wxt.config.ts: The main config file for WXT projects \ No newline at end of file +.output/: All build artifacts will go here
+.wxt/: Generated by WXT, it contains TS config
+assets/: Contains all CSS, images, and other assets that should be processed by WXT
+components/: Auto-imported by default, contains UI components
+composables/: Auto-imported by default, contains source code for your project's composable functions for Vue
+entrypoints/: Contains all the entrypoints that get bundled into your extension
+hooks/: Auto-imported by default, contains source code for your project's hooks for React and Solid
+modules/: Contains local WXT Modules for your project
+public/: Contains any files you want to copy into the output folder as-is, without being processed by WXT
+utils/: Auto-imported by default, contains generic utilities used throughout your project
+.env: Contains Environment Variables
+.env.publish: Contains Environment Variables for publishing
+app.config.ts: Contains Runtime Config
+package.json: The standard file used by your package manager
+tsconfig.json: Config telling TypeScript how to behave
+web-ext.config.ts: Configure Browser Startup
+wxt.config.ts: The main config file for WXT projects
\ No newline at end of file