feat(mdwidget): 已渲染媒体(img/video/audio)追加下载按钮——会话agent生成的媒体可下载;同源走?download=1强制附件(Content-Disposition),内联data用download属性,外链新窗口;注入放在链接处理循环之后防被onclick拦截成markdown加载(2026-09-08用户定夺)

This commit is contained in:
yumoqing 2026-09-08 11:58:38 +08:00
parent f31ef89eca
commit f2884eefc2

View File

@ -65,6 +65,71 @@ bricks.MdWidget = class extends bricks.JsWidget {
links[i].href = '#';
links[i].onclick=this._build.bind(this, url);
}
/* <a download>
会被循环改成 onclick=_build mp4 markdown 加载下载不了
marked 原生透传渲染 img/video/audio这里只追加下载入口不动渲染 */
this._add_media_download_buttons();
}
/* 给已渲染的 <img>/<video>/<audio> 2026-09-08
会话 agent 生成的媒体必须提供下载按钮下载走 ?download=1
ahserver path_download 返回 Content-Disposition:attachment 触发浏览器下载 */
_add_media_download_buttons(){
var host = location.origin;
var nodes = this.dom_element.querySelectorAll('img, video, audio');
for (var i=0; i<nodes.length; i++){
var el = nodes[i];
if (el.getAttribute('data-dl-bound')) continue; // 幂等:已加过
el.setAttribute('data-dl-bound', '1');
/* 取媒体 URLvideo/audio 可能用子 <source src> */
var src = el.currentSrc || el.src || '';
if (!src){
var so = el.querySelector('source');
if (so) src = so.src || so.getAttribute('src') || '';
}
if (!src) continue;
var href, is_data = src.indexOf('data:') === 0;
if (is_data){
href = src; /* 内联 base64直接用 download 属性 */
} else {
/* /idfile ?download=1
外链媒体保持原 URL跨域 download 属性无效靠浏览器行为 */
var sep = src.indexOf('?') >= 0 ? '&' : '?';
href = (src.indexOf(host) === 0 || src.charAt(0) === '/')
? src + sep + 'download=1' : src;
}
/* 文件名URL 末段(去 querydata URL 用媒体类型兜底 */
var fname = '';
if (!is_data){
fname = decodeURIComponent(src.split('?')[0].split('/').pop() || '');
}
if (!fname){
fname = 'download_' + Date.now() + '.' + (el.tagName === 'IMG' ? 'png'
: el.tagName === 'VIDEO' ? 'mp4' : 'mp3');
}
var a = document.createElement('a');
a.href = href;
a.setAttribute('download', fname);
a.textContent = '⬇ 下载';
a.className = 'md-media-download';
a.style.cssText = 'display:inline-block;margin:4px 0;padding:2px 10px;'
+ 'font-size:12px;color:#fff;background:#2563eb;border-radius:6px;'
+ 'cursor:pointer;text-decoration:none;';
/* <a>
显式置空 onclick 防冒泡到父级链接拦截 */
a.onclick = function(e){ e.stopPropagation(); };
if (is_data || href.indexOf(host) === 0 || href.charAt(0) === '/'){
/* 同源/内联a[download] 直接触发下载,不跳转 */
} else {
a.target = '_blank';
a.rel = 'noopener noreferrer';
}
/* 插在媒体元素之后(同一父容器内) */
if (el.nextSibling){
el.parentNode.insertBefore(a, el.nextSibling);
} else {
el.parentNode.appendChild(a);
}
}
}
getname(){
return this.name || 'mdtext';