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:
146
monkey/src/main.ts
Normal file
146
monkey/src/main.ts
Normal 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
169
monkey/src/style.css
Normal 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
4
monkey/src/vite-env.d.ts
vendored
Normal 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" />
|
||||
Reference in New Issue
Block a user