媒体管理器在DokuWiki中以弹窗形式嵌入时,上传文件后预览区经常停留在上一次选择的状态。这个问题在默认模板里可能不明显,因为上传完成后媒体列表会刷新并且选中新文件,进而触发预览更新。但一旦把媒体管理器放入jQuery UI Dialog,并且对话框初始化后复用相同的DOM结构,上传操作不会重新创建<img>节点,也不会改变它的src属性。此时再叠加浏览器对同地址图片的强缓存,用户看到旧图也就不奇怪了。修复的关键在于把上传成功事件和预览更新逻辑重新连接起来,并破坏图片URL的缓存命中条件。

问题现象与缓存原因定位
DokuWiki的媒体管理器默认通过lib/scripts/media.js和upload.js协作完成上传。上传成功后,media.js会重建媒体树,也就是savemediatree函数负责把返回的HTML替换到列表区域。如果你的预览区域在Dialog中,通常是另外一段HTML,例如<div id="media__preview"><img src="..."></div>。这个预览区并不会因为媒体树重建而自动更新,除非你监听了选择变化事件或者上传完成事件。
另一个隐蔽但重要的原因是浏览器缓存。即使你调用preview.attr('src', href)把图片地址写成最新文件的URL,如果URL与上传前完全相同,比如覆盖上传同名文件,浏览器会直接复用缓存,根本不会向服务器发送请求。因此修复时不仅要改地址,还要给地址附加一个变化参数。常见的做法是追加时间戳或随机数,例如?t=1717000000000。
还有一点需要确认:媒体管理器对话框是否真的在每次上传后保留旧的DOM。可以用浏览器开发者工具在上传前后检查<img>节点的src属性变化。如果src没变,就要从事件监听入手;如果src变了但图没变,通常是缓存问题。两种现象对应不同修复侧重,但完整方案通常会同时覆盖两种情况。
用上传完成事件触发预览更新
最直接的修复是在全局ajaxComplete事件中识别媒体上传请求,并在请求成功后延迟更新Dialog里的预览图。ajaxComplete是jQuery的全局AJAX事件,能够捕获DokuWiki发出的上传请求。通过检查请求URL中是否包含call=upload,可以避免与其他AJAX请求混淆。延迟执行是为了等待DokuWiki把服务器返回的媒体列表插入到页面,否则可能拿不到最新选中的文件地址。
下面这段代码可以放在模板的script.js或插件的脚本文件中,在页面加载后执行。它会在每次上传请求完成后,查找Dialog中的选中链接和预览图片,然后给预览图重新设置带时间戳的地址:
jQuery(document).on('ajaxComplete', function (event, xhr, settings) {
if (!settings.url) {
return;
}
if (settings.url.indexOf('call=upload') === -1) {
return;
}
if (xhr.status !== 200) {
return;
}
setTimeout(function () {
var dialog = jQuery('#media__dialog');
var selectedLink = dialog.find('.select.mediafile a').first();
var previewImg = dialog.find('#media__preview img, img.media_preview').first();
if (selectedLink.length === 0 || previewImg.length === 0) {
return;
}
var rawUrl = selectedLink.attr('href') || '';
var freshUrl = rawUrl + '?t=' + Date.now();
previewImg.attr('src', freshUrl);
}, 300);
});
这段代码没有直接依赖DokuWiki的内部事件名称,而是基于稳定的AJAX行为进行拦截,兼容性比较好。不过它默认URL没有查询参数,如果你的媒体文件地址本身已经带有?id=...之类的参数,再追加?t=会导致参数解析错误。此时可以判断rawUrl是否包含问号,再决定使用问号还是表单分隔符。这个逻辑可以写成一个小工具函数,下一节会给出更完整的版本。
结合Dialog的打开事件刷新预览
只监听上传完成事件,能够解决上传后立刻显示旧图的问题,但如果媒体管理器Dialog在关闭后再次打开,而内部DOM没有重建,预览图可能仍然停留在上一次的地址。如果此时用户切换到另一个文件,预视图可能会短暂显示旧图,直到新的选择事件触发更新。为了覆盖这个场景,应该在Dialog的open或dialogopen事件中也执行同样的刷新逻辑。
jQuery UI Dialog会触发dialogopen事件。对于已经初始化的Dialog,可以使用jQuery('#media__dialog').on('dialogopen', refreshPreview)来绑定。如果Dialog是动态创建的,也可以把刷新函数作为初始化选项中的open回调传入。下面的示例把刷新逻辑提取成refreshPreview函数,并同时绑定到上传完成事件和Dialog打开事件:
function refreshPreview() {
var dialog = jQuery('#media__dialog');
if (dialog.length === 0) {
return;
}
var selectedLink = dialog.find('.select.mediafile a').first();
var previewImg = dialog.find('#media__preview img, img.media_preview').first();
if (selectedLink.length === 0 || previewImg.length === 0) {
return;
}
var rawUrl = selectedLink.attr('href') || '';
var separator = rawUrl.indexOf('?') === -1 ? '?' : '&';
previewImg.attr('src', rawUrl + separator + 't=' + Date.now());
}
jQuery(document).on('mediauploaded.dwrefresh', refreshPreview);
jQuery(document).on('dialogopen', refreshPreview);
这里使用了自定义事件mediauploaded.dwrefresh,这是插件或模板可以主动触发的事件,用来在DokuWiki上传完成后通知预览模块刷新。如果你不想依赖自定义事件,可以继续使用上一节的ajaxComplete拦截方式,将refreshPreview放到成功回调中。另外,事件命名空间.dwrefresh可以帮助在插件卸载或重新加载时只解绑自己的处理函数,避免影响其他插件。
需要注意的是,重复绑定是这类修复容易引入的问题。如果页面中多次初始化脚本,或者Dialog被多次创建,同一个refreshPreview可能绑定多次,导致上传完成后执行多次DOM操作。虽然结果通常没有严重副作用,但时间戳多次变化可能导致预览图闪烁。合理的做法是使用命名空间绑定时先解绑:jQuery(document).off('.dwrefresh'),再绑定新的处理函数,或者在插件加载时只执行一次。
完整修复示例与关键注意事项
把上面两个场景合并,可以整理出一个适合DokuWiki插件或模板的脚本片段。这个片段先绑定AJAX上传完成事件,再绑定Dialog打开事件,所有刷新都走同一个函数,并且通过时间戳参数绕过浏览器缓存。为了避免污染原始媒体地址,代码会检查URL中是否已经包含查询参数。
jQuery(function () {
var dialogSelector = '#media__dialog';
var processed = false;
function refreshPreview() {
var dialog = jQuery(dialogSelector);
if (dialog.length === 0) {
return;
}
var link = dialog.find('.select.mediafile a').first();
var img = dialog.find('#media__preview img, img.media_preview').first();
if (link.length === 0 || img.length === 0) {
return;
}
var href = link.attr('href') || '';
var sep = href.indexOf('?') === -1 ? '?' : '&';
img.attr('src', href + sep + '_=' + Date.now());
}
function bindOnce() {
if (processed) {
return;
}
processed = true;
jQuery(document).on('ajaxComplete', function (event, xhr, settings) {
if (!settings.url) {
return;
}
if (settings.url.indexOf('call=upload') === -1) {
return;
}
if (xhr.status !== 200) {
return;
}
setTimeout(refreshPreview, 300);
});
jQuery(document).on('dialogopen', refreshPreview);
}
bindOnce();
});
这段脚本没有直接修改DokuWiki核心文件,所有逻辑都通过事件叠加实现,适合部署在自定义模板或插件中。使用时可以把dialogSelector改成实际的媒体管理器对话框ID,把.select.mediafile a选择器调整为你的模板中选中链接的真实结构。选择器不匹配会让刷新函数静默返回,不会报错,但也意味着修复不会生效,所以最好先在浏览器控制台用document.querySelectorAll验证这些元素是否存在。
还有一点值得强调:时间戳参数虽然有效,但会改变图片URL,可能影响某些依赖URL唯一性的缓存插件或反向代理。如果你的DokuWiki部署在CDN之后,并且媒体文件使用了较长的缓存头,这种绕过方式仍可能被CDN缓存层拦截。更稳妥的做法是在上传成功后执行一次img.removeAttr('src'); img.attr('src', href)强制重新发起请求,或者在服务器端为上传后返回的媒体信息中直接加入版本号。对于绝大多数内网或小规模站点,时间戳方案已经足够。
DokuWikijQuery UI Dialog媒体管理器修改时间:2026-10-05 08:48:43