// ==UserScript==
// @name 资源日记
// @namespace resource-diary-harmony
// @version 9.0.4
// @description 资源捕获工具
// @author 一程
// @match *://*/*
// @license MIT
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJnIiB4MT0iMCUiIHkxPSIwJSIgeDI9IjEwMCUiIHkyPSIxMDAlIj48c3RvcCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjMEE4NEZGIi8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSIjNUJDMEJFIi8+PC9saW5lYXJHcmFkaWVudD48L2RlZnM+PHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiByeD0iNiIgZmlsbD0idXJsKCNnKSIvPjxwYXRoIGQ9Ik0zLjUgNy41TDEyIDMuNUwyMC41IDcuNUwxMiAxMS41WiIgZmlsbD0id2hpdGUiIGZpbGwtb3BhY2l0eT0iMC45IiBzdHJva2U9IndoaXRlIiBzdHJva2Utd2lkdGg9IjAuNiIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxwYXRoIGQ9Ik0zLjUgNy41TDEyIDExLjVWMjAuNUwzLjUgMTYuNVoiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuNiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIwLjYiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48cGF0aCBkPSJNMjAuNSA3LjVMMTIgMTEuNVYyMC41TDIwLjUgMTYuNVoiIGZpbGw9IndoaXRlIiBmaWxsLW9wYWNpdHk9IjAuNiIgc3Ryb2tlPSJ3aGl0ZSIgc3Ryb2tlLXdpZHRoPSIwLjYiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48L3N2Zz4=
// @grant GM_download
// @grant GM_addStyle
// @grant unsafeWindow
// ==/UserScript==
(function(){
'use strict';
if(!/^https?:$/.test(location.protocol))return;
if(window.top!==window.self)return;
if(window.ResourceDiary)return;
const processedElements=new WeakSet();
const processedBg=new WeakSet();
const processedSheets=new WeakSet();
const processedUrls=new Set();
const CONFIG=Object.freeze({
MAX_RESOURCES:800,
MAX_URL_CACHE:3000,
UPDATE_DELAY:120,
BATCH_SIZE:60,
DEBOUNCE_TIME:100,
LAZY_LOAD_OFFSET:'120px',
SCAN_INTERVAL:4000,
ITEM_HEIGHT:80,
FETCH_TIMEOUT:30000,
OBSERVER_DEBOUNCE:220,
CSS_SCAN_INTERVAL:8000
});
const DANGEROUS_PROTOCOLS=new Set([
'javascript:','data:text/html','data:text/javascript',
'vbscript:','mocha:','livescript:','about:','blob:javascript',
'file:','ftp:','telnet:','ssh:'
]);
const ALLOWED_MIMES=new Set([
'image/','audio/','video/','application/json',
'application/xml','text/plain','text/css','text/javascript',
'application/javascript','application/octet-stream',
'application/vnd.apple.mpegurl','application/x-mpegURL',
'application/dash+xml','video/mp2t','application/x-shockwave-flash'
]);
const EXT_TO_TYPE=Object.freeze({
mp4:'mp4',webm:'webm',ogg:'ogg',ogv:'ogv',mov:'mov',
avi:'avi',mkv:'mkv',flv:'flv',m3u8:'m3u8',mpd:'mpd',
ts:'ts',m4v:'m4v','3gp':'3gp','3g2':'3gp',wmv:'wmv',m2ts:'ts',
mp3:'mp3',wav:'wav',flac:'flac',aac:'aac',m4a:'m4a',
wma:'wma',oga:'oga',weba:'weba',opus:'opus',
aiff:'aiff',mid:'mid',midi:'mid',ac3:'ac3',amr:'amr',
jpg:'jpg',jpeg:'jpg',png:'png',gif:'gif',webp:'webp',
svg:'svg',ico:'ico',bmp:'bmp',avif:'avif',jxl:'jxl',
cur:'ico',tif:'bmp',tiff:'bmp',heic:'avif',heif:'avif',
css:'stylesheet',js:'script',mjs:'script',json:'json',
woff:'font',woff2:'font',ttf:'font',otf:'font',eot:'font'
});
const MIME_TO_TYPE=Object.freeze({
'video/mp4':'mp4','video/webm':'webm','video/ogg':'ogg',
'video/quicktime':'mov','video/x-matroska':'mkv',
'video/x-flv':'flv','video/x-msvideo':'avi',
'video/3gpp':'3gp','video/x-m4v':'m4v',
'application/vnd.apple.mpegurl':'m3u8',
'application/x-mpegURL':'m3u8',
'application/dash+xml':'mpd',
'video/mp2t':'ts',
'audio/mpeg':'mp3','audio/wav':'wav','audio/flac':'flac',
'audio/aac':'aac','audio/ogg':'oga','audio/webm':'weba',
'audio/opus':'opus','audio/x-ms-wma':'wma',
'audio/aiff':'aiff','audio/midi':'mid','audio/x-m4a':'m4a',
'image/jpeg':'jpg','image/png':'png','image/gif':'gif',
'image/webp':'webp','image/svg+xml':'svg','image/avif':'avif',
'image/bmp':'bmp','image/x-icon':'ico','image/tiff':'bmp',
'image/heic':'avif','image/heif':'avif',
'text/css':'stylesheet',
'application/javascript':'script','text/javascript':'script',
'application/json':'json','application/xml':'xml'
});
const TYPE_COLORS=Object.freeze({
jpg:'#FF6B9D',jpeg:'#FF6B9D',png:'#5BC0BE',gif:'#FFB74D',
webp:'#4FC3F7',svg:'#FFA726',ico:'#A1887F',bmp:'#90A4AE',
avif:'#81C784',jxl:'#7986CB',image:'#0A84FF',
mp4:'#FF5252',webm:'#5C6BC0',ogg:'#FFB74D',ogv:'#FFB74D',
mov:'#AB47BC',avi:'#8D6E63',mkv:'#78909C',flv:'#EC407A',
m3u8:'#66BB6A',mpd:'#29B6F6',ts:'#FFA726',m4v:'#EF5350',
'3gp':'#8D6E63',wmv:'#78909C',
mp3:'#0A84FF',wav:'#26A69A',flac:'#FFA726',aac:'#EF5350',
m4a:'#7E57C2',wma:'#78909C',oga:'#FF7043',weba:'#42A5F5',
opus:'#AB47BC',audio:'#5BC0BE',aiff:'#26A69A',mid:'#7986CB',
ac3:'#FF7043',amr:'#8D6E63',
script:'#FFA726',stylesheet:'#26A69A',json:'#5C6BC0',
xml:'#90A4AE',font:'#A1887F',
XHR:'#EF5350',fetch:'#FF7043',datauri:'#78909C',other:'#0A84FF'
});
const ICON_COLORS=Object.freeze({
box:['#0A84FF','#5BC0BE'],
file:['#78909C','#90A4AE'],
image:['#5BC0BE','#4FC3F7','#FFB74D'],
video:['#FF5252','#AB47BC','#FFB74D'],
audio:['#0A84FF','#26A69A','#7E57C2'],
script:['#FFA726','#FF7043'],
stylesheet:['#26A69A','#0A84FF','#FFB74D'],
json:['#5C6BC0','#7986CB'],
xml:['#90A4AE','#78909C'],
font:['#A1887F','#8D6E63'],
wifi:['#0A84FF','#5BC0BE'],
download:['#FF7043','#26A69A'],
link:['#42A5F5','#26A69A'],
play:['#FF6B6B','#FFB74D'],
pause:['#78909C','#90A4AE'],
copy:['#5BC0BE','#0A84FF'],
close:['#EF5350','#90A4AE'],
folder:['#FFB74D','#FFA726'],
folderOpen:['#FFB74D','#FFA726','#FF8A65'],
music:['#7E57C2','#AB47BC'],
film:['#5C6BC0','#FF5252','#FFB74D'],
code:['#26A69A','#0A84FF','#FFA726'],
waveform:['#0A84FF','#26A69A','#FF6B9D'],
search:['#0A84FF','#5BC0BE'],
settings:['#78909C','#90A4AE','#FFB74D'],
refresh:['#26A69A','#0A84FF'],
list:['#5BC0BE','#0A84FF'],
grid:['#FFB74D','#5BC0BE'],
heart:['#FF6B9D','#EF5350'],
back:['#0A84FF'],
more:['#90A4AE','#78909C','#5BC0BE'],
harmony:['#0A84FF','#5BC0BE']
});
const Icons={
box:``,
file:``,
image:``,
video:``,
audio:``,
script:``,
stylesheet:``,
json:``,
xml:``,
font:``,
wifi:``,
download:``,
link:``,
play:``,
pause:``,
copy:``,
close:``,
folder:``,
folderOpen:``,
music:``,
film:``,
code:``,
waveform:``,
search:``,
settings:``,
refresh:``,
list:``,
grid:``,
sortDesc:``,
sortAsc:``,
heart:``,
back:``,
more:``,
harmony:``,
expand:``,
compress:``,
speed:``,
loop:``,
volume:``,
volumeMute:``,
pip:``
}
const OriginalAPIs={
xhrOpen:XMLHttpRequest.prototype.open,
xhrSend:XMLHttpRequest.prototype.send,
fetch:window.fetch,
pushState:history.pushState,
replaceState:history.replaceState
};
const SecurityUtils={
urlCache:new Map(),
maxCacheSize:1000,
isSafeUrl(url){
if(!url||typeof url!=='string')return false;
const cached=this.urlCache.get(url);
if(cached!==undefined)return cached;
if(this.urlCache.size>this.maxCacheSize){
const entries=Array.from(this.urlCache.entries()).slice(-500);
this.urlCache.clear();
entries.forEach(([k,v])=>this.urlCache.set(k,v));
}
const lower=url.toLowerCase().trim();
for(const p of DANGEROUS_PROTOCOLS){
if(lower.startsWith(p)){this.urlCache.set(url,false);return false;}
}
if(lower.startsWith('data:')){
const mime=lower.split(',')[0].split(':')[1]?.split(';')[0]||'';
const safe=Array.from(ALLOWED_MIMES).some(m=>mime.startsWith(m));
this.urlCache.set(url,safe);return safe;
}
if(lower.startsWith('blob:')){
try{const o=new URL(url);const safe=o.origin===location.origin;this.urlCache.set(url,safe);return safe;}
catch{this.urlCache.set(url,false);return false;}
}
this.urlCache.set(url,true);return true;
},
// 规范化 URL 生成去重键:相对/绝对/协议相对/带 hash/默认端口 等同资源形态归一,避免重复入库
resourceKey(url){
if(!url||typeof url!=='string')return null;
const s=url.trim();
if(!s||s.length>65536)return null;
const lower=s.toLowerCase();
try{
if(lower.startsWith('data:')){
if(!this.isSafeUrl(s))return null;
return 'data:'+s.slice(0,220)+'#len'+s.length;
}
if(lower.startsWith('blob:')){
return this.isSafeUrl(s)?s:null;
}
if(/^[a-z][a-z0-9+.\-]*:/i.test(s)){
if(!this.isSafeUrl(s))return null;
return this._normalizeURL(new URL(s));
}
if(/[<>"'`\\]/.test(s))return null;
if(!/^(\/|\.{1,2}\/)/.test(s)&&!/\/|\./.test(s))return null;
if(!location.href)return null;
return this._normalizeURL(new URL(s,location.href));
}catch{return null;}
},
_normalizeURL(u){
u.hash='';
if(u.protocol==='http:'&&u.port==='80')u.port='';
else if(u.protocol==='https:'&&u.port==='443')u.port='';
if(u.hostname)u.hostname=u.hostname.toLowerCase();
return u.toString();
},
sanitizeAttr(v){return String(v).replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"').replace(/'/g,''').trim().slice(0,1000);},
sanitizeFilename(f){return f.replace(/[<>:"/\\|?*\x00-\x1f]/g,'_').slice(0,255)||'download';},
createElement(tag,attrs={},children=[]){
const el=document.createElement(tag);
for(const [k,v] of Object.entries(attrs)){
if(v==null)continue;
if(k.startsWith('on')||k==='innerHTML')continue;
if((k==='href'||k==='src'||k==='action')&&!this.isSafeUrl(v))continue;
if(k==='style'&&typeof v==='object')Object.assign(el.style,v);
else if(k==='textContent')el.textContent=v;
else if(k==='className')el.className=v;
else if(k==='dataset')Object.assign(el.dataset,v);
else el.setAttribute(k,this.sanitizeAttr(v));
}
const frag=document.createDocumentFragment();
for(const c of children){
if(typeof c==='string')frag.appendChild(document.createTextNode(c));
else if(c instanceof Node)frag.appendChild(c);
}
if(frag.childNodes.length)el.appendChild(frag);
return el;
}
};
const RD={
resources:[],
filteredResources:[],
currentFilter:'image',
viewMode:'list',
sortMode:'time-desc',
isInitialized:false,
isPanelOpen:false,
isDestroyed:false,
_xhrDataMap:new WeakMap(),
_urlKeyIndex:new Map(),
_rafId:null,
_lastUpdate:0,
_refreshTimer:null,
_statsDirty:true,
_renderSignature:'',
_lastRenderedFilter:'',
_lastRenderedCount:0,
_resourceObserver:null,
_listeners:[],
_bodyCheckInterval:null,
_host:null,
_shadowRoot:null,
_elements:{},
_hljsLoaded:false,
_hljsLoading:false,
_hlsLoaded:false,
_hlsLoading:false,
_loadHlsJS(){
return new Promise((resolve,reject)=>{
if(window.Hls){
this._hlsLoaded=true;
resolve(window.Hls);
return;
}
if(this._hlsLoading){
const check=()=>{
if(window.Hls){
this._hlsLoaded=true;
resolve(window.Hls);
}else{
setTimeout(check,100);
}
};
check();
return;
}
this._hlsLoading=true;
const script=document.createElement('script');
script.src='https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.13/hls.min.js';
script.onload=()=>{
this._hlsLoaded=true;
this._hlsLoading=false;
resolve(window.Hls);
};
script.onerror=()=>{
this._hlsLoading=false;
reject();
};
document.head.appendChild(script);
});
},
_loadHighlightJS(){
return new Promise((resolve,reject)=>{
if(window.hljs){
this._hljsLoaded=true;
resolve(window.hljs);
return;
}
if(this._hljsLoading){
const check=()=>{
if(window.hljs){
this._hljsLoaded=true;
resolve(window.hljs);
}else{
setTimeout(check,100);
}
};
check();
return;
}
this._hljsLoading=true;
const script=document.createElement('script');
script.src='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js';
script.onload=()=>{
this._hljsLoaded=true;
this._hljsLoading=false;
resolve(window.hljs);
};
script.onerror=()=>{
this._hljsLoading=false;
reject();
};
document.head.appendChild(script);
});
},
_applyHighlight(element,language){
this._loadHighlightJS().then(hljs=>{
if(!this._shadowRoot.querySelector('#rd-hljs-style')){
const link=document.createElement('link');
link.id='rd-hljs-style';
link.rel='stylesheet';
link.href='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css';
this._shadowRoot.appendChild(link);
}
if(language){
hljs.highlightElement(element,{language});
}else{
hljs.highlightElement(element);
}
}).catch(()=>{});
},
_detectLanguageFromUrl(url){
if(!url)return null;
const ext=url.split('.').pop()?.toLowerCase()||'';
const map={
'js':'javascript','mjs':'javascript','ts':'typescript',
'css':'css','scss':'scss','less':'less',
'html':'html','htm':'html','xml':'xml','svg':'xml',
'json':'json','jsonc':'json',
'py':'python','rb':'ruby','java':'java','c':'c','cpp':'cpp',
'go':'go','rs':'rust','php':'php','sh':'bash','bash':'bash',
'md':'markdown','sql':'sql','yaml':'yaml','yml':'yaml',
'toml':'toml','ini':'ini','conf':'ini',
'text':'plaintext','txt':'plaintext'
};
return map[ext]||null;
},
_detectLanguageFromContentType(ct){
if(!ct)return null;
const lower=ct.toLowerCase();
if(lower.includes('javascript'))return 'javascript';
if(lower.includes('json'))return 'json';
if(lower.includes('css'))return 'css';
if(lower.includes('html'))return 'html';
if(lower.includes('xml'))return 'xml';
if(lower.includes('markdown'))return 'markdown';
if(lower.includes('yaml'))return 'yaml';
if(lower.includes('sql'))return 'sql';
if(lower.includes('python'))return 'python';
if(lower.includes('php'))return 'php';
return null;
},
getDisplayType(url,contentType='',initiatorType=''){
if(!SecurityUtils.isSafeUrl(url))return 'other';
if(url.startsWith('blob:')){
const m=contentType||'';
if(m.includes('video'))return m.includes('webm')?'webm':m.includes('ogg')?'ogg':'mp4';
if(m.includes('audio'))return m.includes('mpeg')?'mp3':m.includes('wav')?'wav':m.includes('flac')?'flac':m.includes('aac')?'aac':'mp3';
if(m.includes('image'))return m.includes('png')?'png':m.includes('gif')?'gif':m.includes('webp')?'webp':m.includes('svg')?'svg':'jpg';
return 'other';
}
if(url.startsWith('data:')){
const m=url.split(',')[0].split(':')[1]?.split(';')[0]||'';
if(m.includes('image')){
if(m.includes('svg'))return 'svg';
if(m.includes('gif'))return 'gif';
if(m.includes('png'))return 'png';
if(m.includes('webp'))return 'webp';
if(m.includes('avif'))return 'avif';
return m.includes('jpeg')||m.includes('jpg')?'jpg':'image';
}
if(m.includes('video'))return 'mp4';
if(m.includes('audio'))return 'mp3';
return 'datauri';
}
if(contentType){
const mt=contentType.split(';')[0].toLowerCase();
if(MIME_TO_TYPE[mt])return MIME_TO_TYPE[mt];
if(mt.includes('video'))return 'mp4';
if(mt.includes('audio'))return 'mp3';
if(mt.includes('image'))return 'image';
if(mt.includes('script'))return 'script';
if(mt.includes('css'))return 'stylesheet';
if(mt.includes('font'))return 'font';
}
try{
const u=new URL(url);const ext=u.pathname.split('.').pop()?.toLowerCase()||'';
if(EXT_TO_TYPE[ext])return EXT_TO_TYPE[ext];
}catch{}
if(initiatorType==='xmlhttprequest')return 'XHR';
if(initiatorType==='fetch')return 'fetch';
return 'other';
},
getFilterType(displayType){
const t=displayType.toLowerCase();
if(['mp4','webm','ogg','ogv','mov','avi','mkv','flv','m3u8','mpd',
'ts','m4v','3gp','wmv','m2ts',
'mp3','wav','flac','aac','m4a','wma','oga','weba','opus','audio',
'aiff','mid','ac3','amr'].includes(t))return 'media';
if(['jpg','jpeg','png','gif','webp','svg','ico','bmp','avif','jxl','image',
'tif','tiff','heic','heif','cur'].includes(t))return 'image';
return 'other';
},
getPreviewUrl(r){
if(!SecurityUtils.isSafeUrl(r.url))return '';
if(r.filterType==='image')return r.url;
return '';
},
formatUrl(url){
if(!SecurityUtils.isSafeUrl(url))return '[不安全URL已阻止]';
if(url.startsWith('data:')){const m=url.split(',')[0];return SecurityUtils.sanitizeAttr(m)+',...';}
if(url.startsWith('blob:')){try{const u=new URL(url);return 'blob:...'+u.pathname.slice(-20);}catch{return 'blob:...';}}
return SecurityUtils.sanitizeAttr(url);
},
getTypeIcon(displayType){
const t=displayType.toLowerCase();
if(['mp4','webm','ogg','ogv','mov','avi','mkv','flv','m3u8','mpd','ts','m4v','3gp','wmv','m2ts'].includes(t))return Icons.video;
if(['mp3','wav','flac','aac','m4a','wma','oga','weba','opus','audio','aiff','mid','ac3','amr'].includes(t))return Icons.audio;
if(['jpg','jpeg','png','gif','webp','svg','ico','bmp','avif','jxl','image','tif','tiff','heic','heif','cur'].includes(t))return Icons.image;
if(['script','js','mjs'].includes(t))return Icons.script;
if(['stylesheet','css'].includes(t))return Icons.stylesheet;
if(['json'].includes(t))return Icons.json;
if(['xml'].includes(t))return Icons.xml;
if(['font','woff','woff2','ttf','otf','eot'].includes(t))return Icons.font;
if(['xhr'].includes(t))return Icons.wifi;
if(['fetch'].includes(t))return Icons.download;
if(['datauri'].includes(t))return Icons.link;
return Icons.file;
},
async copyToClipboard(text){
if(!text||typeof text!=='string'){this.showToast('无效的复制内容');return;}
try{
if(navigator.clipboard?.writeText)await navigator.clipboard.writeText(text);
else{
const ta=SecurityUtils.createElement('textarea',{value:text,style:{position:'fixed',left:'-9999px',opacity:'0'}});
document.body.appendChild(ta);ta.select();document.execCommand('copy');document.body.removeChild(ta);
}
this.showToast('已复制');
}catch{this.showToast('复制失败,手动复制');prompt('手动复制',text);}
},
async downloadResource(url,filename){
if(!SecurityUtils.isSafeUrl(url)){this.showToast('不安全的下载地址');return;}
this.showToast('开始下载...');
try{
if(typeof GM_download==='function'&&GM_download.toString().includes('native code')){
const name=filename||this.extractFilename(url);
GM_download({url,name,onload:()=>this.showToast('下载完成'),onerror:()=>{this.fallbackDownload(url,filename);}});
}else await this.fallbackDownload(url,filename);
}catch{this.fallbackDownload(url,filename);}
},
fallbackDownload(url,filename){
return new Promise((resolve,reject)=>{
try{
const a=document.createElement('a');a.href=url;a.download=filename||this.extractFilename(url);a.style.display='none';document.body.appendChild(a);
let same=false;try{same=new URL(url,location.href).origin===location.origin;}catch{}
if(url.startsWith('data:')||url.startsWith('blob:')||same){a.click();this.showToast('下载已开始');setTimeout(()=>{if(a.parentNode)document.body.removeChild(a);},100);resolve();}
else{document.body.removeChild(a);this.fetchAndDownload(url,filename).then(resolve).catch(reject);}
}catch(err){reject(err);}
});
},
async fetchAndDownload(url,filename){
const ctrl=new AbortController();const tid=setTimeout(()=>ctrl.abort(),CONFIG.FETCH_TIMEOUT);
try{
const resp=await fetch(url,{credentials:'omit',signal:ctrl.signal});
clearTimeout(tid);
if(!resp.ok)throw new Error('Fetch failed');
const blob=await resp.blob();const blobUrl=URL.createObjectURL(blob);
const a=document.createElement('a');a.href=blobUrl;a.download=filename||this.extractFilename(url);a.click();
setTimeout(()=>URL.revokeObjectURL(blobUrl),1000);this.showToast('下载完成');
}catch(err){
if(err.name==='AbortError')this.showToast('下载超时');
else{window.open(url,'_blank','noopener,noreferrer');this.showToast('已在新标签页打开');}
}
},
extractFilename(url){
try{
if(url.startsWith('data:')){const ext=url.match(/data:image\/(\w+)/)?.[1]||'bin';return `datauri_${Date.now()}.${ext}`;}
if(url.startsWith('blob:'))return `blob_${Date.now()}.bin`;
const u=new URL(url,location.href);const path=decodeURIComponent(u.pathname);const name=path.split('/').pop()||'download';
return SecurityUtils.sanitizeFilename(name);
}catch{return `download_${Date.now()}`;}
},
showToast(msg){
if(!this._shadowRoot)return;
const old=this._shadowRoot.getElementById('rd-toast');if(old)old.remove();
const el=SecurityUtils.createElement('div',{id:'rd-toast',style:{}});
el.innerHTML=`
${msg}
`;
this._shadowRoot.appendChild(el);
requestAnimationFrame(()=>{el.classList.add('show');});
setTimeout(()=>{
el.classList.remove('show');
setTimeout(()=>{if(el.parentNode)el.remove();},400);
},2000);
},
init(){if(this.isInitialized||this.isDestroyed)return;this._waitForBodyAndInit();},
_waitForBodyAndInit(){
if(document.body){this._delayedInit();return;}
let attempts=0;const max=50;if(this._bodyCheckInterval)clearInterval(this._bodyCheckInterval);
this._bodyCheckInterval=setInterval(()=>{
attempts++;
if(document.body){clearInterval(this._bodyCheckInterval);this._bodyCheckInterval=null;this._delayedInit();}
else if(attempts>=max){clearInterval(this._bodyCheckInterval);this._bodyCheckInterval=null;const obs=new MutationObserver((_,o)=>{if(document.body){o.disconnect();this._delayedInit();}});obs.observe(document.documentElement,{childList:true,subtree:true});}
},100);
},
_delayedInit(){
const fn=()=>{try{this._performInit();}catch(e){console.error('ResourceDiary init error:',e);this.isInitialized=false;setTimeout(()=>this._waitForBodyAndInit(),1000);}};
if(typeof requestIdleCallback!=='undefined')requestIdleCallback(fn,{timeout:2000});else setTimeout(fn,0);
},
_performInit(){
if(this.isInitialized||this.isDestroyed)return;
this.setupUI();
this.setupResourceCapture();
this.setupResourceObserver();
this.setupSPASupport();
this.setupKeyboardShortcuts();
this.setupVisibilityHandler();
setTimeout(()=>this.scanExistingResources(),500);
setTimeout(()=>this.scanExistingResources(),1500);
setTimeout(()=>this.scanExistingResources(),3000);
this.setupPeriodicScan();
this.isInitialized=true;
},
setupPeriodicScan(){
if(this._refreshTimer)clearInterval(this._refreshTimer);
this._refreshTimer=setInterval(()=>{
if(document.hidden)return;
this.scanExistingResources();
if(this.isPanelOpen)this.scheduleUpdate();
},CONFIG.SCAN_INTERVAL);
},
rebuildUI(){
const wasOpen=this.isPanelOpen;
const scrollTop=this._elements.list?.scrollTop||0;
this.destroyUI();
this.setupUI();
this.updateFilterButtons();
this.scrollFilterToActive();
if(this._elements.list)this._elements.list.scrollTop=scrollTop;
if(wasOpen){this._elements.panel?.classList.add('active');this.isPanelOpen=true;this.renderList();}
},
destroy(){
this.isDestroyed=true;
if(this._refreshTimer){clearInterval(this._refreshTimer);this._refreshTimer=null;}
if(this._rafId){cancelAnimationFrame(this._rafId);this._rafId=null;}
if(this._bodyCheckInterval){clearInterval(this._bodyCheckInterval);this._bodyCheckInterval=null;}
if(this._resourceObserver){this._resourceObserver.disconnect();this._resourceObserver=null;}
if(this._perfObserver){this._perfObserver.disconnect();this._perfObserver=null;}
if(this._listeners){this._listeners.forEach(fn=>{try{fn();}catch{}});this._listeners=[];}
XMLHttpRequest.prototype.open=OriginalAPIs.xhrOpen;
XMLHttpRequest.prototype.send=OriginalAPIs.xhrSend;
window.fetch=OriginalAPIs.fetch;
history.pushState=OriginalAPIs.pushState;
history.replaceState=OriginalAPIs.replaceState;
this.destroyUI();
this.resources=[];this.filteredResources=[];
this._urlKeyIndex.clear();
processedUrls.clear();
},
destroyUI(){
if(this._host&&this._host.parentNode)this._host.parentNode.removeChild(this._host);
this._host=null;this._shadowRoot=null;this._elements={};
this._lockDepth=0;
if(document.body){document.body.style.overflow='';document.documentElement.style.overflow='';document.body.style.paddingRight='';}
},
updateFilterButtons(){
if(!this._shadowRoot)return;
this._shadowRoot.querySelectorAll('.rd-filter').forEach(b=>b.classList.toggle('active',b.dataset.filter===this.currentFilter));
},
scrollFilterToActive(){
if(!this._shadowRoot)return;
requestAnimationFrame(()=>{
const act=this._shadowRoot.querySelector('.rd-filter.active');const c=this._shadowRoot.querySelector('.rd-filters-scroll');
if(act&&c){const left=act.offsetLeft-(c.clientWidth/2)+(act.clientWidth/2);c.scrollTo({left:Math.max(0,left),behavior:'smooth'});}
});
},
setupUI(){
if(this._host)this.destroyUI();
this._host=document.createElement('div');
this._host.style.cssText='position:fixed;top:0;left:0;width:0;height:0;overflow:visible;pointer-events:none;z-index:2147483647;';
this._shadowRoot=this._host.attachShadow({mode:'open'});
this.injectStyles();
this.createUI();
document.body.appendChild(this._host);
this.bindEvents();
},
injectStyles(){
if(!this._shadowRoot)return;
const style=document.createElement('style');
style.textContent=`
:host{
all:initial;
display:block;
position:fixed;top:0;left:0;width:0;height:0;overflow:visible;pointer-events:none;z-index:2147483647;
font-family:'HarmonyOS Sans SC','HarmonyOS Sans','PingFang SC','-apple-system',BlinkMacSystemFont,sans-serif;
--rd-bg-primary:rgba(245,247,250,0.72);
--rd-bg-secondary:rgba(255,255,255,0.65);
--rd-bg-tertiary:rgba(255,255,255,0.45);
--rd-bg-elevated:rgba(255,255,255,0.85);
--rd-border:rgba(10,30,80,0.06);
--rd-border-strong:rgba(10,30,80,0.1);
--rd-text-primary:#1c1c1e;
--rd-text-secondary:#5f6368;
--rd-text-tertiary:#98989e;
--rd-accent:#0A84FF;
--rd-accent-light:#5BC0BE;
--rd-accent-warm:#FFB74D;
--rd-accent-glow:rgba(10,132,255,0.28);
--rd-accent-glow-soft:rgba(10,132,255,0.12);
--rd-shadow-card:0 2px 12px rgba(10,30,80,0.04),0 1px 2px rgba(10,30,80,0.02);
--rd-shadow-elevated:0 8px 32px rgba(10,30,80,0.08),0 2px 8px rgba(10,30,80,0.04);
--rd-shadow-fab:0 12px 40px rgba(10,132,255,0.18),inset 0 0.5px 0 rgba(255,255,255,0.6);
--rd-radius-sm:12px;
--rd-radius-md:18px;
--rd-radius-lg:24px;
--rd-radius-xl:32px;
--rd-radius-pill:9999px;
--rd-blur:blur(25px) saturate(180%);
--rd-blur-light:blur(20px) saturate(160%);
--rd-blur-mini:blur(15px) saturate(150%);
}
:host *{box-sizing:border-box;pointer-events:auto;}
.rd-aura{
position:fixed;
top:0;left:0;right:0;bottom:0;
pointer-events:none;
z-index:2147483646;
opacity:0.5;
background:
radial-gradient(ellipse 800px 600px at 20% 0%, rgba(10,132,255,0.15), transparent 60%),
radial-gradient(ellipse 600px 500px at 80% 30%, rgba(91,192,190,0.12), transparent 60%),
radial-gradient(ellipse 500px 400px at 50% 80%, rgba(255,183,77,0.08), transparent 60%);
animation:rdAuraFloat 18s ease-in-out infinite;
}
@keyframes rdAuraFloat{
0%,100%{transform:translate3d(0,0,0) scale(1);}
33%{transform:translate3d(-2%,1%,0) scale(1.02);}
66%{transform:translate3d(2%,-1%,0) scale(0.98);}
}
#rd-fab{
position:fixed;
bottom:calc(120px + env(safe-area-inset-bottom, 0px));
right:20px;
width:60px;height:60px;
border-radius:50%;
background:linear-gradient(135deg,rgba(10,132,255,0.95),rgba(91,192,190,0.95));
backdrop-filter:var(--rd-blur-mini);
-webkit-backdrop-filter:var(--rd-blur-mini);
border:0.5px solid rgba(255,255,255,0.4);
box-shadow:var(--rd-shadow-fab);
display:flex;align-items:center;justify-content:center;
cursor:pointer;
transition:transform 0.45s cubic-bezier(0.34,1.56,0.64,1),box-shadow 0.45s ease,opacity 0.45s ease;
user-select:none;
-webkit-user-select:none;
will-change:transform;
transform:translateZ(0);
color:#fff;
pointer-events:auto;
touch-action:manipulation;
z-index:10;
}
#rd-fab::before{
content:'';
position:absolute;
inset:0;
border-radius:50%;
background:radial-gradient(circle at 30% 30%,rgba(255,255,255,0.5),transparent 60%);
opacity:0.7;
pointer-events:none;
}
#rd-fab:hover{
transform:scale(1.08) translateZ(0);
box-shadow:0 16px 48px rgba(10,132,255,0.28),inset 0 0.5px 0 rgba(255,255,255,0.7);
}
#rd-fab:active{transform:scale(0.92) translateZ(0);}
#rd-fab svg{width:32px;height:32px;stroke:currentColor;stroke-width:1.7;position:relative;z-index:1;}
:host(.rd-open) #rd-fab{
opacity:0;
pointer-events:none;
transform:scale(0.6) translateZ(0);
}
#rd-backdrop{
position:fixed;
inset:0;
background:rgba(10,15,30,0.28);
backdrop-filter:blur(6px) saturate(140%);
-webkit-backdrop-filter:blur(6px) saturate(140%);
opacity:0;
visibility:hidden;
pointer-events:none;
transition:opacity 0.35s ease,visibility 0s linear 0.35s;
z-index:9;
}
#rd-backdrop.active{
opacity:1;
visibility:visible;
pointer-events:auto;
transition:opacity 0.35s ease,visibility 0s;
}
#rd-panel{
position:fixed;
top:24px;left:0;right:0;bottom:0;
transform:translate3d(0,calc(100% + 24px),0);
transition:transform 0.45s cubic-bezier(0.32,0.94,0.6,1),visibility 0s linear 0.45s;
visibility:hidden;
background:var(--rd-bg-primary);
backdrop-filter:var(--rd-blur);
-webkit-backdrop-filter:var(--rd-blur);
border-top:0.5px solid var(--rd-border-strong);
border-radius:32px 32px 0 0;
box-shadow:0 -16px 60px rgba(10,30,80,0.12),inset 0 0.5px 0 rgba(255,255,255,0.4);
display:flex;flex-direction:column;
overflow:hidden;
contain:layout;
will-change:transform;
z-index:11;
}
#rd-panel::before{
content:'';
position:absolute;top:8px;left:50%;
transform:translateX(-50%);
width:40px;height:4px;
background:rgba(10,30,80,0.18);
border-radius:2px;
}
#rd-panel.active{transform:translate3d(0,0,0);visibility:visible;transition:transform 0.45s cubic-bezier(0.32,0.94,0.6,1),visibility 0s;}
/* PC/平板/大宽屏:面板锁定手机端尺寸(固定 420px 宽,右侧停靠浮窗),
不随页面宽度拉伸,图标文字保持与手机端一致的显示比例 */
@media (min-width:768px) and (min-height:520px){
#rd-panel{
top:24px;bottom:24px;left:auto;right:24px;
width:420px;max-width:calc(100vw - 48px);
border-radius:32px;
border-top:none;border-left:0.5px solid var(--rd-border-strong);border-right:0.5px solid var(--rd-border-strong);border-bottom:0.5px solid var(--rd-border-strong);
box-shadow:0 24px 80px rgba(10,30,80,0.18),inset 0 0.5px 0 rgba(255,255,255,0.4);
transform:translate3d(calc(100% + 48px),0,0);
}
#rd-panel.active{transform:translate3d(0,0,0);}
#rd-panel::before{display:none;}
}
.rd-header{
display:flex;
align-items:center;
padding:20px 20px 12px 20px;
flex-shrink:0;
gap:12px;
flex-wrap:nowrap;
overflow:hidden;
}
.rd-header .rd-title-area{
flex:1;
display:flex;
flex-direction:column;
min-width:0;
gap:2px;
}
.rd-header h3{
margin:0;
font-size:20px;
font-weight:600;
color:var(--rd-text-primary);
display:flex;
align-items:center;
gap:10px;
white-space:nowrap;
flex-shrink:0;
letter-spacing:-0.02em;
}
.rd-header h3 .rd-header-icon{
width:32px;height:32px;
border-radius:10px;
background:linear-gradient(135deg,var(--rd-accent),var(--rd-accent-light));
display:inline-flex;align-items:center;justify-content:center;
color:#fff;
box-shadow:0 4px 16px var(--rd-accent-glow);
}
.rd-header h3 .rd-header-icon svg{width:20px;height:20px;stroke:currentColor;stroke-width:1.8;}
.rd-header .rd-page-url{
font-size:11px;
color:var(--rd-text-tertiary);
overflow:hidden;
text-overflow:ellipsis;
white-space:nowrap;
max-width:100%;
}
.rd-controls{display:flex;gap:8px;flex-shrink:0;margin-left:auto;align-items:center;}
.rd-icon-btn{
width:40px;height:40px;
border:none;
background:var(--rd-bg-tertiary);
color:var(--rd-text-primary);
border-radius:50%;
cursor:pointer;
display:flex;align-items:center;justify-content:center;
padding:0;
transition:transform 0.25s cubic-bezier(0.34,1.56,0.64,1),background 0.25s ease,box-shadow 0.25s ease;
backdrop-filter:var(--rd-blur-mini);
-webkit-backdrop-filter:var(--rd-blur-mini);
box-shadow:var(--rd-shadow-card);
will-change:transform;
transform:translateZ(0);
pointer-events:auto;
touch-action:manipulation;
border:0.5px solid var(--rd-border);
}
.rd-icon-btn:hover{
background:var(--rd-bg-elevated);
transform:scale(1.06) translateZ(0);
box-shadow:var(--rd-shadow-elevated);
}
.rd-icon-btn:active{transform:scale(0.9) translateZ(0);}
.rd-icon-btn svg{width:20px;height:20px;stroke:currentColor;stroke-width:1.7;}
.rd-toolbar{padding:8px 20px 12px 20px;flex-shrink:0;}
.rd-filters-scroll{
overflow-x:auto;
scrollbar-width:none;-ms-overflow-style:none;
-webkit-overflow-scrolling:touch;
overscroll-behavior-x:none;
transform:translateZ(0);
padding:4px 0;
}
.rd-filters-scroll::-webkit-scrollbar{display:none;}
.rd-filters{display:flex;gap:10px;padding:2px 0;}
.rd-filter{
flex:1 1 0;
min-width:0;
padding:9px 12px;
background:var(--rd-bg-tertiary);
border-radius:16px;
cursor:pointer;
color:var(--rd-text-secondary);
transition:transform 0.3s cubic-bezier(0.34,1.56,0.64,1),background 0.3s ease,color 0.3s ease,box-shadow 0.3s ease,border-color 0.3s ease;
border:0.5px solid var(--rd-border);
display:flex;align-items:center;justify-content:center;
gap:8px;
white-space:nowrap;
will-change:transform,background,box-shadow,color;
transform:translateZ(0);
backdrop-filter:var(--rd-blur-mini);
-webkit-backdrop-filter:var(--rd-blur-mini);
box-shadow:var(--rd-shadow-card);
pointer-events:auto;
font-family:inherit;
}
.rd-filter:hover{
color:var(--rd-text-primary);
background:var(--rd-bg-elevated);
transform:translateY(-1px) translateZ(0);
box-shadow:var(--rd-shadow-elevated);
}
.rd-filter.active{
background:linear-gradient(135deg,var(--rd-accent),#4FC3F7);
color:#fff;
box-shadow:0 8px 28px var(--rd-accent-glow),inset 0 0.5px 0 rgba(255,255,255,0.3);
transform:translateY(0) translateZ(0);
border-color:transparent;
}
.rd-filter-icon{
width:36px;height:36px;flex-shrink:0;
display:flex;align-items:center;justify-content:center;
border-radius:12px;
background:linear-gradient(135deg,rgba(10,132,255,0.12),rgba(91,192,190,0.12));
transition:background 0.3s ease;
}
.rd-filter.active .rd-filter-icon{background:rgba(255,255,255,0.22);}
.rd-filter svg{width:22px;height:22px;display:block;flex-shrink:0;stroke:currentColor;stroke-width:1.7;}
.rd-filter-text{
display:flex;flex-direction:column;align-items:flex-start;justify-content:center;
gap:1px;line-height:1.15;
min-width:0;overflow:hidden;
}
.rd-filter-count{
font-size:16px;
font-weight:700;
color:var(--rd-text-primary);
line-height:1.1;
font-variant-numeric:tabular-nums;
transition:color 0.3s ease;
}
.rd-filter-name{
font-size:11px;
font-weight:500;
color:var(--rd-text-tertiary);
line-height:1.2;
transition:color 0.3s ease;
}
.rd-filter.active .rd-filter-count{color:#fff;}
.rd-filter.active .rd-filter-name{color:rgba(255,255,255,0.85);}
.rd-list{
flex:1;
overflow-y:auto;
padding:6px 20px calc(20px + env(safe-area-inset-bottom, 0px)) 20px;
-webkit-overflow-scrolling:touch;
overscroll-behavior:none;
will-change:scroll-position;
scrollbar-width:thin;scrollbar-color:var(--rd-border-strong) transparent;
}
.rd-list::-webkit-scrollbar{width:4px;}
.rd-list::-webkit-scrollbar-track{background:transparent;}
.rd-list::-webkit-scrollbar-thumb{background:var(--rd-border-strong);border-radius:8px;}
.rd-list-content{display:flex;flex-direction:column;gap:10px;padding-bottom:6px;}
.rd-list-content.rd-grid-view{display:grid;grid-template-columns:repeat(auto-fill,minmax(148px,1fr));gap:12px;}
.rd-list-content.rd-grid-view .rd-entry{flex-direction:column;align-items:stretch;min-height:auto;padding:10px;gap:8px;}
.rd-list-content.rd-grid-view .rd-entry-thumb-wrapper{width:100%;height:92px;flex-shrink:0;border-radius:14px;align-self:stretch;}
.rd-list-content.rd-grid-view .rd-entry-thumb{border-radius:14px;}
.rd-list-content.rd-grid-view .rd-entry-fallback svg{width:34px;height:34px;}
.rd-list-content.rd-grid-view .rd-entry-content{gap:6px;}
.rd-list-content.rd-grid-view .rd-entry-header{flex-direction:column;align-items:flex-start;height:auto;gap:6px;}
.rd-list-content.rd-grid-view .rd-entry-filename{white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;width:100%;flex:none;font-size:12px;}
.rd-list-content.rd-grid-view .rd-entry-actions{margin-left:0;width:100%;justify-content:flex-end;}
.rd-list-content.rd-grid-view .rd-action-btn{width:30px;height:30px;}
.rd-list-content.rd-grid-view .rd-entry-url{-webkit-line-clamp:1;max-height:1.5em;}
.rd-entry{
display:flex;gap:14px;
padding:14px 16px;
background:var(--rd-bg-elevated);
border-radius:var(--rd-radius-lg);
transition:transform 0.25s ease,background 0.25s ease,border-color 0.25s ease,box-shadow 0.25s ease;
cursor:pointer;
align-items:center;
min-height:72px;
contain:layout style;
border:0.5px solid var(--rd-border);
box-shadow:var(--rd-shadow-card);
pointer-events:auto;
position:relative;
overflow:hidden;
touch-action:manipulation;
}
.rd-entry::before{
content:'';
position:absolute;
top:0;left:0;right:0;height:50%;
background:linear-gradient(180deg,rgba(255,255,255,0.3),transparent);
border-radius:var(--rd-radius-lg) var(--rd-radius-lg) 0 0;
pointer-events:none;
opacity:0.6;
}
.rd-entry:hover{
transform:translateY(-2px);
box-shadow:0 12px 36px rgba(10,30,80,0.08),0 2px 8px rgba(10,30,80,0.04);
border-color:var(--rd-accent-glow);
background:var(--rd-bg-elevated);
}
.rd-entry:active{transform:scale(0.98);}
.rd-entry-thumb-wrapper{
width:52px;height:52px;
flex-shrink:0;
display:flex;align-items:center;justify-content:center;
background:linear-gradient(135deg,rgba(255,255,255,0.5),rgba(255,255,255,0.3));
border-radius:18px;
overflow:hidden;
align-self:center;
contain:layout style;
box-shadow:inset 0 1px 4px rgba(10,30,80,0.04),0 1px 2px rgba(10,30,80,0.02);
border:0.5px solid var(--rd-border);
position:relative;
z-index:1;
}
.rd-entry-thumb{
width:100%;height:100%;
object-fit:cover;
border-radius:18px;
}
.rd-entry-fallback{
display:flex;align-items:center;justify-content:center;
color:var(--rd-accent);
}
.rd-entry-fallback svg{width:28px;height:28px;stroke:currentColor;stroke-width:1.6;}
.rd-entry-content{
flex:1;
min-width:0;
display:flex;flex-direction:column;
justify-content:center;
gap:4px;
padding:0;
contain:layout;
position:relative;
z-index:1;
}
.rd-entry-header{
display:flex;align-items:center;
gap:10px;
height:24px;
min-width:0;
overflow:hidden;
}
.rd-entry-type{
color:#fff;
padding:0 10px;
border-radius:8px;
font-size:10px;
font-weight:600;
text-transform:uppercase;
letter-spacing:0.5px;
height:20px;
line-height:20px;
display:inline-flex;align-items:center;justify-content:center;
flex-shrink:0;
box-shadow:0 2px 8px rgba(10,30,80,0.08);
}
${Object.entries(TYPE_COLORS).map(([t,c])=>`.rd-entry-type.${t}{background:linear-gradient(135deg,${c},${c}dd)!important;}`).join('')}
.rd-entry-type.rd-invalid-badge{background:linear-gradient(135deg,#EF5350,#C62828)!important;color:#fff;flex-shrink:0;}
.rd-entry.rd-entry-broken{opacity:0.55;filter:saturate(0.55);}
.rd-entry.rd-entry-broken .rd-entry-thumb{filter:grayscale(0.7);}
.rd-entry-filename{
font-size:13px;
font-weight:500;
color:var(--rd-text-primary);
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
flex:1;
min-width:0;
}
.rd-entry-actions{
display:flex;gap:4px;
margin-left:auto;
align-items:center;
flex-shrink:0;
}
.rd-action-btn{
cursor:pointer;
width:34px;height:34px;
border-radius:50%;
background:rgba(255,255,255,0.25);
transition:transform 0.2s cubic-bezier(0.34,1.56,0.64,1),background 0.2s ease,color 0.2s ease,box-shadow 0.2s ease;
border:0.5px solid rgba(255,255,255,0.18);
display:flex;align-items:center;justify-content:center;
padding:0;
color:var(--rd-text-secondary);
pointer-events:auto;
touch-action:manipulation;
}
.rd-action-btn:hover{
background:rgba(255,255,255,0.6);
transform:scale(1.08);
color:var(--rd-text-primary);
box-shadow:0 4px 12px rgba(10,30,80,0.06);
}
.rd-action-btn:active{transform:scale(0.88);}
.rd-action-btn svg{width:16px;height:16px;stroke:currentColor;stroke-width:1.7;pointer-events:none;}
.rd-download-btn{color:var(--rd-accent);}
.rd-download-btn:hover{color:#0070DD;background:rgba(10,132,255,0.1);}
.rd-play-btn{color:#FF6B6B;}
.rd-play-btn:hover{background:rgba(255,107,107,0.1);color:#FF5252;}
.rd-copy-btn{color:var(--rd-text-secondary);}
.rd-copy-btn:hover{background:rgba(0,0,0,0.04);color:var(--rd-text-primary);}
.rd-entry-url{
word-break:break-all;
color:var(--rd-text-tertiary);
font-family:'HarmonyOS Sans Mono','SF Mono',Monaco,Consolas,monospace;
font-size:10.5px;
line-height:1.5;
max-height:3em;
overflow:hidden;
display:-webkit-box;
-webkit-line-clamp:2;
-webkit-box-orient:vertical;
contain:layout;
opacity:0.75;
transition:opacity 0.3s;
}
.rd-entry:hover .rd-entry-url{opacity:1;}
.rd-empty{
text-align:center;
color:var(--rd-text-secondary);
padding:80px 20px;
font-size:14px;
display:flex;flex-direction:column;align-items:center;
gap:20px;
contain:layout;
}
.rd-empty-icon{
width:80px;height:80px;
border-radius:24px;
background:linear-gradient(135deg,rgba(10,132,255,0.08),rgba(91,192,190,0.08));
display:flex;align-items:center;justify-content:center;
color:var(--rd-accent);
margin-bottom:8px;
}
.rd-empty-icon svg{width:44px;height:44px;stroke:currentColor;stroke-width:1.5;}
#rd-toast{
position:fixed;
top:50%;left:50%;
transform:translate(-50%,-50%) scale(0.94);
background:rgba(20,28,40,0.9);
color:#f5f5f7;
padding:14px 24px;
border-radius:var(--rd-radius-pill);
font-size:14px;
font-weight:500;
z-index:2147483647;
pointer-events:none;
opacity:0;
transition:opacity 0.35s ease,transform 0.35s ease;
box-shadow:0 16px 48px rgba(0,0,0,0.24),inset 0 0.5px 0 rgba(255,255,255,0.1);
border:0.5px solid rgba(255,255,255,0.08);
display:flex;align-items:center;gap:8px;
}
#rd-toast.show{
opacity:1;
transform:translate(-50%,-50%) scale(1);
}
.rd-toast-icon{width:18px;height:18px;color:#5BC0BE;stroke-width:2.2;}
#rd-inline-player,#rd-img-preview,#rd-text-preview,#rd-source-preview{
position:fixed;
z-index:2147483647;
top:24px;left:0;right:0;bottom:0;
pointer-events:auto;
animation:rdFadeIn 0.4s ease;
border-top:0.5px solid var(--rd-border-strong);
border-radius:var(--rd-radius-xl) var(--rd-radius-xl) 0 0;
box-shadow:0 -16px 60px rgba(10,30,80,0.12),inset 0 0.5px 0 rgba(255,255,255,0.4);
}
@keyframes rdFadeIn{
0%{opacity:0;}
100%{opacity:1;}
}
#rd-inline-player{
background:var(--rd-bg-primary);
overflow:hidden;
display:flex;flex-direction:column;
}
#rd-player-header,#rd-img-preview-header,#rd-text-preview-header,#rd-source-preview-header{
display:flex;align-items:center;justify-content:space-between;
padding:18px 20px;
background:transparent;
flex-shrink:0;height:auto;
contain:layout;
}
#rd-player-title,#rd-img-preview-title,#rd-text-preview-title,#rd-source-preview-title{
color:var(--rd-text-primary);
font-size:17px;
font-weight:600;
display:flex;align-items:center;
gap:10px;
letter-spacing:-0.01em;
}
#rd-player-title svg,#rd-img-preview-title svg,#rd-text-preview-title svg,#rd-source-preview-title svg{
width:22px;height:22px;
stroke:currentColor;stroke-width:1.6;
color:var(--rd-accent);
}
#rd-media-wrapper{
flex:1;
display:flex;align-items:center;justify-content:center;
background:linear-gradient(180deg,var(--rd-bg-secondary),var(--rd-bg-primary));
overflow:hidden;
min-height:0;
position:relative;
width:100%;
padding:4px 20px 20px 20px;
}
#rd-video{
width:100%;height:100%;
max-width:100%;max-height:100%;
object-fit:contain;
display:block;
background:#000;
transform:translateZ(0);
-webkit-transform:translateZ(0);
will-change:transform;
backface-visibility:hidden;
-webkit-backface-visibility:hidden;
}
#rd-video-wrapper{
position:relative;
width:100%;height:100%;
display:flex;align-items:center;justify-content:center;
border-radius:var(--rd-radius-md);
overflow:hidden;
contain:layout style paint;
}
.rd-video-loading{
position:absolute;
top:50%;left:50%;
transform:translate(-50%,-50%);
width:48px;height:48px;
border:3px solid rgba(255,255,255,0.15);
border-top-color:#0A84FF;
border-radius:50%;
animation:rdSpin 0.8s linear infinite;
pointer-events:none;
z-index:2;
}
@keyframes rdSpin{to{transform:translate(-50%,-50%) rotate(360deg);}}
.rd-video-controls{
position:absolute;
bottom:0;left:0;right:0;
display:flex;flex-direction:column;
gap:6px;
padding:10px 14px 24px;
background:linear-gradient(0deg,rgba(0,0,0,0.7),transparent);
opacity:0;
transition:opacity 0.3s ease;
z-index:3;
}
#rd-video-wrapper.rd-show-controls .rd-video-controls{opacity:1;}
.rd-video-progress-bar{
width:100%;
display:flex;align-items:center;
gap:10px;
}
.rd-video-btn-row{
display:flex;align-items:center;
gap:8px;
}
.rd-video-btn{
width:34px;height:34px;
border:none;
background:rgba(255,255,255,0.12);
color:#fff;
border-radius:8px;
cursor:pointer;
display:flex;align-items:center;justify-content:center;
padding:0;
flex-shrink:0;
transition:background 0.2s ease,transform 0.15s ease;
backdrop-filter:blur(4px);
-webkit-backdrop-filter:blur(4px);
}
.rd-video-btn:hover{background:rgba(255,255,255,0.25);}
.rd-video-btn:active{transform:scale(0.9);}
.rd-video-btn.active{background:var(--rd-accent);}
.rd-video-btn svg{width:18px;height:18px;stroke:currentColor;stroke-width:1.7;pointer-events:none;}
.rd-video-progress{
flex:1;min-width:0;
height:24px;
display:flex;align-items:center;
cursor:pointer;
position:relative;
}
.rd-video-progress-track{
width:100%;height:4px;
background:rgba(255,255,255,0.2);
border-radius:4px;
position:relative;
overflow:hidden;
transition:height 0.2s ease;
}
.rd-video-progress:hover .rd-video-progress-track{height:6px;}
.rd-video-progress-filled{
position:absolute;left:0;top:0;height:100%;
background:linear-gradient(90deg,#0A84FF,#5BC0BE);
border-radius:4px;
pointer-events:none;
}
.rd-video-progress-thumb{
position:absolute;top:50%;
width:12px;height:12px;
background:#fff;
border-radius:50%;
transform:translate(-50%,-50%);
pointer-events:none;
box-shadow:0 0 0 3px rgba(10,132,255,0.4);
opacity:0;
transition:opacity 0.2s ease;
}
.rd-video-progress:hover .rd-video-progress-thumb{opacity:1;}
.rd-video-time{
font-size:12px;
font-family:'HarmonyOS Sans Mono','SF Mono',Monaco,Consolas,monospace;
color:rgba(255,255,255,0.9);
flex-shrink:0;
font-weight:500;
user-select:none;
-webkit-user-select:none;
}
.rd-video-speed-menu{
position:absolute;
bottom:62px;
right:14px;
display:none;
flex-direction:column;
gap:2px;
padding:6px;
background:rgba(20,28,40,0.92);
border-radius:12px;
box-shadow:0 8px 32px rgba(0,0,0,0.3);
backdrop-filter:blur(12px);
-webkit-backdrop-filter:blur(12px);
border:0.5px solid rgba(255,255,255,0.08);
z-index:4;
}
.rd-video-speed-menu.rd-open{display:flex;}
.rd-video-speed-item{
padding:7px 16px;
border-radius:8px;
font-size:13px;
font-weight:500;
color:rgba(255,255,255,0.7);
cursor:pointer;
text-align:center;
transition:background 0.15s ease,color 0.15s ease;
user-select:none;
-webkit-user-select:none;
}
.rd-video-speed-item:hover{background:rgba(255,255,255,0.08);color:#fff;}
.rd-video-speed-item.active{background:var(--rd-accent);color:#fff;}
.rd-video-center-btn{
position:absolute;
top:50%;left:50%;
transform:translate(-50%,-50%);
width:64px;height:64px;
border-radius:50%;
background:rgba(0,0,0,0.5);
border:none;
color:#fff;
display:flex;align-items:center;justify-content:center;
cursor:pointer;
z-index:2;
opacity:0;
transition:opacity 0.25s ease;
backdrop-filter:blur(8px);
-webkit-backdrop-filter:blur(8px);
pointer-events:none;
}
#rd-video-wrapper.rd-show-center .rd-video-center-btn{opacity:1;pointer-events:auto;}
.rd-video-center-btn svg{width:28px;height:28px;stroke:currentColor;stroke-width:1.8;fill:currentColor;}
#rd-video-wrapper:fullscreen,#rd-video-wrapper:-webkit-full-screen{
border-radius:0;
background:#000;
}
#rd-video-wrapper:fullscreen #rd-video,#rd-video-wrapper:-webkit-full-screen #rd-video{
border-radius:0;
}
#rd-inline-player:fullscreen,#rd-inline-player:-webkit-full-screen{
width:100%;max-width:100%;
height:100%;max-height:100%;
border-radius:0;
}
.rd-custom-audio{
width:100%;
padding:28px 32px;
display:flex;flex-direction:column;
gap:20px;
background:var(--rd-bg-secondary);
border-radius:var(--rd-radius-lg);
margin:8px 0;
backdrop-filter:var(--rd-blur-mini);
-webkit-backdrop-filter:var(--rd-blur-mini);
border:0.5px solid var(--rd-border);
box-shadow:var(--rd-shadow-card);
position:relative;
overflow:hidden;
}
.rd-custom-audio::before{
content:'';
position:absolute;
top:0;left:0;right:0;height:50%;
background:linear-gradient(180deg,rgba(255,255,255,0.4),transparent);
border-radius:var(--rd-radius-lg) var(--rd-radius-lg) 0 0;
pointer-events:none;
}
.rd-audio-info{
display:flex;align-items:center;
gap:14px;
color:var(--rd-text-primary);
font-size:14px;
font-weight:500;
word-break:break-all;
position:relative;
}
.rd-audio-info-icon{
width:44px;height:44px;
border-radius:14px;
background:linear-gradient(135deg,var(--rd-accent),var(--rd-accent-light));
display:flex;align-items:center;justify-content:center;
color:#fff;
box-shadow:0 4px 16px var(--rd-accent-glow);
}
.rd-audio-info-icon svg{width:24px;height:24px;stroke:currentColor;stroke-width:1.6;}
.rd-audio-filename{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.rd-audio-controls{display:flex;align-items:center;gap:14px;flex-wrap:wrap;position:relative;}
.rd-play-pause-btn{
width:48px;height:48px;
border-radius:50%;
background:linear-gradient(135deg,var(--rd-accent),#4FC3F7);
border:none;
display:flex;align-items:center;justify-content:center;
cursor:pointer;
color:#fff;
transition:all 0.3s cubic-bezier(0.34,1.56,0.64,1);
flex-shrink:0;
box-shadow:0 8px 24px var(--rd-accent-glow);
pointer-events:auto;
}
.rd-play-pause-btn:hover{transform:scale(1.06);box-shadow:0 12px 36px var(--rd-accent-glow);}
.rd-play-pause-btn:active{transform:scale(0.9);}
.rd-play-pause-btn svg{width:22px;height:22px;stroke:currentColor;stroke-width:1.8;fill:currentColor;}
.rd-progress-container{
flex:1;
position:relative;height:32px;
display:flex;align-items:center;
cursor:pointer;
pointer-events:auto;
}
.rd-progress-bg{
width:100%;height:4px;
background:rgba(10,30,80,0.08);
border-radius:4px;
position:relative;
overflow:hidden;
}
.rd-progress-filled{
position:absolute;left:0;top:0;height:100%;width:0;
background:linear-gradient(90deg,var(--rd-accent),var(--rd-accent-light));
border-radius:4px;
pointer-events:none;
transition:width 0.08s;
}
.rd-progress-thumb{
position:absolute;top:50%;
width:14px;height:14px;
background:#fff;
border-radius:50%;
transform:translate(-50%,-50%);
pointer-events:none;
box-shadow:0 2px 8px var(--rd-accent-glow),0 0 0 2px var(--rd-accent);
transition:box-shadow 0.2s,transform 0.2s;
}
.rd-progress-container:hover .rd-progress-thumb{
transform:translate(-50%,-50%) scale(1.15);
box-shadow:0 4px 16px var(--rd-accent-glow),0 0 0 2px var(--rd-accent);
}
.rd-time{
font-size:12px;
font-family:'HarmonyOS Sans Mono','SF Mono',Monaco,Consolas,monospace;
color:var(--rd-text-secondary);
display:flex;gap:4px;
flex-shrink:0;
font-weight:500;
}
#rd-img-preview{
background:var(--rd-bg-primary);
display:flex;flex-direction:column;
overflow:hidden;
}
#rd-img-preview-content{
flex:1;
display:flex;align-items:center;justify-content:center;
overflow:hidden;
padding:20px;
background:transparent;
cursor:zoom-out;
}
#rd-preview-img{
max-width:100%;max-height:100%;
width:auto;height:auto;
object-fit:contain;
border-radius:18px;
cursor:default;
box-shadow:0 8px 40px rgba(10,30,80,0.08);
}
#rd-text-preview,#rd-source-preview{
background:var(--rd-bg-primary);
display:flex;flex-direction:column;
overflow:hidden;
}
#rd-text-preview-scroll,#rd-source-preview-scroll{
flex:1;
overflow:auto;
padding:20px;
background:var(--rd-bg-primary);
-webkit-overflow-scrolling:touch;
overscroll-behavior:none;
will-change:scroll-position;
}
#rd-text-preview-scroll::-webkit-scrollbar,#rd-source-preview-scroll::-webkit-scrollbar{width:4px;}
#rd-text-preview-scroll::-webkit-scrollbar-track,#rd-source-preview-scroll::-webkit-scrollbar-track{background:transparent;}
#rd-text-preview-scroll::-webkit-scrollbar-thumb,#rd-source-preview-scroll::-webkit-scrollbar-thumb{background:var(--rd-border-strong);border-radius:8px;}
#rd-text-preview-content,#rd-source-preview-content{
margin:0!important;
padding:16px!important;
border-radius:14px!important;
min-height:calc(100% - 1px);
box-sizing:border-box;
display:block;
}
.rd-iframe-container{
flex:1;
position:relative;
background:transparent;
overflow:hidden;
border-radius:0;
}
.rd-iframe-container iframe{
width:100%;height:100%;
border:none;
background:transparent;
}
*{outline:none!important;-webkit-tap-highlight-color:transparent!important;}
:host(.rd-scrolling) .rd-aura{animation-play-state:paused!important;}
:host(.rd-scrolling) #rd-panel{backdrop-filter:none!important;-webkit-backdrop-filter:none!important;}
:host(.rd-scrolling) .rd-list{background:var(--rd-bg-primary);}
:host(.rd-scrolling) #rd-text-preview,:host(.rd-scrolling) #rd-source-preview{backdrop-filter:none!important;-webkit-backdrop-filter:none!important;}
:host(.rd-scrolling) #rd-text-preview-scroll,:host(.rd-scrolling) #rd-source-preview-scroll{background:var(--rd-bg-primary);}
@media (prefers-color-scheme: dark){
:host{
--rd-bg-primary:rgba(20,24,32,0.82);
--rd-bg-secondary:rgba(36,40,50,0.65);
--rd-bg-tertiary:rgba(255,255,255,0.04);
--rd-bg-elevated:rgba(48,52,64,0.85);
--rd-border:rgba(255,255,255,0.05);
--rd-border-strong:rgba(255,255,255,0.08);
--rd-text-primary:#f5f5f7;
--rd-text-secondary:#a8acb3;
--rd-text-tertiary:#6c6c70;
--rd-accent:#0A84FF;
--rd-accent-light:#5BC0BE;
--rd-accent-glow:rgba(10,132,255,0.32);
--rd-accent-glow-soft:rgba(10,132,255,0.16);
--rd-shadow-card:0 2px 12px rgba(0,0,0,0.2),0 1px 2px rgba(0,0,0,0.1);
--rd-shadow-elevated:0 8px 32px rgba(0,0,0,0.3),0 2px 8px rgba(0,0,0,0.15);
--rd-shadow-fab:0 12px 40px rgba(10,132,255,0.32),inset 0 0.5px 0 rgba(255,255,255,0.1);
}
#rd-fab{
background:linear-gradient(135deg,rgba(10,132,255,0.92),rgba(91,192,190,0.92));
border-color:rgba(255,255,255,0.12);
}
#rd-panel{
background:var(--rd-bg-primary);
border-top-color:rgba(255,255,255,0.06);
box-shadow:0 -16px 60px rgba(0,0,0,0.4),inset 0 0.5px 0 rgba(255,255,255,0.06);
}
#rd-panel::before{background:rgba(255,255,255,0.18);}
.rd-header h3{color:var(--rd-text-primary);}
.rd-icon-btn{background:rgba(255,255,255,0.05);border-color:rgba(255,255,255,0.04);color:var(--rd-text-primary);}
.rd-icon-btn:hover{background:rgba(255,255,255,0.1);}
.rd-filter{background:rgba(255,255,255,0.04);border-color:rgba(255,255,255,0.04);color:var(--rd-text-secondary);}
.rd-filter:hover{background:rgba(255,255,255,0.08);color:var(--rd-text-primary);}
.rd-filter.active{background:linear-gradient(135deg,var(--rd-accent),#4FC3F7);color:#fff;}
.rd-entry{background:var(--rd-bg-elevated);border-color:rgba(255,255,255,0.05);}
.rd-entry:hover{background:rgba(255,255,255,0.1);border-color:rgba(10,132,255,0.2);}
.rd-entry::before{background:linear-gradient(180deg,rgba(255,255,255,0.04),transparent);}
.rd-entry-thumb-wrapper{background:linear-gradient(135deg,rgba(255,255,255,0.06),rgba(255,255,255,0.02));border-color:rgba(255,255,255,0.05);}
.rd-action-btn{background:rgba(255,255,255,0.04);border-color:rgba(255,255,255,0.04);}
.rd-action-btn:hover{background:rgba(255,255,255,0.08);}
.rd-custom-audio{background:rgba(255,255,255,0.04);border-color:rgba(255,255,255,0.05);}
.rd-custom-audio::before{background:linear-gradient(180deg,rgba(255,255,255,0.04),transparent);}
.rd-progress-bg{background:rgba(255,255,255,0.08);}
.rd-entry-url{color:var(--rd-text-tertiary);}
.rd-empty{color:var(--rd-text-secondary);}
.rd-empty-icon{background:linear-gradient(135deg,rgba(10,132,255,0.12),rgba(91,192,190,0.12));}
}
`;
this._shadowRoot.appendChild(style);
},
getFilterIcon(type){
const map={image:Icons.image, media:Icons.waveform, other:Icons.file};
return map[type]||Icons.file;
},
getFilterName(type){
const names={image:'图片', media:'媒体', other:'其他'};
return names[type]||type;
},
createUI(){
if(!this._shadowRoot)return;
const c=document.createElement('div');c.className='rd-container';
c.innerHTML=`
${Icons.box}
`;
this._shadowRoot.appendChild(c);
this._elements={
floatingBtn:this._shadowRoot.getElementById('rd-fab'),
backdrop:this._shadowRoot.getElementById('rd-backdrop'),
panel:this._shadowRoot.getElementById('rd-panel'),
list:this._shadowRoot.getElementById('rd-list'),
listContent:this._shadowRoot.querySelector('.rd-list-content'),
closeBtn:this._shadowRoot.getElementById('rd-close'),
viewSourceBtn:this._shadowRoot.getElementById('rd-view-source'),
viewModeBtn:this._shadowRoot.getElementById('rd-view-mode'),
sortBtn:this._shadowRoot.getElementById('rd-sort')
};
this.updateViewModeBtn();
this.updateSortBtn();
},
bindEvents(){
if(!this._shadowRoot)return;
const root=this._shadowRoot;
root.addEventListener('click',e=>{
const t=e.target;
if(t.closest('#rd-close')){this.forceHidePanel();return;}
if(t.closest('#rd-view-source')){this.viewSource();return;}
if(t.closest('#rd-view-mode')){this.toggleViewMode();return;}
if(t.closest('#rd-sort')){this.toggleSort();return;}
const filter=t.closest('.rd-filter');if(filter){this.setFilter(filter.dataset.filter);return;}
const action=t.closest('.rd-action-btn');if(action){e.stopPropagation();const url=action.dataset.url;if(!url||!SecurityUtils.isSafeUrl(url))return;if(action.classList.contains('rd-download-btn'))this.downloadResource(url);else if(action.classList.contains('rd-play-btn'))this.playMedia(url,action.dataset.displayType);else if(action.classList.contains('rd-copy-btn'))this.copyToClipboard(url);return;}
const thumb=t.closest('.rd-entry-thumb-wrapper');if(thumb){e.stopPropagation();const url=thumb.dataset.url;if(url&&SecurityUtils.isSafeUrl(url))this.previewImage(url);return;}
const entry=t.closest('.rd-entry');if(entry&&!t.closest('.rd-entry-actions')){const url=entry.dataset.url;const displayType=entry.dataset.displayType;const filterType=this.getFilterType(displayType);if(!url||!SecurityUtils.isSafeUrl(url))return;if(filterType==='media')this.playMedia(url,displayType);else if(filterType==='image')this.previewImage(url);else this.previewTextContent(url);}
});
const fab=this._elements.floatingBtn;
if(fab){
fab.addEventListener('click', e => {
e.stopPropagation();
this.togglePanel();
});
}
const bd=this._elements.backdrop;if(bd)bd.addEventListener('click',e=>{e.stopPropagation();this.hidePanel();});
const closeBtn=this._elements.closeBtn;if(closeBtn)closeBtn.addEventListener('click',e=>{e.stopPropagation();this.forceHidePanel();});
this.scheduleUpdate();
const list=this._elements.list;if(list){let sx=0,sy=0,sw=false,st=0;list.addEventListener('touchstart',e=>{sx=e.changedTouches[0].screenX;sy=e.changedTouches[0].screenY;st=Date.now();sw=false;},{passive:true});list.addEventListener('touchmove',e=>{if(!sx)return;const x=e.changedTouches[0].screenX,y=e.changedTouches[0].screenY;const dx=Math.abs(sx-x),dy=Math.abs(sy-y);if(dx>dy&&dx>10)sw=true;},{passive:true});list.addEventListener('touchend',e=>{if(!sw)return;const dx=sx-e.changedTouches[0].screenX;const dt=Date.now()-st;if(Math.abs(dx)>50&&dt<300){const filters=['image','media','other'];const ci=filters.indexOf(this.currentFilter);const ni=dx>0?Math.min(ci+1,filters.length-1):Math.max(ci-1,0);if(ni!==ci){this.setFilter(filters[ni]);setTimeout(()=>this.scrollFilterToActive(),50);}}sx=0;sw=false;},{passive:true});this._bindScrollOptimization(list);}
},
_bindScrollOptimization(el){
if(!el||el._rdScrollBound)return;el._rdScrollBound=true;
let t;el.addEventListener('scroll',()=>{this._host.classList.add('rd-scrolling');clearTimeout(t);t=setTimeout(()=>{this._host.classList.remove('rd-scrolling');},150);},{passive:true});
},
forceHidePanel(){
const p=this._elements.panel;
if(p)p.classList.remove('active');
const bd=this._elements.backdrop;
if(bd)bd.classList.remove('active');
if(this._host)this._host.classList.remove('rd-open');
this.isPanelOpen=false;
this._unlockBodyScroll();
},
setupKeyboardShortcuts(){
const handler=e=>{
if(document.hidden)return;
if(this._lastVisibleTime&&Date.now()-this._lastVisibleTime<800)return;
if(e.key==='Escape'){
if(this._shadowRoot){
const hadOverlay=!!(this._shadowRoot.getElementById('rd-inline-player')||this._shadowRoot.getElementById('rd-img-preview')||this._shadowRoot.getElementById('rd-text-preview')||this._shadowRoot.getElementById('rd-source-preview'));
if(hadOverlay){try{const v=this._shadowRoot.querySelector('#rd-inline-player video')||this._shadowRoot.querySelector('#rd-inline-player audio');if(v){if(v._hls){try{v._hls.destroy();}catch{}}v.pause();}}catch{}}
this._shadowRoot.getElementById('rd-inline-player')?.remove();
this._shadowRoot.getElementById('rd-img-preview')?.remove();
this._shadowRoot.getElementById('rd-text-preview')?.remove();
this._shadowRoot.getElementById('rd-source-preview')?.remove();
if(hadOverlay){
this._unlockBodyScroll();
return;
}
}
if(this.isPanelOpen)this.hidePanel();
}
};
document.addEventListener('keydown',handler);
this._listeners.push(()=>document.removeEventListener('keydown',handler));
},
setFilter(filter){
if(this.currentFilter===filter){
this.currentFilter=null;
}else{
this.currentFilter=filter;
}
this.updateFilterButtons();
this.scrollFilterToActive();
if(this.currentFilter==='media'||this.currentFilter==='image')this.scanExistingResources();
this.scheduleUpdate();
},
toggleViewMode(){
this.viewMode=this.viewMode==='list'?'grid':'list';
this.updateViewModeBtn();
this.applyViewMode();
},
updateViewModeBtn(){
const btn=this._elements.viewModeBtn;if(!btn)return;
btn.innerHTML=this.viewMode==='list'?Icons.list:Icons.grid;
btn.title=this.viewMode==='list'?'列表视图(点击切换网格)':'网格视图(点击切换列表)';
},
applyViewMode(){
const c=this._elements.listContent;if(c)c.classList.toggle('rd-grid-view',this.viewMode==='grid');
},
toggleSort(){
const modes=['time-desc','name-asc','name-desc','time-asc'];
const idx=modes.indexOf(this.sortMode);
this.sortMode=modes[(idx+1)%modes.length];
this.updateSortBtn();
this._renderSignature='';
this.renderList();
this.showToast(this.getSortInfo(this.sortMode).title);
},
updateSortBtn(){
const btn=this._elements.sortBtn;if(!btn)return;
const info=this.getSortInfo(this.sortMode);
btn.innerHTML=info.icon;
btn.title=info.title;
},
getSortInfo(mode){
const map={
'time-desc':{title:'排序:最新优先',icon:Icons.sortDesc},
'time-asc':{title:'排序:最旧优先',icon:Icons.sortAsc},
'name-asc':{title:'排序:名称 A→Z',icon:Icons.sortAsc},
'name-desc':{title:'排序:名称 Z→A',icon:Icons.sortDesc}
};
return map[mode]||map['time-desc'];
},
sortResources(arr){
const m=this.sortMode;
arr.sort((a,b)=>{
switch(m){
case 'time-desc':return (b.timestamp||0)-(a.timestamp||0);
case 'time-asc':return (a.timestamp||0)-(b.timestamp||0);
case 'name-asc':{const x=(a.filename||'').toLowerCase(),y=(b.filename||'').toLowerCase();return xy?1:0;}
case 'name-desc':{const x=(a.filename||'').toLowerCase(),y=(b.filename||'').toLowerCase();return xy?-1:0;}
default:return 0;
}
});
return arr;
},
_lockDepth:0,
_savedBodyOverflow:'',
_savedHtmlOverflow:'',
_savedBodyPaddingRight:'',
_lockBodyScroll(){
this._lockDepth++;
if(this._lockDepth>1)return;
const body=document.body;
const html=document.documentElement;
this._savedBodyOverflow=body.style.overflow;
this._savedHtmlOverflow=html.style.overflow;
this._savedBodyPaddingRight=body.style.paddingRight;
const sw=window.innerWidth-document.documentElement.clientWidth;
if(sw>0)body.style.paddingRight=sw+'px';
body.style.overflow='hidden';
html.style.overflow='hidden';
},
_unlockBodyScroll(){
if(this._lockDepth>0)this._lockDepth--;
if(this._lockDepth>0)return;
const body=document.body;
const html=document.documentElement;
body.style.overflow=this._savedBodyOverflow;
html.style.overflow=this._savedHtmlOverflow;
body.style.paddingRight=this._savedBodyPaddingRight;
},
togglePanel(){
const p=this._elements.panel;if(!p)return;
if(p.classList.contains('active')){this.hidePanel();}else{this._openPanel();}
},
_openPanel(){
const p=this._elements.panel;if(!p)return;
p.classList.add('active');this._elements.backdrop?.classList.add('active');this._host?.classList.add('rd-open');this.isPanelOpen=true;this._lockBodyScroll();this.manualRefresh();this.renderList();this.scrollFilterToActive();
},
hidePanel(){
const p=this._elements.panel;if(p)p.classList.remove('active');this._elements.backdrop?.classList.remove('active');this._host?.classList.remove('rd-open');this._host?.classList.remove('rd-scrolling');this.isPanelOpen=false;
this._unlockBodyScroll();
if(this._shadowRoot){setTimeout(()=>{const ev=this._shadowRoot.querySelector('#rd-inline-player video');if(ev&&ev._hls){try{ev._hls.destroy();}catch{}}this._shadowRoot.getElementById('rd-inline-player')?.remove();this._shadowRoot.getElementById('rd-img-preview')?.remove();this._shadowRoot.getElementById('rd-text-preview')?.remove();this._shadowRoot.getElementById('rd-source-preview')?.remove();},300);}
},
_ensureHostAttached(){
if(this.isDestroyed)return;
if(this._host&&this._host.isConnected)return;
const wasOpen=this.isPanelOpen;
this.setupUI();
this.updateFilterButtons();
if(wasOpen)this._openPanel();
},
setupVisibilityHandler(){
const self=this;
let wasHidden=false;
const getVideo=()=>{try{return self._shadowRoot&&self._shadowRoot.querySelector('#rd-inline-player video');}catch{return null;}};
const resumeVideo=v=>{if(!v)return;if(v._hls){try{v._hls.startLoad();}catch{}}if(v._rdWasPlaying){v._rdWasPlaying=false;try{v.play().catch(()=>{});}catch{}}};
const onVis=()=>{
const v=getVideo();
if(document.hidden){
wasHidden=true;
if(v&&!v.paused){v._rdWasPlaying=true;try{v.pause();}catch{}}
}else{
self._lastVisibleTime=Date.now();
self._ensureHostAttached();
if(wasHidden){
wasHidden=false;
resumeVideo(getVideo());
self.scanExistingResources();
}
}
};
const onPageShow=e=>{if(e&&e.persisted){self._lastVisibleTime=Date.now();self._ensureHostAttached();resumeVideo(getVideo());}};
document.addEventListener('visibilitychange',onVis);
window.addEventListener('pageshow',onPageShow);
this._listeners.push(()=>{document.removeEventListener('visibilitychange',onVis);window.removeEventListener('pageshow',onPageShow);});
},
previewImage(url){
if(!SecurityUtils.isSafeUrl(url)){this.showToast('不安全的图片地址');return;}
if(!this._shadowRoot)return;
this._shadowRoot.getElementById('rd-img-preview')?.remove();
const preview=SecurityUtils.createElement('div',{id:'rd-img-preview'});
const header=SecurityUtils.createElement('div',{id:'rd-img-preview-header'});
const title=SecurityUtils.createElement('span',{id:'rd-img-preview-title'});title.innerHTML=Icons.image+' 图片预览';
const close=SecurityUtils.createElement('button',{className:'rd-icon-btn',id:'rd-close-preview',title:'关闭'});close.innerHTML=Icons.close;
header.appendChild(title);header.appendChild(close);
const content=SecurityUtils.createElement('div',{id:'rd-img-preview-content'});
const img=document.createElement('img');img.id='rd-preview-img';img.src=url;img.alt='预览';
img.addEventListener('error',()=>{
content.innerHTML=`${Icons.image}
图片加载失败
该图片可能已被删除或需要登录访问
`;
});
content.appendChild(img);
preview.appendChild(header);preview.appendChild(content);
const closeHandler=()=>{preview.remove();this._unlockBodyScroll();};
close.addEventListener('click',closeHandler);
preview.addEventListener('click',e=>{if(e.target===preview||e.target.id==='rd-img-preview-content'||e.target.id==='rd-img-preview-header')closeHandler();});
this._shadowRoot.appendChild(preview);
this._lockBodyScroll();
},
async previewTextContent(url){
if(!SecurityUtils.isSafeUrl(url)){this.showToast('不安全的资源地址');return;}
if(!this._shadowRoot)return;
this.showToast('加载中...');
const ctrl=new AbortController();const tid=setTimeout(()=>ctrl.abort(),CONFIG.FETCH_TIMEOUT);
try{
const resp=await fetch(url,{credentials:'omit',signal:ctrl.signal,headers:{'Accept':'text/plain,text/html,application/json'}});
clearTimeout(tid);
const ct=resp.headers.get('content-type')||'';
if(!/text\/|json|xml|javascript/.test(ct)){this.showIframePreview(url);return;}
const text=await resp.text();const display=text;
this._shadowRoot.getElementById('rd-text-preview')?.remove();
const preview=SecurityUtils.createElement('div',{id:'rd-text-preview'});
const header=SecurityUtils.createElement('div',{id:'rd-text-preview-header'});
const title=SecurityUtils.createElement('span',{id:'rd-text-preview-title'});title.innerHTML=Icons.file+' 内容预览';
const close=SecurityUtils.createElement('button',{className:'rd-icon-btn',id:'rd-text-preview-close',title:'关闭'});close.innerHTML=Icons.close;
header.appendChild(title);header.appendChild(close);
preview.appendChild(header);
const scroll=SecurityUtils.createElement('div',{id:'rd-text-preview-scroll'});
const pre=document.createElement('pre');pre.id='rd-text-preview-content';pre.textContent=display;
pre.style.whiteSpace='pre-wrap';
pre.style.wordBreak='break-word';
pre.style.background='#1e1e2e';
pre.style.color='#e0e0e0';
scroll.appendChild(pre);preview.appendChild(scroll);
this._bindScrollOptimization(scroll);
const handler=()=>{preview.remove();this._unlockBodyScroll();};
close.addEventListener('click',handler);
preview.addEventListener('click',e=>{if(e.target===preview||e.target.id==='rd-text-preview-scroll')handler();});
this._shadowRoot.appendChild(preview);
this._lockBodyScroll();
const lang=this._detectLanguageFromUrl(url)||this._detectLanguageFromContentType(ct);
if(lang)this._applyHighlight(pre,lang);
else if(ct.includes('html')||ct.includes('xml'))this._applyHighlight(pre,'html');
else this._applyHighlight(pre,'plaintext');
}catch(e){this.showIframePreview(url);}
},
showIframePreview(url){
if(!this._shadowRoot)return;
this._shadowRoot.getElementById('rd-text-preview')?.remove();
const preview=SecurityUtils.createElement('div',{id:'rd-text-preview',style:{display:'flex',flexDirection:'column'}});
const header=SecurityUtils.createElement('div',{id:'rd-text-preview-header'});
const title=SecurityUtils.createElement('span',{id:'rd-text-preview-title'});title.innerHTML=Icons.file+' 内容预览';
const controls=SecurityUtils.createElement('div',{style:{display:'flex',gap:'8px',alignItems:'center'}});
const open=SecurityUtils.createElement('button',{className:'rd-icon-btn',title:'新窗口打开'});open.innerHTML=Icons.link;
const close=SecurityUtils.createElement('button',{className:'rd-icon-btn',id:'rd-text-preview-close',title:'关闭'});close.innerHTML=Icons.close;
controls.appendChild(open);controls.appendChild(close);
header.appendChild(title);header.appendChild(controls);
const container=SecurityUtils.createElement('div',{className:'rd-iframe-container'});
const iframe=SecurityUtils.createElement('iframe',{src:url,sandbox:'allow-same-origin allow-scripts allow-popups allow-downloads'});
container.appendChild(iframe);
preview.appendChild(header);preview.appendChild(container);
const handler=()=>{preview.remove();this._unlockBodyScroll();};
close.addEventListener('click',handler);
open.addEventListener('click',()=>{window.open(url,'_blank','noopener,noreferrer');});
preview.addEventListener('click',e=>{if(e.target===preview||e.target.className==='rd-iframe-container')handler();});
this._shadowRoot.appendChild(preview);
this._lockBodyScroll();
},
viewSource(){
if(!this._shadowRoot)return;
const src='\n'+document.documentElement.outerHTML;
this._shadowRoot.getElementById('rd-source-preview')?.remove();
const preview=SecurityUtils.createElement('div',{id:'rd-source-preview'});
const header=SecurityUtils.createElement('div',{id:'rd-source-preview-header'});
const title=SecurityUtils.createElement('span',{id:'rd-source-preview-title'});title.innerHTML=Icons.code+' 页面源码';
const controls=SecurityUtils.createElement('div',{style:{display:'flex',gap:'8px',alignItems:'center'}});
const copyBtn=SecurityUtils.createElement('button',{className:'rd-icon-btn',id:'rd-source-preview-copy',title:'复制源码'});copyBtn.innerHTML=Icons.copy;
const close=SecurityUtils.createElement('button',{className:'rd-icon-btn',id:'rd-source-preview-close',title:'关闭'});close.innerHTML=Icons.close;
controls.appendChild(copyBtn);controls.appendChild(close);
header.appendChild(title);header.appendChild(controls);
const scroll=SecurityUtils.createElement('div',{id:'rd-source-preview-scroll'});
const pre=document.createElement('pre');pre.id='rd-source-preview-content';pre.textContent=src;
pre.style.whiteSpace='pre-wrap';
pre.style.wordBreak='break-word';
pre.style.background='#1e1e2e';
pre.style.color='#e0e0e0';
scroll.appendChild(pre);
preview.appendChild(header);preview.appendChild(scroll);
this._bindScrollOptimization(scroll);
const handler=()=>{preview.remove();this._unlockBodyScroll();};
copyBtn.addEventListener('click',()=>this.copyToClipboard(src));
close.addEventListener('click',handler);
preview.addEventListener('click',e=>{if(e.target===preview||e.target.id==='rd-source-preview-scroll')handler();});
this._shadowRoot.appendChild(preview);
this._lockBodyScroll();
this._applyHighlight(pre,'html');
},
VIDEO_TYPES:['mp4','webm','ogg','ogv','mov','avi','mkv','flv','m3u8','mpd','ts','m4v','3gp','wmv','m2ts'],
AUDIO_TYPES:['mp3','wav','flac','aac','m4a','wma','oga','weba','opus','audio','aiff','mid','ac3','amr'],
isVideoType(displayType){
const t=(displayType||'').toLowerCase();
return this.VIDEO_TYPES.includes(t);
},
playMedia(url,displayType){
if(!SecurityUtils.isSafeUrl(url)){this.showToast('不安全的资源地址');return;}
if(!this._shadowRoot)return;
const ev=this._shadowRoot.querySelector('#rd-inline-player video');if(ev&&ev._hls){try{ev._hls.destroy();}catch{}}
this._shadowRoot.getElementById('rd-inline-player')?.remove();
const player=SecurityUtils.createElement('div',{id:'rd-inline-player'});
const header=SecurityUtils.createElement('div',{id:'rd-player-header'});
const title=SecurityUtils.createElement('span',{id:'rd-player-title'});
const dt=(displayType||'').toLowerCase();
const isVideo=this.isVideoType(dt)||(!dt&&/\.(mp4|webm|ogg|ogv|mov|avi|mkv|flv|m3u8|mpd|ts|m4v|3gp|wmv)(\?|#|$)/i.test(url));
const isHLS=dt==='m3u8'||/\.m3u8(\?|#|$)/i.test(url);
const isDash=dt==='mpd'||(!dt&&/\.mpd(\?|#|$)/i.test(url));
title.innerHTML=(isVideo?Icons.video:Icons.audio)+' '+(isVideo?'视频播放':'音频播放');
const close=SecurityUtils.createElement('button',{className:'rd-icon-btn',id:'rd-close-player',title:'关闭'});close.innerHTML=Icons.close;
const headerDlBtn=SecurityUtils.createElement('button',{className:'rd-icon-btn',id:'rd-player-download',title:'下载'});headerDlBtn.innerHTML=Icons.download;
const headerBtns=SecurityUtils.createElement('div',{style:{display:'flex',gap:'8px',alignItems:'center',flexShrink:'0'}});
headerBtns.appendChild(headerDlBtn);headerBtns.appendChild(close);
header.appendChild(title);header.appendChild(headerBtns);
headerDlBtn.addEventListener('click',e=>{e.stopPropagation();this.downloadResource(url);});
const wrapper=SecurityUtils.createElement('div',{id:'rd-media-wrapper'});
player.appendChild(header);player.appendChild(wrapper);
let updateFullscreenUI=()=>{};
let toggleFullscreen=()=>{};
let hlsInstance=null;
if(isVideo){
const NATIVE_VIDEO=['mp4','webm','ogg','ogv','mov','m4v','m3u8'];
const showVideoError=()=>{
const errWrap=document.createElement('div');
errWrap.style.cssText='display:flex;flex-direction:column;align-items:center;gap:12px;padding:40px;color:var(--rd-text-secondary);text-align:center;';
errWrap.innerHTML=`${Icons.video}
无法直接播放此视频格式
该视频格式(${dt||'未知'})可能需要专用播放器,请尝试下载后播放${isHLS?',HLS流媒体加载失败,可能是跨域限制或网络问题':isDash?',此为DASH流媒体格式浏览器可能不支持':''}
`;
wrapper.innerHTML='';
wrapper.appendChild(errWrap);
const dlBtn2=document.createElement('button');
dlBtn2.style.cssText='margin-top:6px;padding:10px 28px;border-radius:14px;background:var(--rd-accent);color:#fff;border:none;cursor:pointer;font-size:14px;font-weight:600;line-height:1;box-shadow:0 4px 14px rgba(10,132,255,0.25);transition:transform 0.2s ease,box-shadow 0.2s ease;';
dlBtn2.textContent='下载视频';
dlBtn2.addEventListener('mouseenter',()=>{dlBtn2.style.transform='scale(1.04)';dlBtn2.style.boxShadow='0 6px 20px rgba(10,132,255,0.35)';});
dlBtn2.addEventListener('mouseleave',()=>{dlBtn2.style.transform='scale(1)';dlBtn2.style.boxShadow='0 4px 14px rgba(10,132,255,0.25)';});
dlBtn2.addEventListener('click',()=>this.downloadResource(url));
errWrap.appendChild(dlBtn2);
};
const videoWrap=document.createElement('div');
videoWrap.id='rd-video-wrapper';
const video=document.createElement('video');
video.id='rd-video';
video.width=1280;
video.height=720;
video.autoplay=true;
video.playsInline=true;
video.preload='auto';
video.setAttribute('playsinline','');
video.setAttribute('webkit-playsinline','');
video.setAttribute('x-webkit-airplay','allow');
if(isHLS){
if(video.canPlayType('application/vnd.apple.mpegurl')){
video.src=url;
}else{
this._loadHlsJS().then(Hls=>{
if(Hls.isSupported()){
hlsInstance=new Hls({
enableWorker:true,
lowLatencyMode:true,
backBufferLength:90,
maxBufferLength:30,
maxMaxBufferLength:60,
startLevel:-1,
capLevelToPlayerSize:true
});
video._hls=hlsInstance;
hlsInstance.loadSource(url);
hlsInstance.attachMedia(video);
hlsInstance.on(Hls.Events.MANIFEST_PARSED,()=>{
video.play().catch(()=>{});
});
hlsInstance.on(Hls.Events.ERROR,(_,data)=>{
if(!data.fatal)return;
if(data.type===Hls.ErrorTypes.NETWORK_ERROR){try{hlsInstance.startLoad();return;}catch{}}
else if(data.type===Hls.ErrorTypes.MEDIA_ERROR){try{hlsInstance.recoverMediaError();return;}catch{}}
clearTimeout(errTimer);
loading.style.display='none';
showVideoError();
});
}else{
clearTimeout(errTimer);
loading.style.display='none';
showVideoError();
}
}).catch(()=>{
clearTimeout(errTimer);
loading.style.display='none';
showVideoError();
});
}
}else{
video.src=url;
}
const loading=document.createElement('div');
loading.className='rd-video-loading';
videoWrap.appendChild(video);
videoWrap.appendChild(loading);
const fmt=s=>{if(!isFinite(s)||s<0)return'0:00';const h=Math.floor(s/3600);const m=Math.floor(s%3600/60);const ss=Math.floor(s%60);if(h>0)return`${h}:${m.toString().padStart(2,'0')}:${ss.toString().padStart(2,'0')}`;return`${m}:${ss.toString().padStart(2,'0')}`;};
const centerBtn=document.createElement('button');
centerBtn.className='rd-video-center-btn';
centerBtn.innerHTML=Icons.play;
videoWrap.appendChild(centerBtn);
const controls=document.createElement('div');
controls.className='rd-video-controls';
const playPauseBtn=document.createElement('button');
playPauseBtn.className='rd-video-btn rd-video-play-btn';
playPauseBtn.innerHTML=Icons.play;
const progressWrap=document.createElement('div');
progressWrap.className='rd-video-progress';
const progressTrack=document.createElement('div');
progressTrack.className='rd-video-progress-track';
const progressFilled=document.createElement('div');
progressFilled.className='rd-video-progress-filled';
const progressThumb=document.createElement('div');
progressThumb.className='rd-video-progress-thumb';
progressTrack.appendChild(progressFilled);
progressTrack.appendChild(progressThumb);
progressWrap.appendChild(progressTrack);
const curTimeLabel=document.createElement('span');
curTimeLabel.className='rd-video-time';
curTimeLabel.textContent='0:00';
const durTimeLabel=document.createElement('span');
durTimeLabel.className='rd-video-time';
durTimeLabel.textContent='0:00';
const speedBtn=document.createElement('button');
speedBtn.className='rd-video-btn rd-video-speed-btn';
speedBtn.innerHTML=Icons.speed;
speedBtn.title='播放速度';
const speedMenu=document.createElement('div');
speedMenu.className='rd-video-speed-menu';
const speeds=[0.5,0.75,1,1.25,1.5,2];
speeds.forEach(sp=>{
const item=document.createElement('div');
item.className='rd-video-speed-item'+(sp===1?' active':'');
item.textContent=sp===1?'正常':sp+'x';
item.dataset.speed=sp;
item.addEventListener('click',e=>{
e.stopPropagation();
video.playbackRate=sp;
speedMenu.classList.remove('rd-open');
speedMenu.querySelectorAll('.rd-video-speed-item').forEach(i=>i.classList.toggle('active',i===item));
speedBtn.classList.toggle('active',sp!==1);
});
speedMenu.appendChild(item);
});
const volumeBtn=document.createElement('button');
volumeBtn.className='rd-video-btn rd-video-volume-btn';
volumeBtn.innerHTML=Icons.volume;
const fsBtn=document.createElement('button');
fsBtn.className='rd-video-btn rd-video-fs-btn';
fsBtn.innerHTML=Icons.expand;
fsBtn.title='全屏';
const progressBar=document.createElement('div');
progressBar.className='rd-video-progress-bar';
progressBar.appendChild(curTimeLabel);
progressBar.appendChild(progressWrap);
progressBar.appendChild(durTimeLabel);
const btnRow=document.createElement('div');
btnRow.className='rd-video-btn-row';
btnRow.appendChild(playPauseBtn);
const btnSpacer=document.createElement('div');
btnSpacer.style.cssText='flex:1';
btnRow.appendChild(btnSpacer);
btnRow.appendChild(speedBtn);
btnRow.appendChild(volumeBtn);
btnRow.appendChild(fsBtn);
controls.appendChild(progressBar);
controls.appendChild(btnRow);
videoWrap.appendChild(controls);
videoWrap.appendChild(speedMenu);
let errTimer=setTimeout(()=>{if(video.readyState===0)showVideoError();},8000);
let controlsTimer=null;
let centerTimer=null;
const showControls=()=>{
videoWrap.classList.add('rd-show-controls');
if(controlsTimer)clearTimeout(controlsTimer);
controlsTimer=setTimeout(()=>{videoWrap.classList.remove('rd-show-controls');},10000);
if(video.paused)showCenter();
};
const hideControls=()=>{
if(controlsTimer)clearTimeout(controlsTimer);
videoWrap.classList.remove('rd-show-controls');
};
const showCenter=()=>{
videoWrap.classList.add('rd-show-center');
if(centerTimer)clearTimeout(centerTimer);
centerTimer=setTimeout(()=>{videoWrap.classList.remove('rd-show-center');},3000);
};
const hideCenter=()=>{
if(centerTimer)clearTimeout(centerTimer);
videoWrap.classList.remove('rd-show-center');
};
const togglePlay=()=>{
if(video.paused){video.play();}else{video.pause();}
};
const updatePlayUI=()=>{
playPauseBtn.innerHTML=video.paused?Icons.play:Icons.pause;
centerBtn.innerHTML=video.paused?Icons.play:Icons.pause;
if(video.paused){showCenter();showControls();}
else{hideCenter();}
};
const updateProgress=()=>{
const p=video.currentTime/video.duration*100||0;
progressFilled.style.width=p+'%';
progressThumb.style.left=p+'%';
curTimeLabel.textContent=fmt(video.currentTime);
durTimeLabel.textContent=fmt(video.duration);
};
const updateVolumeUI=()=>{
volumeBtn.innerHTML=video.muted||video.volume===0?Icons.volumeMute:Icons.volume;
};
toggleFullscreen=()=>{
if(!document.fullscreenElement&&!document.webkitFullscreenElement){
const reqFs=videoWrap.requestFullscreen||videoWrap.webkitRequestFullscreen;
if(reqFs){
const result=reqFs.call(videoWrap);
if(result&&result.catch)result.catch(()=>{});
if(video.videoWidth>video.videoHeight&&screen.orientation&&screen.orientation.lock){
screen.orientation.lock('landscape').catch(()=>{});
}else if(video.videoHeight>video.videoWidth&&screen.orientation&&screen.orientation.lock){
screen.orientation.lock('portrait').catch(()=>{});
}
}
}else{
if(document.exitFullscreen)document.exitFullscreen();
else if(document.webkitExitFullscreen)document.webkitExitFullscreen();
if(screen.orientation&&screen.orientation.unlock){screen.orientation.unlock();}
}
};
updateFullscreenUI=()=>{
const isFs=!!(document.fullscreenElement||document.webkitFullscreenElement);
fsBtn.innerHTML=isFs?Icons.compress:Icons.expand;
fsBtn.title=isFs?'退出全屏':'全屏';
if(isFs&&video.videoWidth&&video.videoHeight){
if(screen.orientation&&screen.orientation.lock){
const target=video.videoWidth>video.videoHeight?'landscape':'portrait';
if(screen.orientation.type&&!screen.orientation.type.startsWith(target)){
screen.orientation.lock(target).catch(()=>{});
}
}
}
};
video.addEventListener('loadstart',()=>{loading.style.display='block';});
video.addEventListener('waiting',()=>{loading.style.display='block';});
video.addEventListener('canplay',()=>{loading.style.display='none';});
video.addEventListener('playing',()=>{loading.style.display='none';updatePlayUI();});
video.addEventListener('loadeddata',()=>{clearTimeout(errTimer);loading.style.display='none';updateProgress();updatePlayUI();});
video.addEventListener('loadedmetadata',updateProgress);
video.addEventListener('durationchange',updateProgress);
video.addEventListener('timeupdate',updateProgress);
video.addEventListener('play',updatePlayUI);
video.addEventListener('pause',updatePlayUI);
video.addEventListener('volumechange',updateVolumeUI);
video.addEventListener('ended',()=>{updatePlayUI();});
video.addEventListener('error',()=>{clearTimeout(errTimer);loading.style.display='none';showVideoError();});
if(!NATIVE_VIDEO.includes(dt)&&dt){
video.addEventListener('canplay',()=>{
if(video.videoWidth===0){
const hint=document.createElement('div');
hint.style.cssText='position:absolute;bottom:60px;left:50%;transform:translateX(-50%);background:rgba(0,0,0,0.7);color:#fff;padding:6px 14px;border-radius:10px;font-size:12px;pointer-events:none;white-space:nowrap;z-index:5;';
hint.textContent='当前格式仅支持音频播放,建议下载后查看完整视频';
videoWrap.appendChild(hint);
}
},{once:true});
}
playPauseBtn.addEventListener('click',e=>{e.stopPropagation();togglePlay();});
centerBtn.addEventListener('click',e=>{e.stopPropagation();togglePlay();});
let clickTimer=null;
video.addEventListener('click',()=>{
if(clickTimer){
clearTimeout(clickTimer);
clickTimer=null;
togglePlay();
}else{
clickTimer=setTimeout(()=>{
clickTimer=null;
if(videoWrap.classList.contains('rd-show-controls')){
hideControls();
}else{
showControls();
}
},250);
}
});
progressWrap.addEventListener('click',e=>{
e.stopPropagation();
const r=progressTrack.getBoundingClientRect();
const p=Math.max(0,Math.min(1,(e.clientX-r.left)/r.width));
video.currentTime=p*video.duration;
});
let isDragging=false;
progressWrap.addEventListener('touchstart',e=>{isDragging=true;video.pause();},{passive:true});
progressWrap.addEventListener('touchmove',e=>{
if(!isDragging)return;
e.stopPropagation();
const r=progressTrack.getBoundingClientRect();
const p=Math.max(0,Math.min(1,(e.touches[0].clientX-r.left)/r.width));
video.currentTime=p*video.duration;
},{passive:true});
progressWrap.addEventListener('touchend',()=>{isDragging=false;},{passive:true});
speedBtn.addEventListener('click',e=>{e.stopPropagation();speedMenu.classList.toggle('rd-open');});
volumeBtn.addEventListener('click',e=>{e.stopPropagation();video.muted=!video.muted;});
fsBtn.addEventListener('click',e=>{e.stopPropagation();toggleFullscreen();});
document.addEventListener('fullscreenchange',updateFullscreenUI);
document.addEventListener('webkitfullscreenchange',updateFullscreenUI);
player._videoKeyHandler=e=>{
if(!player.parentNode){document.removeEventListener('keydown',player._videoKeyHandler);return;}
if(e.target.tagName==='INPUT'||e.target.tagName==='TEXTAREA')return;
switch(e.key){
case' ':e.preventDefault();togglePlay();break;
case'ArrowLeft':e.preventDefault();video.currentTime=Math.max(0,video.currentTime-5);break;
case'ArrowRight':e.preventDefault();video.currentTime=Math.min(video.duration||0,video.currentTime+5);break;
case'ArrowUp':e.preventDefault();video.volume=Math.min(1,video.volume+0.1);break;
case'ArrowDown':e.preventDefault();video.volume=Math.max(0,video.volume-0.1);break;
case'm':case'M':video.muted=!video.muted;break;
case'f':case'F':toggleFullscreen();break;
}
};
document.addEventListener('keydown',player._videoKeyHandler);
wrapper.appendChild(videoWrap);
}else{
const audioUI=this.createCustomAudioPlayer(url);
wrapper.appendChild(audioUI);
}
const handler=()=>{
try{if(hlsInstance){hlsInstance.destroy();hlsInstance=null;}if(player.querySelector('video'))player.querySelector('video').pause();if(player.querySelector('audio'))player.querySelector('audio').pause();}catch{}
if(player._videoKeyHandler)document.removeEventListener('keydown',player._videoKeyHandler);
document.removeEventListener('fullscreenchange',updateFullscreenUI);
document.removeEventListener('webkitfullscreenchange',updateFullscreenUI);
player.remove();this._unlockBodyScroll();
};
close.addEventListener('click',handler);
player.addEventListener('click',e=>{if(e.target===player)handler();});
this._shadowRoot.appendChild(player);
this._lockBodyScroll();
},
createCustomAudioPlayer(url){
const container=document.createElement('div');
container.className='rd-custom-audio';
const audio=document.createElement('audio');
audio.src=url;audio.preload='auto';
const info=document.createElement('div');info.className='rd-audio-info';
const infoIcon=document.createElement('div');infoIcon.className='rd-audio-info-icon';
infoIcon.innerHTML=Icons.music;
const fname=document.createElement('div');fname.className='rd-audio-filename';
fname.textContent=this.extractFilename(url);
info.appendChild(infoIcon);info.appendChild(fname);
const controls=document.createElement('div');controls.className='rd-audio-controls';
const playBtn=document.createElement('button');playBtn.className='rd-play-pause-btn';
playBtn.innerHTML=Icons.play;
const progWrap=document.createElement('div');progWrap.className='rd-progress-container';
const progBg=document.createElement('div');progBg.className='rd-progress-bg';
const progFilled=document.createElement('div');progFilled.className='rd-progress-filled';
const progThumb=document.createElement('div');progThumb.className='rd-progress-thumb';
progBg.appendChild(progFilled);progBg.appendChild(progThumb);progWrap.appendChild(progBg);
const time=document.createElement('div');time.className='rd-time';
const cur=document.createElement('span');cur.textContent='0:00';
const sep=document.createElement('span');sep.textContent='/';
const dur=document.createElement('span');dur.textContent='0:00';
time.appendChild(cur);time.appendChild(sep);time.appendChild(dur);
controls.appendChild(playBtn);controls.appendChild(progWrap);controls.appendChild(time);
container.appendChild(info);container.appendChild(controls);container.appendChild(audio);
const fmt=s=>{if(!isFinite(s))return'0:00';const m=Math.floor(s/60);const ss=Math.floor(s%60);return `${m}:${ss.toString().padStart(2,'0')}`;};
const updateDur=()=>{if(isFinite(audio.duration)&&audio.duration>0){dur.textContent=fmt(audio.duration);updateProg();}};
const updateProg=()=>{
const p=audio.currentTime/audio.duration*100||0;
progFilled.style.width=p+'%';
progThumb.style.left=p+'%';
cur.textContent=fmt(audio.currentTime);
};
audio.addEventListener('loadedmetadata',()=>{
if(audio.duration===Infinity||isNaN(audio.duration)){
audio.currentTime=1e101;
audio.ontimeupdate=()=>{audio.ontimeupdate=null;audio.currentTime=0;updateDur();};
}else{updateDur();}
});
audio.addEventListener('durationchange',updateDur);
audio.addEventListener('timeupdate',updateProg);
playBtn.addEventListener('click',()=>{if(audio.paused){audio.play();playBtn.innerHTML=Icons.pause;}else{audio.pause();playBtn.innerHTML=Icons.play;}});
audio.addEventListener('ended',()=>{playBtn.innerHTML=Icons.play;progFilled.style.width='0%';progThumb.style.left='0%';});
progWrap.addEventListener('click',e=>{
const r=progBg.getBoundingClientRect();
const p=Math.max(0,Math.min(1,(e.clientX-r.left)/r.width));
audio.currentTime=p*audio.duration;
});
return container;
},
addResource(r){
if(!r||typeof r.url!=='string')return false;
const raw=r.url.trim();
if(!raw)return false;
// 结构校验 + 规范化去重:垃圾字符串、危险协议、失败变体一律不入库
const key=SecurityUtils.resourceKey(raw);
if(!key)return false;
// 同一资源(规范化后同键)已存在 → 合并元数据而不是再次入库
const existing=this._urlKeyIndex.get(key);
if(existing){this._mergeResource(existing,r);return false;}
if(processedUrls.has(key))return false;
processedUrls.add(key);
if(processedUrls.size>CONFIG.MAX_URL_CACHE){
const it=processedUrls.values();
for(let i=0;i<500;i++)processedUrls.delete(it.next().value);
}
const url=raw;
const displayType=this.getDisplayType(url,r.contentType||'',r.initiatorType||'');
const filterType=this.getFilterType(displayType);
r.displayType=displayType;
r.filterType=filterType;
r.icon=this.getTypeIcon(displayType);
r.previewUrl=this.getPreviewUrl(r);
r.filename=this.extractFilename(url);
if(!r.timestamp)r.timestamp=Date.now();
r._rdKey=key;
if(this.resources.length>=CONFIG.MAX_RESOURCES){
const evicted=this.resources.pop();
if(evicted&&evicted._rdKey){
this._urlKeyIndex.delete(evicted._rdKey);
processedUrls.delete(evicted._rdKey);
}
}
this.resources.unshift(r);
this._urlKeyIndex.set(key,r);
this._statsDirty=true;
this.scheduleUpdate();
return true;
},
// 重复捕获同一资源时补充缺失元数据(如先经 PerformanceObserver 无 content-type,后经 XHR 拿到真实类型)
_mergeResource(oldR,newR){
if(newR.contentType&&!oldR.contentType){
oldR.contentType=newR.contentType;
const dt=this.getDisplayType(oldR.url,newR.contentType,oldR.initiatorType||'');
oldR.displayType=dt;
oldR.filterType=this.getFilterType(dt);
oldR.icon=this.getTypeIcon(dt);
oldR.previewUrl=this.getPreviewUrl(oldR);
oldR.filename=oldR.filename||this.extractFilename(oldR.url);
this._statsDirty=true;
this.scheduleUpdate();
}
},
scheduleUpdate(){
if(this._rafId)return;
this._rafId=requestAnimationFrame(()=>{
this._rafId=null;
const now=Date.now();
if(now-this._lastUpdate{
try{
const finalUrl=resp.url||url;
const ct=resp.headers.get('content-type')||'';
self.addResource({url:finalUrl,contentType:ct,initiatorType:'fetch',timestamp:Date.now()});
}catch{}
}).catch(()=>{});
}
return promise;
};
if(typeof PerformanceObserver!=='undefined'){
try{
const obs=new PerformanceObserver(list=>{
for(const entry of list.getEntries()){
try{
const url=entry.name;
if(!url||!SecurityUtils.isSafeUrl(url))continue;
// 过滤失败的请求(4xx/5xx 及被中断的请求),避免无效资源入库
if(typeof entry.responseStatus==='number'&&entry.responseStatus>=400)continue;
const it=entry.initiatorType||'resource';
let ct='';
if(entry.transferSize!==undefined&&entry.encodedBodySize>0){
const ext=url.split('?')[0].split('.').pop()?.toLowerCase()||'';
if(['mp4','webm','ogg','ogv','mov','m4v','flv','mkv','avi','3gp','wmv','m2ts','ts'].includes(ext))ct='video';
else if(['mp3','wav','flac','aac','m4a','wma','oga','weba','opus','aiff','mid','ac3','amr'].includes(ext))ct='audio';
else if(['jpg','jpeg','png','gif','webp','svg','ico','bmp','avif','heic'].includes(ext))ct='image';
else if(ext==='css')ct='text/css';
else if(['js','mjs'].includes(ext))ct='application/javascript';
else if(['woff','woff2','ttf','otf','eot'].includes(ext))ct='font';
else if(['m3u8','mpd'].includes(ext))ct='application/vnd.apple.mpegurl';
}
self.addResource({url,contentType:ct,initiatorType:it,timestamp:Date.now()});
}catch{}
}
});
obs.observe({entryTypes:['resource']});
this._perfObserver=obs;
}catch{}
try{
const entries=performance.getEntriesByType('resource');
for(const entry of entries){
try{
const url=entry.name;
if(!url||!SecurityUtils.isSafeUrl(url))continue;
if(typeof entry.responseStatus==='number'&&entry.responseStatus>=400)continue;
const it=entry.initiatorType||'resource';
let ct='';
const ext=url.split('?')[0].split('.').pop()?.toLowerCase()||'';
if(['mp4','webm','ogg','ogv','mov','m4v','flv','mkv','avi','3gp','wmv','m2ts','ts'].includes(ext))ct='video';
else if(['mp3','wav','flac','aac','m4a','wma','oga','weba','opus','aiff','mid','ac3','amr'].includes(ext))ct='audio';
else if(['jpg','jpeg','png','gif','webp','svg','ico','bmp','avif','heic'].includes(ext))ct='image';
else if(ext==='css')ct='text/css';
else if(['js','mjs'].includes(ext))ct='application/javascript';
else if(['woff','woff2','ttf','otf','eot'].includes(ext))ct='font';
else if(['m3u8','mpd'].includes(ext))ct='application/vnd.apple.mpegurl';
self.addResource({url,contentType:ct,initiatorType:it,timestamp:Date.now()});
}catch{}
}
}catch{}
}
},
setupResourceObserver(){
if(this._resourceObserver)return;
const self=this;
let pending=false;
const flush=()=>{
pending=false;
if(self.isDestroyed||!self._host)return;
self._scanQueuedNodes();
if(self.isPanelOpen)self.scheduleUpdate();
};
const schedule=()=>{
if(pending)return;
pending=true;
requestAnimationFrame(flush);
};
this._observerQueue=[];
this._resourceObserver=new MutationObserver(muts=>{
for(const m of muts){
if(m.type==='childList'){
for(const n of m.addedNodes){
if(n.nodeType===1){n._rdAttrChange=false;self._observerQueue.push(n);}
}
}else if(m.type==='attributes'){
m.target._rdAttrChange=true;
self._observerQueue.push(m.target);
}
}
if(self._observerQueue.length)schedule();
});
this._resourceObserver.observe(document.body,{childList:true,subtree:true,attributes:true,attributeFilter:['src','href','srcset','poster','data-src','data-original','data-bg','style','background','data-video','data-video-src','data-media','data-mp4','data-hls','data-url','data-poster']});
},
_scanQueuedNodes(){
const queue=this._observerQueue;
this._observerQueue=[];
const seen=new Set();
const attrChanged=new Set();
for(const node of queue){
if(node._rdAttrChange){
node._rdAttrChange=false;
attrChanged.add(node);
}else{
this._captureFromNode(node,seen);
}
}
if(attrChanged.size){
for(const el of attrChanged){
this._captureElement(el);
}
}
},
_captureFromNode(root,seen){
if(!root||root.nodeType!==1)return;
if(!seen)seen=new Set();
const collect=(el)=>{
if(!el||el.nodeType!==1||seen.has(el))return;
seen.add(el);
this._captureElement(el);
if(el.shadowRoot){
try{
const sEls=el.shadowRoot.querySelectorAll('img,video,audio,source,track,picture,object,embed,iframe,link,script,input[type=image],image,[data-src],[data-original],[data-lazy-src],[data-srcset],[data-bg],[data-poster]');
for(const s of sEls)collect(s);
}catch{}
}
if(el.tagName==='IFRAME'){
this._scanIframeElement(el);
}
};
collect(root);
if(root.querySelectorAll){
try{
const els=root.querySelectorAll('img,video,audio,source,track,picture,object,embed,iframe,link,script,input[type=image],image,[style],[background],[data-src],[data-original],[data-lazy-src],[data-lazy-srcset],[data-srcset],[data-bg],[data-bgset],[data-background],[data-poster],[data-video],[data-video-src],[data-media],[data-mp4],[data-hls],[data-url],[data-source],[data-file],[data-play]');
for(const el of els)collect(el);
}catch{}
}
if(root.shadowRoot){
try{
const sEls=root.shadowRoot.querySelectorAll('img,video,audio,source,track,picture,object,embed,iframe,link,script,input[type=image],image,[data-src],[data-original],[data-lazy-src],[data-srcset],[data-bg],[data-poster]');
for(const s of sEls)collect(s);
}catch{}
}
},
_scanIframeElement(iframe){
if(!iframe||iframe._rdScanned)return;
iframe._rdScanned=true;
const scanIframe=()=>{
try{
const doc=iframe.contentDocument;
if(!doc)return;
const elements=doc.querySelectorAll('img,video,audio,source,track,picture,object,embed,iframe,link,script,input[type=image],image');
for(const el of elements)this._captureElement(el,true);
try{
const sel='[data-src],[data-original],[data-lazy-src],[data-lazy-srcset],[data-srcset],[data-bg],[data-bgset],[data-background],[data-image],[data-thumb],[data-poster],[data-video]';
const lazyEls=doc.querySelectorAll(sel);
for(const el of lazyEls)this._captureElement(el,true);
}catch{}
}catch{}
};
scanIframe();
iframe.addEventListener('load',scanIframe);
},
_parseSrcset(srcset){
if(!srcset)return[];
const out=[];
for(const part of srcset.split(',')){
const u=part.trim().split(/\s+/)[0];
if(u)out.push(u);
}
return out;
},
_captureElement(el,useCache){
if(!el||el.nodeType!==1)return;
if(useCache&&processedElements.has(el))return;
processedElements.add(el);
const tag=(el.tagName||'').toLowerCase();
const push=(url,ct,it)=>{
if(url){const s=String(url).trim();if(s&&SecurityUtils.isSafeUrl(s))this.addResource({url:s,contentType:ct||'',initiatorType:it||'scan',timestamp:Date.now()});}
};
const lazyAttrs=['data-src','data-original','data-lazy-src','data-lazy','data-srcset','data-lazy-srcset','data-bg','data-bgset','data-image','data-thumb','data-poster'];
if(tag==='img'){
push(el.src,'image','scan');
const ss=el.getAttribute('srcset')||el.srcset;if(ss)this._parseSrcset(ss).forEach(u=>push(u,'image','srcset'));
push(el.currentSrc,'image','current');
lazyAttrs.forEach(a=>{const v=el.getAttribute(a);if(v){if(a.includes('srcset')||a.includes('bgset'))this._parseSrcset(v).forEach(u=>push(u,'image','lazy'));else push(v,'image','lazy');}});
}
else if(tag==='source'){
push(el.src,'','scan');
const ss=el.getAttribute('srcset')||el.srcset;if(ss)this._parseSrcset(ss).forEach(u=>push(u,'','srcset'));
const vt=el.getAttribute('type')||'';
if(vt)push(el.src,vt,'source-type');
}
else if(tag==='video'){
push(el.src,el.type||'video','scan');
push(el.poster,'image','poster');
push(el.getAttribute('data-poster'),'image','lazy-poster');
if(el.src&&el.src.startsWith('blob:'))push(el.src,'video/blob','blob-media');
lazyAttrs.forEach(a=>{const v=el.getAttribute(a);if(v)push(v,'video','lazy');});
const videoLazyAttrs=['data-video','data-video-src','data-media','data-mp4','data-hls','data-url','data-src-video','data-source','data-file','data-play'];
videoLazyAttrs.forEach(a=>{const v=el.getAttribute(a);if(v&&SecurityUtils.isSafeUrl(v))push(v,'video','lazy-'+a);});
try{
const sources=el.querySelectorAll('source');
for(const s of sources){
push(s.src,s.getAttribute('type')||'video','video-source');
const ss=s.getAttribute('srcset')||s.srcset;
if(ss)this._parseSrcset(ss).forEach(u=>push(u,'video','srcset'));
}
}catch{}
if(!el.src&&!el.querySelector('source')){
const dataSrc=el.getAttribute('data-src')||el.getAttribute('data-video')||el.getAttribute('data-media');
if(dataSrc&&SecurityUtils.isSafeUrl(dataSrc))push(dataSrc,'video','fallback-src');
}
}
else if(tag==='audio'){
push(el.src,el.type||'audio','scan');
if(el.src&&el.src.startsWith('blob:'))push(el.src,'audio/blob','blob-media');
lazyAttrs.forEach(a=>{const v=el.getAttribute(a);if(v)push(v,'audio','lazy');});
}
else if(tag==='track'){
push(el.src,'text/vtt','track');
}
else if(tag==='iframe'){
push(el.src,'','iframe');
}
else if(tag==='link'){
const rel=(el.rel||'').toLowerCase();
if(rel==='stylesheet'||rel==='preload'||rel==='prefetch'||rel==='icon'||rel==='shortcut icon'||rel==='apple-touch-icon'||rel==='manifest')push(el.href,'','link');
}
else if(tag==='script'){
push(el.src,'','scan');
}
else if(tag==='object'||tag==='embed'){
push(el.data,'','scan');
push(el.src,'','scan');
}
else if(tag==='input'){
if((el.type||'').toLowerCase()==='image')push(el.src,'image','input-image');
}
else if(tag==='image'){
push(el.getAttribute('href')||el.getAttribute('xlink:href'),'image','svg-image');
}
else if(tag==='picture'){
const sources=el.querySelectorAll('source');
for(const s of sources){push(s.src,'','scan');const ss=s.getAttribute('srcset')||s.srcset;if(ss)this._parseSrcset(ss).forEach(u=>push(u,'image','srcset'));}
}
const inlineStyle=el.getAttribute('style');
if(inlineStyle){
const urls=inlineStyle.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/g);
for(const m of urls)push(m[1],'','bg');
}
const bgAttr=el.getAttribute&&el.getAttribute('background');
if(bgAttr)push(bgAttr,'image','bg');
for(const a of['data-bg','data-background','data-bgset','data-bg-src']){
const v=el.getAttribute&&el.getAttribute(a);
if(v){if(a.includes('bgset'))this._parseSrcset(v).forEach(u=>push(u,'image','lazy-bg'));else push(v,'image','lazy-bg');}
}
},
setupSPASupport(){
const self=this;
const origPush=history.pushState;
history.pushState=function(...args){
origPush.apply(this,args);
setTimeout(()=>self.scanExistingResources(),500);
};
const origReplace=history.replaceState;
history.replaceState=function(...args){
origReplace.apply(this,args);
setTimeout(()=>self.scanExistingResources(),500);
};
window.addEventListener('popstate',()=>setTimeout(()=>self.scanExistingResources(),500));
},
scanExistingResources(){
const self=this;
const scan=()=>{
const elements=document.querySelectorAll('img,video,audio,source,track,picture,object,embed,iframe,link,script,input[type=image],image');
const seen=new Set();
for(const el of elements){
if(seen.has(el))continue;
seen.add(el);
self._captureElement(el,true);
}
self._scanInlineAndBgImages(seen);
self._scanLazyAttributes(seen);
self._scanMetaMedia();
self._scanStyleSheets();
self._scanIframes();
self._scanShadowDOM();
};
scan();
},
_scanIframes(){
try{
const iframes=document.querySelectorAll('iframe');
for(const iframe of iframes){
try{
const doc=iframe.contentDocument;
if(!doc)continue;
const elements=doc.querySelectorAll('img,video,audio,source,track,picture,object,embed,iframe,link,script,input[type=image],image');
for(const el of elements){
this._captureElement(el,true);
}
try{
const sel='[data-src],[data-original],[data-lazy-src],[data-lazy-srcset],[data-srcset],[data-bg],[data-bgset],[data-background],[data-image],[data-thumb],[data-poster],[data-video],[data-video-src],[data-media],[data-mp4],[data-hls],[data-url],[data-source],[data-file],[data-play]';
const lazyEls=doc.querySelectorAll(sel);
for(const el of lazyEls)this._captureElement(el,true);
}catch{}
const metas=doc.querySelectorAll('meta[property],meta[name]');
for(const m of metas){
const prop=(m.getAttribute('property')||m.getAttribute('name')||'').toLowerCase();
const content=m.getAttribute('content');
if(!content||!SecurityUtils.isSafeUrl(content))continue;
if(prop.startsWith('og:image')||prop.startsWith('og:video')||prop.startsWith('og:audio')||prop==='twitter:image'||prop==='twitter:image:src'||prop==='twitter:player:stream'){
const ct=prop.includes('video')?'video':(prop.includes('audio')?'audio':'image');
this.addResource({url:content,contentType:ct,initiatorType:'iframe-meta',timestamp:Date.now()});
}
}
}catch{}
}
}catch{}
},
_scanShadowDOM(){
try{
const walk=(root)=>{
if(!root||root.nodeType!==1)return;
if(root.shadowRoot){
try{
const els=root.shadowRoot.querySelectorAll('img,video,audio,source,track,picture,object,embed,iframe,link,script,input[type=image],image,[data-src],[data-original],[data-lazy-src],[data-srcset],[data-bg],[data-poster]');
for(const el of els){
this._captureElement(el,true);
if(el.shadowRoot)walk(el);
}
}catch{}
}
if(root.querySelectorAll){
const all=root.querySelectorAll('*');
for(const el of all){
if(el.shadowRoot)walk(el);
}
}
};
walk(document.body);
}catch{}
},
_scanLazyAttributes(seen){
try{
const sel='[data-src],[data-original],[data-lazy-src],[data-lazy-srcset],[data-srcset],[data-bg],[data-bgset],[data-background],[data-image],[data-thumb],[data-poster],[data-video],[data-video-src],[data-media],[data-mp4],[data-hls],[data-url],[data-source],[data-file],[data-play]';
const all=document.querySelectorAll(sel);
for(const el of all){
if(seen.has(el))continue;
seen.add(el);
this._captureElement(el,true);
}
}catch{}
},
_scanMetaMedia(){
try{
const metas=document.querySelectorAll('meta[property],meta[name]');
for(const m of metas){
const prop=(m.getAttribute('property')||m.getAttribute('name')||'').toLowerCase();
const content=m.getAttribute('content');
if(!content||!SecurityUtils.isSafeUrl(content))continue;
if(prop.startsWith('og:image')||prop.startsWith('og:video')||prop.startsWith('og:audio')||prop==='twitter:image'||prop==='twitter:image:src'||prop==='twitter:player:stream'){
const ct=prop.includes('video')?'video':(prop.includes('audio')?'audio':'image');
this.addResource({url:content,contentType:ct,initiatorType:'meta',timestamp:Date.now()});
}
}
}catch{}
},
_scanInlineAndBgImages(seen){
try{
const all=document.querySelectorAll('[style],[background]');
for(const el of all){
if(seen.has(el))continue;
seen.add(el);
if(processedBg.has(el))continue;
processedBg.add(el);
const inlineStyle=el.getAttribute('style');
if(inlineStyle){
const urls=inlineStyle.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/g);
for(const m of urls){if(SecurityUtils.isSafeUrl(m[1]))this.addResource({url:m[1],contentType:'image',initiatorType:'bg',timestamp:Date.now()});}
}
const bgAttr=el.getAttribute('background');
if(bgAttr&&SecurityUtils.isSafeUrl(bgAttr))this.addResource({url:bgAttr,contentType:'image',initiatorType:'bg',timestamp:Date.now()});
const bg=getComputedStyle(el).backgroundImage;
if(bg&&bg!=='none'){
const urls=bg.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/g);
for(const m of urls){if(m[1]&&SecurityUtils.isSafeUrl(m[1]))this.addResource({url:m[1],contentType:'image',initiatorType:'bg',timestamp:Date.now()});}
}
}
}catch{}
},
_scanStyleSheets(){
try{
for(const sheet of document.styleSheets){
if(processedSheets.has(sheet))continue;
let rules;
try{rules=sheet.cssRules||sheet.rules;}catch{processedSheets.add(sheet);continue;}
if(!rules||rules.length===0)continue;
processedSheets.add(sheet);
for(const rule of rules){
if(rule.type===3||rule.type===CSSRule.IMPORT_RULE){
if(rule.href&&SecurityUtils.isSafeUrl(rule.href))this.addResource({url:rule.href,contentType:'text/css',initiatorType:'css-import',timestamp:Date.now()});
continue;
}
if(rule.type===5||rule.type===CSSRule.FONT_FACE_RULE){
const style=rule.style;
if(style){
const src=style.getPropertyValue('src');
if(src){
const urls=src.matchAll(/url\(\s*['"]?([^'")]+)['"]?\s*\)/g);
for(const m of urls){if(m[1]&&SecurityUtils.isSafeUrl(m[1]))this.addResource({url:m[1],contentType:'font',initiatorType:'css-font',timestamp:Date.now()});}
}
}
continue;
}
const style=rule.style;
if(!style)continue;
for(let i=0;i{
if(this.currentFilter&&this.currentFilter!=='all'){
return this.getFilterType(r.displayType)===this.currentFilter;
}
return true;
});
this.sortResources(filtered);
this.filteredResources=filtered;
this.updateCounts();
if(filtered.length===0){
if(this._lastRenderedFilter!=='__empty__'){
this._lastRenderedFilter='__empty__';
this._renderSignature='';
this._elements.listContent.innerHTML=`
${Icons.harmony}
暂无捕获的资源
浏览网页时将自动捕获图片、视频、音频等资源
`;
}
return;
}
const batch=filtered.slice(0,CONFIG.BATCH_SIZE);
const sig=batch.map(r=>r.url+(r.broken?'!b':'')).join('\x01')+this.currentFilter+batch.length+this.sortMode+this.viewMode;
if(sig===this._renderSignature&&this.currentFilter===this._lastRenderedFilter){
return;
}
const filterChanged=this.currentFilter!==this._lastRenderedFilter;
this._renderSignature=sig;
this._lastRenderedFilter=this.currentFilter;
const list=this._elements.list;
const savedScroll=list?list.scrollTop:0;
const frag=document.createDocumentFragment();
for(const r of batch){
frag.appendChild(this.createEntry(r));
}
this._elements.listContent.innerHTML='';
this._elements.listContent.appendChild(frag);
this.applyViewMode();
if(list&&!filterChanged)list.scrollTop=savedScroll;
},
createEntry(r){
const entry=document.createElement('div');
entry.className='rd-entry';
entry.dataset.url=r.url;
entry.dataset.displayType=r.displayType;
const thumbWrap=document.createElement('div');
thumbWrap.className='rd-entry-thumb-wrapper';
const typeColor=TYPE_COLORS[r.displayType]||'#0A84FF';
thumbWrap.style.background=`linear-gradient(135deg,${typeColor}22,${typeColor}0d)`;
if(r.previewUrl){
const img=document.createElement('img');
img.className='rd-entry-thumb';
img.src=r.previewUrl;
img.loading='lazy';
img.onerror=()=>{
thumbWrap.innerHTML=`${r.icon}
`;
// 缩略图加载失败 → 标记为失效资源,列表中置灰并显示“失效”角标
if(!r.broken){r.broken=true;entry.classList.add('rd-entry-broken');this._attachInvalidBadge(entry);}
};
// 加载成功 → 自动摘除失效标记(临时网络故障自愈)
img.onload=()=>{
if(r.broken){r.broken=false;entry.classList.remove('rd-entry-broken');entry.querySelector('.rd-invalid-badge')?.remove();}
};
thumbWrap.appendChild(img);
thumbWrap.dataset.url=r.url;
}else{
thumbWrap.innerHTML=`${r.icon}
`;
}
if(r.broken)entry.classList.add('rd-entry-broken');
entry.appendChild(thumbWrap);
const content=document.createElement('div');
content.className='rd-entry-content';
const header=document.createElement('div');
header.className='rd-entry-header';
const typeBadge=document.createElement('span');
typeBadge.className=`rd-entry-type ${r.displayType}`;
typeBadge.textContent=r.displayType;
header.appendChild(typeBadge);
if(r.broken)this._attachInvalidBadge(entry,header);
const filename=document.createElement('div');
filename.className='rd-entry-filename';
filename.textContent=r.filename;
filename.title=r.filename;
header.appendChild(filename);
const actions=document.createElement('div');
actions.className='rd-entry-actions';
const copyBtn=document.createElement('button');
copyBtn.className='rd-action-btn rd-copy-btn';
copyBtn.title='复制链接';
copyBtn.innerHTML=Icons.copy;
copyBtn.dataset.url=r.url;
actions.appendChild(copyBtn);
const isMedia=r.filterType==='media';
if(isMedia){
const playBtn=document.createElement('button');
playBtn.className='rd-action-btn rd-play-btn';
playBtn.title='播放';
playBtn.innerHTML=Icons.play;
playBtn.dataset.url=r.url;
playBtn.dataset.displayType=r.displayType;
actions.appendChild(playBtn);
}
const dlBtn=document.createElement('button');
dlBtn.className='rd-action-btn rd-download-btn';
dlBtn.title='下载';
dlBtn.innerHTML=Icons.download;
dlBtn.dataset.url=r.url;
actions.appendChild(dlBtn);
header.appendChild(actions);
const urlEl=document.createElement('div');
urlEl.className='rd-entry-url';
urlEl.textContent=r.url;
content.appendChild(header);
content.appendChild(urlEl);
entry.appendChild(content);
return entry;
},
// 为失效资源条目追加“失效”角标
_attachInvalidBadge(entry,header){
const h=header||entry.querySelector('.rd-entry-header');
if(!h||h.querySelector('.rd-invalid-badge'))return;
const badge=document.createElement('span');
badge.className='rd-entry-type rd-invalid-badge';
badge.textContent='失效';
h.insertBefore(badge,h.firstChild.nextSibling||null);
},
updateCounts(){
if(!this._shadowRoot)return;
const counts={all:0,image:0,media:0,other:0};
for(const r of this.resources){
const ft=this.getFilterType(r.displayType);
counts.all++;
counts[ft]=(counts[ft]||0)+1;
}
for(const f in counts){
const el=this._shadowRoot.querySelector(`[data-count="${f}"]`);
if(el)el.textContent=counts[f];
}
const urlEl=this._shadowRoot.getElementById('rd-page-url');
if(urlEl)urlEl.textContent=SecurityUtils.sanitizeAttr(location.href);
},
};
RD.init();
window.ResourceDiary=RD;
})();