feat: initialize VidBee download extension with core functionality

* Add .gitignore files for main and monkey directories.
* Create initial project structure for VidBee download extension.
* Implement main functionality in src/main/index.ts to handle deep links.
* Develop UI components including DownloadDialog and VidBee download button.
* Set up TypeScript configuration and Vite for build process.
* Add styles for download button and tooltips.
* Integrate localization for user interface elements.
* Establish deep link handling in the renderer for improved user experience.
This commit is contained in:
Nexmoe
2025-12-20 19:03:13 +08:00
parent a1c44baf65
commit 79a78d4322
16 changed files with 1564 additions and 39 deletions

146
monkey/src/main.ts Normal file
View File

@@ -0,0 +1,146 @@
import './style.css'
// Get current video URL
// yt-dlp can handle all URLs directly, so we just return the current URL
function getVideoUrl(): string | null {
return window.location.href
}
// Temporarily hide button
function hideButtonTemporarily(): void {
const container = document.getElementById('vidbee-download-btn')
if (container) {
container.classList.add('vidbee-hidden')
// Auto restore after 5 seconds
setTimeout(() => {
if (container) {
container.classList.remove('vidbee-hidden')
}
}, 5000)
}
}
// Create VidBee download button
function createVidBeeButton(): void {
// Check if button already exists
if (document.getElementById('vidbee-download-btn')) {
return
}
const videoUrl = getVideoUrl()
if (!videoUrl) {
return
}
// Create button container
const container = document.createElement('div')
container.id = 'vidbee-download-btn'
container.className = 'vidbee-download-container'
// Create main download button
const button = document.createElement('button')
button.className = 'vidbee-download-button'
button.setAttribute('aria-label', 'Download with VidBee')
button.innerHTML = `
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
<polyline points="7 10 12 15 17 10"></polyline>
<line x1="12" y1="15" x2="12" y2="3"></line>
</svg>
<span class="vidbee-tooltip">Download with VidBee</span>
`
// Create close button
const closeButton = document.createElement('button')
closeButton.className = 'vidbee-close-button'
closeButton.setAttribute('aria-label', 'Hide button')
closeButton.innerHTML = `
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
<span class="vidbee-tooltip">Hide</span>
`
// Handle close button click - temporarily hide
closeButton.addEventListener('click', (e) => {
e.stopPropagation()
hideButtonTemporarily()
})
let clickTimer: number | null = null
let clickCount = 0
// Handle main button click event - single click for download
button.addEventListener('click', () => {
clickCount++
if (clickCount === 1) {
clickTimer = window.setTimeout(() => {
// Single click - trigger download
const vidbeeUrl = `vidbee://download?url=${encodeURIComponent(videoUrl)}`
window.location.href = vidbeeUrl
clickCount = 0
}, 300)
} else if (clickCount === 2) {
// Double click - temporarily hide
if (clickTimer !== null) {
clearTimeout(clickTimer)
}
clickCount = 0
hideButtonTemporarily()
}
})
// Assemble container
container.appendChild(button)
container.appendChild(closeButton)
// Insert container directly to body (fixed position)
document.body.appendChild(container)
}
// Initialize when page loads
function init(): void {
// Wait for page to fully load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', createVidBeeButton)
} else {
createVidBeeButton()
}
// Handle SPA navigation (when navigating between videos on sites like YouTube, Bilibili, etc.)
let lastUrl = location.href
let urlCheckTimer: number | null = null
const checkUrlChange = () => {
const currentUrl = location.href
if (currentUrl !== lastUrl) {
lastUrl = currentUrl
// Remove old button and create new one
const oldButton = document.getElementById('vidbee-download-btn')
if (oldButton) {
oldButton.remove()
}
// Wait a bit for the page to update (different sites have different update speeds)
const hostname = window.location.hostname
const delay = hostname.includes('bilibili.com') ? 800 : 500
setTimeout(createVidBeeButton, delay)
}
}
// Use MutationObserver for DOM changes (works for most SPA sites)
new MutationObserver(() => {
if (urlCheckTimer !== null) {
clearTimeout(urlCheckTimer)
}
urlCheckTimer = window.setTimeout(checkUrlChange, 100)
}).observe(document.body, { childList: true, subtree: true })
// Also listen to popstate for browser navigation
window.addEventListener('popstate', () => {
setTimeout(checkUrlChange, 300)
})
}
init()

