0.0.7 #11
31
CLAUDE.md
31
CLAUDE.md
|
@ -19,6 +19,7 @@ npm i # Install dependencies
|
||||||
```bash
|
```bash
|
||||||
npm run build:chrome # Build for Chrome/Chromium browsers
|
npm run build:chrome # Build for Chrome/Chromium browsers
|
||||||
npm run build:firefox # Build for Firefox
|
npm run build:firefox # Build for Firefox
|
||||||
|
npm run build:safari # Build for Safari (uses background.html)
|
||||||
npm run build # Default build (Chrome)
|
npm run build # Default build (Chrome)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -71,6 +72,7 @@ npm run qc # Alias for quality-check
|
||||||
### Browser Compatibility
|
### Browser Compatibility
|
||||||
- **Chrome**: Uses service worker, full CSP support
|
- **Chrome**: Uses service worker, full CSP support
|
||||||
- **Firefox**: Uses background scripts, modified CSP, specific gecko settings
|
- **Firefox**: Uses background scripts, modified CSP, specific gecko settings
|
||||||
|
- **Safari**: Uses background page (background.html) instead of service worker
|
||||||
- **Prebuild Scripts**: Automatically modify manifest.json and HTML for each platform
|
- **Prebuild Scripts**: Automatically modify manifest.json and HTML for each platform
|
||||||
|
|
||||||
### Storage Strategy
|
### Storage Strategy
|
||||||
|
@ -99,6 +101,7 @@ npm run qc # Alias for quality-check
|
||||||
### `/scripts/`
|
### `/scripts/`
|
||||||
- **prebuild-chrome.js**: Removes localhost dev configs for production
|
- **prebuild-chrome.js**: Removes localhost dev configs for production
|
||||||
- **prebuild-firefox.js**: Adapts manifest for Firefox compatibility
|
- **prebuild-firefox.js**: Adapts manifest for Firefox compatibility
|
||||||
|
- **prebuild-safari.js**: Creates background.html and adapts manifest for Safari
|
||||||
|
|
||||||
### `/public/`
|
### `/public/`
|
||||||
- **manifest.json**: Extension manifest (modified by prebuild scripts)
|
- **manifest.json**: Extension manifest (modified by prebuild scripts)
|
||||||
|
@ -127,4 +130,30 @@ npm run qc # Alias for quality-check
|
||||||
### Error Handling
|
### Error Handling
|
||||||
- Graceful fallbacks for storage API unavailability
|
- Graceful fallbacks for storage API unavailability
|
||||||
- Resource URL rotation handling with automatic refresh
|
- Resource URL rotation handling with automatic refresh
|
||||||
- Cross-browser compatibility with feature detection
|
- Cross-browser compatibility with feature detection
|
||||||
|
|
||||||
|
## Safari Extension Considerations
|
||||||
|
|
||||||
|
### Background Script Handling
|
||||||
|
Safari Web Extensions have different requirements for background scripts:
|
||||||
|
|
||||||
|
1. **Background Page vs Service Worker**: Safari uses `background.page` instead of `service_worker`
|
||||||
|
2. **Background HTML**: The prebuild script creates `background.html` that loads `background.js`
|
||||||
|
3. **Manifest Configuration**: Uses `"background": { "page": "background.html", "persistent": false }`
|
||||||
|
|
||||||
|
### Auto-redirect Functionality
|
||||||
|
The auto-redirect feature in Safari may require special handling due to:
|
||||||
|
- Different WebKit extension APIs
|
||||||
|
- Safari's stricter security policies
|
||||||
|
- Tab management differences from Chromium
|
||||||
|
|
||||||
|
### Building for Safari
|
||||||
|
```bash
|
||||||
|
npm run build:safari # Creates background.html and Safari-specific manifest
|
||||||
|
```
|
||||||
|
|
||||||
|
The Safari build process:
|
||||||
|
1. Removes localhost development configurations
|
||||||
|
2. Converts `service_worker` to `background.page`
|
||||||
|
3. Creates `background.html` wrapper for `background.js`
|
||||||
|
4. Adds Safari-specific browser settings
|
|
@ -8,6 +8,7 @@
|
||||||
"build": "echo 'No platform specified, will build for Chromium.' && npm run build-chrome",
|
"build": "echo 'No platform specified, will build for Chromium.' && npm run build-chrome",
|
||||||
"build:chrome": "npm run prebuild:chrome && vue-tsc -b && vite build && cp -r public/* dist/",
|
"build:chrome": "npm run prebuild:chrome && vue-tsc -b && vite build && cp -r public/* dist/",
|
||||||
"build:firefox": "npm run prebuild:firefox && vue-tsc -b && vite build && cp -r public/* dist/",
|
"build:firefox": "npm run prebuild:firefox && vue-tsc -b && vite build && cp -r public/* dist/",
|
||||||
|
"build:safari": "npm run prebuild:safari && vue-tsc -b && vite build && cp -r public/* dist/",
|
||||||
"dev:refresh": "vue-tsc -b && vite build && cp -r public/* dist/",
|
"dev:refresh": "vue-tsc -b && vite build && cp -r public/* dist/",
|
||||||
"build:watch": "vite build --watch",
|
"build:watch": "vite build --watch",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
|
@ -15,7 +16,8 @@
|
||||||
"quality-check": "biome ci",
|
"quality-check": "biome ci",
|
||||||
"qc": "npm run quality-check",
|
"qc": "npm run quality-check",
|
||||||
"prebuild:chrome": "node scripts/prebuild-chrome.js",
|
"prebuild:chrome": "node scripts/prebuild-chrome.js",
|
||||||
"prebuild:firefox": "node scripts/prebuild-firefox.js"
|
"prebuild:firefox": "node scripts/prebuild-firefox.js",
|
||||||
|
"prebuild:safari": "node scripts/prebuild-safari.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.7",
|
"@tailwindcss/vite": "^4.1.7",
|
||||||
|
|
359
scripts/prebuild-safari.js
Normal file
359
scripts/prebuild-safari.js
Normal file
|
@ -0,0 +1,359 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// 处理 manifest.json for Safari
|
||||||
|
function processManifest() {
|
||||||
|
const manifestPath = path.join(__dirname, '../public/manifest.json');
|
||||||
|
const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
|
||||||
|
|
||||||
|
// 移除本地调试相关的配置
|
||||||
|
if (manifest.host_permissions) {
|
||||||
|
manifest.host_permissions = manifest.host_permissions.filter(
|
||||||
|
permission => !permission.includes('localhost')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (manifest.content_security_policy && manifest.content_security_policy.extension_pages) {
|
||||||
|
// 移除 CSP 中的本地开发相关配置
|
||||||
|
manifest.content_security_policy.extension_pages = manifest.content_security_policy.extension_pages
|
||||||
|
.replace(/script-src 'self' http:\/\/localhost:5173;\s*/g, '')
|
||||||
|
.replace(/\s*http:\/\/localhost:5173\s*/g, ' ')
|
||||||
|
.replace(/\s*ws:\/\/localhost:5173\s*/g, ' ')
|
||||||
|
.replace(/;\s+/g, '; ') // 标准化分号后的空格
|
||||||
|
.replace(/\s+/g, ' ') // 合并多个空格为一个
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safari 特殊处理:添加 appShell.html 到 content scripts 匹配
|
||||||
|
if (manifest.content_scripts && manifest.content_scripts[0]) {
|
||||||
|
// 添加 appShell.html 的匹配规则
|
||||||
|
const existingMatches = manifest.content_scripts[0].matches;
|
||||||
|
if (!existingMatches.includes("https://monster-siren.hypergryph.com/")) {
|
||||||
|
existingMatches.push("https://monster-siren.hypergryph.com/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safari 特殊处理:使用 background.page 而不是 service_worker
|
||||||
|
if (manifest.background && manifest.background.service_worker) {
|
||||||
|
// Safari 扩展在 Manifest V3 中必须使用 persistent: false
|
||||||
|
// 但为了调试,我们暂时设为 true 来确保页面加载
|
||||||
|
manifest.background = {
|
||||||
|
page: "background.html",
|
||||||
|
persistent: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 background.html 文件用于 Safari
|
||||||
|
const backgroundHtmlPath = path.join(__dirname, '../public/background.html');
|
||||||
|
const backgroundHtmlContent = `<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>MSR Mod Background</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>MSR Mod Background Page</h1>
|
||||||
|
<p>If you can see this page, the background page is loaded!</p>
|
||||||
|
<div id="log"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// 创建日志函数,同时显示在页面和控制台
|
||||||
|
function log(message) {
|
||||||
|
console.log(message);
|
||||||
|
const logDiv = document.getElementById('log');
|
||||||
|
if (logDiv) {
|
||||||
|
logDiv.innerHTML += '<div>' + message + '</div>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log('=== SAFARI BACKGROUND PAGE LOADED ===');
|
||||||
|
log('Document ready state: ' + document.readyState);
|
||||||
|
log('Location: ' + location.href);
|
||||||
|
log('Time: ' + new Date().toISOString());
|
||||||
|
|
||||||
|
// 确保在 Safari 中正确加载脚本
|
||||||
|
try {
|
||||||
|
log('Safari extension context: ' + JSON.stringify({
|
||||||
|
chrome: typeof chrome,
|
||||||
|
browser: typeof browser,
|
||||||
|
safari: typeof safari
|
||||||
|
}));
|
||||||
|
} catch (e) {
|
||||||
|
log('Error in background.html: ' + e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听事件
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
log('=== DOMContentLoaded fired ===');
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener('load', function() {
|
||||||
|
log('=== Window load fired ===');
|
||||||
|
});
|
||||||
|
|
||||||
|
log('About to load background.js...');
|
||||||
|
</script>
|
||||||
|
<script src="background.js" onload="log('background.js loaded successfully')" onerror="log('Failed to load background.js')"></script>
|
||||||
|
<script>
|
||||||
|
log('=== After background.js script tag ===');
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
fs.writeFileSync(backgroundHtmlPath, backgroundHtmlContent);
|
||||||
|
|
||||||
|
// 创建 Safari 兼容的 background.js
|
||||||
|
const backgroundJsPath = path.join(__dirname, '../public/background.js');
|
||||||
|
let backgroundJsContent = fs.readFileSync(backgroundJsPath, 'utf8');
|
||||||
|
|
||||||
|
// 检查是否已经添加过 Safari 代码,避免重复
|
||||||
|
if (backgroundJsContent.includes('=== Safari background.js starting ===')) {
|
||||||
|
console.log('Safari background.js already processed, skipping...');
|
||||||
|
} else {
|
||||||
|
// 在开头添加 Safari 调试信息(只添加一次)
|
||||||
|
const safariDebugCode = `
|
||||||
|
console.log("=== Safari background.js starting ===");
|
||||||
|
console.log("Available APIs:", {
|
||||||
|
chrome: typeof chrome,
|
||||||
|
browser: typeof browser,
|
||||||
|
safari: typeof safari
|
||||||
|
});
|
||||||
|
|
||||||
|
// Safari 特殊处理
|
||||||
|
if (typeof chrome === 'undefined' && typeof browser === 'undefined') {
|
||||||
|
console.log("No extension APIs available in Safari");
|
||||||
|
// 如果没有扩展 API,创建一个空的对象避免错误
|
||||||
|
window.chrome = {
|
||||||
|
webRequest: { onBeforeRequest: { addListener: () => {} } },
|
||||||
|
storage: { sync: { get: () => Promise.resolve({}) } },
|
||||||
|
tabs: { create: () => {}, remove: () => {}, update: () => {} },
|
||||||
|
runtime: {
|
||||||
|
getURL: (path) => path,
|
||||||
|
onMessage: { addListener: () => {} }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safari 消息监听器:处理来自 content script 的重定向请求
|
||||||
|
if (typeof chrome !== 'undefined' && chrome.runtime && chrome.runtime.onMessage) {
|
||||||
|
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||||||
|
console.log('Background received message:', message);
|
||||||
|
|
||||||
|
if (message.action === 'redirect_to_extension') {
|
||||||
|
console.log('Processing redirect request from content script');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 创建新标签页并打开扩展
|
||||||
|
const extensionUrl = chrome.runtime.getURL('index.html');
|
||||||
|
chrome.tabs.create({ url: extensionUrl }, (newTab) => {
|
||||||
|
console.log('New extension tab created:', newTab.id);
|
||||||
|
|
||||||
|
// 关闭原始标签页
|
||||||
|
if (sender.tab && sender.tab.id) {
|
||||||
|
chrome.tabs.remove(sender.tab.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendResponse({ success: true, url: extensionUrl });
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to redirect:', error);
|
||||||
|
sendResponse({ success: false, error: error.message });
|
||||||
|
}
|
||||||
|
|
||||||
|
return true; // 保持消息通道开放
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
`;
|
||||||
|
|
||||||
|
// 替换 Safari 的重定向 URL 监听
|
||||||
|
backgroundJsContent = backgroundJsContent.replace(
|
||||||
|
/{ urls: \['https:\/\/monster-siren\.hypergryph\.com\/api\/fontset', 'https:\/\/monster-siren\.hypergryph\.com\/manifest\.json'\] }/g,
|
||||||
|
"{ urls: ['https://monster-siren.hypergryph.com/api/fontset', 'https://monster-siren.hypergryph.com/manifest.json', 'https://monster-siren.hypergryph.com/'] }"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 替换 Safari 的重定向判断逻辑
|
||||||
|
backgroundJsContent = backgroundJsContent.replace(
|
||||||
|
/details\.url === 'https:\/\/monster-siren\.hypergryph\.com\/manifest\.json'/g,
|
||||||
|
"(details.url === 'https://monster-siren.hypergryph.com/manifest.json' || details.url === 'https://monster-siren.hypergryph.com/')"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 清理可能的重复条件
|
||||||
|
backgroundJsContent = backgroundJsContent.replace(
|
||||||
|
/\(\(details\.url === 'https:\/\/monster-siren\.hypergryph\.com\/manifest\.json' \|\| details\.url === 'https:\/\/monster-siren\.hypergryph\.com\/'\) \|\| details\.url === 'https:\/\/monster-siren\.hypergryph\.com\/'\)/g,
|
||||||
|
"(details.url === 'https://monster-siren.hypergryph.com/manifest.json' || details.url === 'https://monster-siren.hypergryph.com/')"
|
||||||
|
);
|
||||||
|
|
||||||
|
backgroundJsContent = safariDebugCode + backgroundJsContent;
|
||||||
|
}
|
||||||
|
fs.writeFileSync(backgroundJsPath, backgroundJsContent);
|
||||||
|
console.log('✅ Safari-compatible background.js created');
|
||||||
|
|
||||||
|
// 创建 Safari 专用的 content.js
|
||||||
|
const contentJsPath = path.join(__dirname, '../public/content.js');
|
||||||
|
|
||||||
|
// 检查是否已经处理过 content.js
|
||||||
|
const existingContentJs = fs.existsSync(contentJsPath) ? fs.readFileSync(contentJsPath, 'utf8') : '';
|
||||||
|
if (existingContentJs.includes('checkRedirectPreference')) {
|
||||||
|
console.log('Safari content.js already processed, skipping...');
|
||||||
|
} else {
|
||||||
|
const contentJsContent = `
|
||||||
|
// Safari 扩展 content script for redirect
|
||||||
|
console.log('MSR Mod content script loaded on:', window.location.href);
|
||||||
|
|
||||||
|
// 兼容 Safari 的浏览器 API
|
||||||
|
const browserAPI = typeof browser !== 'undefined' ? browser : chrome;
|
||||||
|
|
||||||
|
// 异步函数:检查重定向偏好设置
|
||||||
|
async function checkRedirectPreference() {
|
||||||
|
try {
|
||||||
|
console.log('Checking redirect preferences...');
|
||||||
|
|
||||||
|
// 读取偏好设置
|
||||||
|
const pref = await browserAPI.storage.sync.get('preferences');
|
||||||
|
console.log('Retrieved preferences:', pref);
|
||||||
|
|
||||||
|
// 检查自动重定向设置(默认为 true)
|
||||||
|
const shouldRedirect = pref === undefined ||
|
||||||
|
pref.preferences === undefined ||
|
||||||
|
pref.preferences.autoRedirect === undefined ||
|
||||||
|
pref.preferences.autoRedirect === true;
|
||||||
|
|
||||||
|
console.log('Should redirect:', shouldRedirect);
|
||||||
|
return shouldRedirect;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error reading preferences:', error);
|
||||||
|
// 如果读取偏好设置失败,默认重定向
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行重定向的函数
|
||||||
|
function performRedirect() {
|
||||||
|
console.log('Performing redirect to extension...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 对于 Safari,我们需要使用消息传递来请求重定向
|
||||||
|
// 因为 content script 无法直接访问 chrome.runtime.getURL
|
||||||
|
|
||||||
|
// 方案1:尝试通过消息传递
|
||||||
|
if (typeof chrome !== 'undefined' && chrome.runtime) {
|
||||||
|
chrome.runtime.sendMessage({action: 'redirect_to_extension'}, (response) => {
|
||||||
|
if (chrome.runtime.lastError) {
|
||||||
|
console.log('Message sending failed, trying direct redirect...');
|
||||||
|
// 方案2:尝试直接重定向(可能在某些情况下有效)
|
||||||
|
window.location.href = 'safari-web-extension://[extension-id]/index.html';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Chrome runtime not available, trying alternative redirect...');
|
||||||
|
// 方案3:显示提示让用户手动打开扩展
|
||||||
|
document.body.innerHTML = \`
|
||||||
|
<div style="
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
background: #1a1a1a;
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
|
||||||
|
z-index: 10000;
|
||||||
|
">
|
||||||
|
<h2>MSR Mod Extension Detected</h2>
|
||||||
|
<p>Please click the MSR Mod extension icon in your Safari toolbar to open the app.</p>
|
||||||
|
<button onclick="window.close()" style="
|
||||||
|
background: #007AFF;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-top: 10px;
|
||||||
|
">Close Tab</button>
|
||||||
|
</div>
|
||||||
|
\`;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Redirect failed:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主逻辑:检查页面并根据偏好设置决定是否重定向
|
||||||
|
async function main() {
|
||||||
|
// 检查是否是目标页面
|
||||||
|
if (window.location.pathname === '/' || window.location.href.includes('appShell.html')) {
|
||||||
|
console.log('Detected target page, checking preferences...');
|
||||||
|
|
||||||
|
// 检查偏好设置
|
||||||
|
const shouldRedirect = await checkRedirectPreference();
|
||||||
|
|
||||||
|
if (shouldRedirect) {
|
||||||
|
console.log('Auto-redirect is enabled, proceeding with redirect...');
|
||||||
|
performRedirect();
|
||||||
|
} else {
|
||||||
|
console.log('Auto-redirect is disabled, skipping redirect.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行主逻辑
|
||||||
|
main().catch(error => {
|
||||||
|
console.error('Error in main function:', error);
|
||||||
|
});
|
||||||
|
`;
|
||||||
|
|
||||||
|
fs.writeFileSync(contentJsPath, contentJsContent);
|
||||||
|
}
|
||||||
|
console.log('✅ Safari-compatible content.js created');
|
||||||
|
|
||||||
|
// Safari 可能需要额外的权限
|
||||||
|
if (!manifest.permissions.includes('activeTab')) {
|
||||||
|
manifest.permissions.push('activeTab');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 Safari 特有配置
|
||||||
|
manifest.browser_specific_settings = {
|
||||||
|
safari: {
|
||||||
|
minimum_version: "14.0"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
||||||
|
console.log('✅ Safari Manifest.json processed');
|
||||||
|
console.log('✅ Background.html created for Safari');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理 index.html
|
||||||
|
function processIndexHtml() {
|
||||||
|
const indexPath = path.join(__dirname, '../index.html');
|
||||||
|
let content = fs.readFileSync(indexPath, 'utf8');
|
||||||
|
|
||||||
|
// 替换脚本地址
|
||||||
|
content = content.replace(
|
||||||
|
/src="[^"]*\/src\/main\.ts"/g,
|
||||||
|
'src="./src/main.ts"'
|
||||||
|
);
|
||||||
|
|
||||||
|
// 移除 crossorigin 属性
|
||||||
|
content = content.replace(/\s+crossorigin/g, '');
|
||||||
|
|
||||||
|
fs.writeFileSync(indexPath, content);
|
||||||
|
console.log('✅ Index.html processed for Safari');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行处理
|
||||||
|
try {
|
||||||
|
processManifest();
|
||||||
|
processIndexHtml();
|
||||||
|
console.log('🎉 Safari build preparation completed!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error during Safari build preparation:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ const favourites = useFavourites()
|
||||||
|
|
||||||
const hover = ref(false)
|
const hover = ref(false)
|
||||||
|
|
||||||
const props = defineProps<{
|
defineProps<{
|
||||||
item: QueueItem
|
item: QueueItem
|
||||||
index: number
|
index: number
|
||||||
}>()
|
}>()
|
||||||
|
|
|
@ -28,7 +28,7 @@ export function isSafari(): boolean {
|
||||||
* @returns {boolean} 如果是移动版 Safari 返回 true,否则返回 false
|
* @returns {boolean} 如果是移动版 Safari 返回 true,否则返回 false
|
||||||
*/
|
*/
|
||||||
export function isMobileSafari(): boolean {
|
export function isMobileSafari(): boolean {
|
||||||
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream
|
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !(window as any).MSStream
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,7 +47,7 @@ export function supportsWebAudioVisualization(): boolean {
|
||||||
const hasAudioContext = 'AudioContext' in window || 'webkitAudioContext' in window
|
const hasAudioContext = 'AudioContext' in window || 'webkitAudioContext' in window
|
||||||
const hasAnalyserNode = hasAudioContext && (
|
const hasAnalyserNode = hasAudioContext && (
|
||||||
'AnalyserNode' in window ||
|
'AnalyserNode' in window ||
|
||||||
(window.AudioContext && 'createAnalyser' in AudioContext.prototype)
|
((window as any).AudioContext && 'createAnalyser' in (window as any).AudioContext.prototype)
|
||||||
)
|
)
|
||||||
|
|
||||||
return hasAudioContext && hasAnalyserNode
|
return hasAudioContext && hasAnalyserNode
|
||||||
|
|
Loading…
Reference in New Issue
Block a user