169
monkey/src/style.css Normal file
View File

@@ -0,0 +1,169 @@
/* VidBee Download Button Container */
.vidbee-download-container {
position: fixed;
bottom: 16px;
right: 16px;
z-index: 9999;
transition: all 0.2s ease;
overflow: visible;
}
/* Hidden state */
.vidbee-download-container.vidbee-hidden {
opacity: 0;
pointer-events: none;
transform: scale(0);
}
/* Main Download Button */
.vidbee-download-button {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 36px;
height: 36px;
padding: 0;
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
color: rgba(255, 255, 255, 0.8);
border: 1px solid rgba(255, 255, 255, 0.1);
border-radius: 50%;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
cursor: pointer;
font-size: 0;
transition: all 0.2s ease;
opacity: 0.6;
overflow: visible;
}
.vidbee-download-button:hover {
opacity: 1;
background: rgba(0, 0, 0, 0.7);
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transform: scale(1.1);
}
.vidbee-download-button:active {
transform: scale(0.95);
}
.vidbee-download-button svg {
flex-shrink: 0;
stroke: currentColor;
width: 16px;
height: 16px;
}
/* Tooltip */
.vidbee-tooltip {
position: absolute;
right: calc(100% + 8px);
top: 50%;
padding: 6px 10px;
background: rgba(0, 0, 0, 0.9);
backdrop-filter: blur(8px);
-webkit-backdrop-filter: blur(8px);
color: white;
font-size: 12px;
font-weight: 500;
white-space: nowrap;
border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease, transform 0.2s ease;
transform: translateY(-50%) translateX(4px);
z-index: 10000;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1;
}
.vidbee-tooltip::after {
content: '';
position: absolute;
left: 100%;
top: 50%;
transform: translateY(-50%);
border: 4px solid transparent;
border-left-color: rgba(0, 0, 0, 0.9);
}
.vidbee-download-button:hover .vidbee-tooltip {
opacity: 1;
transform: translateY(-50%) translateX(0);
}
/* Close Button - Hidden by default */
.vidbee-close-button {
position: absolute;
top: -6px;
right: -6px;
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
padding: 0;
background: rgba(255, 77, 77, 0.9);
backdrop-filter: blur(4px);
-webkit-backdrop-filter: blur(4px);
color: white;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
cursor: pointer;
font-size: 0;
transition: all 0.2s ease;
z-index: 1;
opacity: 0;
pointer-events: none;
overflow: visible;
}
/* Show close button on container hover */
.vidbee-download-container:hover .vidbee-close-button {
opacity: 1;
pointer-events: auto;
}
.vidbee-close-button:hover {
background: rgba(255, 77, 77, 1);
transform: scale(1.15);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4);
}
.vidbee-close-button:active {
transform: scale(0.9);
}
.vidbee-close-button svg {
flex-shrink: 0;
stroke: currentColor;
width: 10px;
height: 10px;
}
/* Close button tooltip positioning */
.vidbee-close-button .vidbee-tooltip {
right: calc(100% + 6px);
top: 50%;
left: auto;
transform: translateY(-50%) translateX(4px);
}
.vidbee-close-button .vidbee-tooltip::after {
left: 100%;
top: 50%;
right: auto;
transform: translateY(-50%);
border-left-color: rgba(0, 0, 0, 0.9);
border-top-color: transparent;
}
.vidbee-download-container:hover .vidbee-close-button:hover .vidbee-tooltip {
opacity: 1;
transform: translateY(-50%) translateX(0);
}

4
monkey/src/vite-env.d.ts vendored Normal file
View File

@@ -0,0 +1,4 @@
/// <reference types="vite/client" />
/// <reference types="vite-plugin-monkey/client" />
//// <reference types="vite-plugin-monkey/global" />
/// <reference types="vite-plugin-monkey/style" />