From d332dbed6ac0495c4e62e469558618dc0eb29c57 Mon Sep 17 00:00:00 2001 From: wylovely <2016688030@qq.com> Date: Thu, 24 Sep 2026 15:27:10 +0800 Subject: [PATCH 1/4] fix(#787): derive Viewer title from data-original-url so image preview shows the filename (was empty for
) --- server/src/main/resources/web/picture.ftl | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/server/src/main/resources/web/picture.ftl b/server/src/main/resources/web/picture.ftl index 648430a3e..e6b3905d0 100644 --- a/server/src/main/resources/web/picture.ftl +++ b/server/src/main/resources/web/picture.ftl @@ -58,6 +58,17 @@ }); } + // 从原始图片 URL 提取文件名(优先用 data-original-url,避免受反代 / CORS 代理 URL 影响) + function getImageNameFromOriginalUrl(imageElement) { + var originalUrl = imageElement.getAttribute('data-original-url') || ''; + // 去除查询串与锚点 + var noQuery = originalUrl.split('?')[0].split('#')[0]; + // 取路径最后一段作为文件名(兼容 / 与 \) + var lastSep = Math.max(noQuery.lastIndexOf('/'), noQuery.lastIndexOf('\\')); + var name = lastSep >= 0 ? noQuery.substring(lastSep + 1) : noQuery; + return name || 'image'; + } + // 初始化图片查看器 function initImageViewer() { var viewer = new Viewer(document.getElementById('image'), { @@ -66,6 +77,11 @@ button: false, backdrop: false, loop: true, + // #787:
没有 DOM 的 src 属性,Viewer.js 用 image.src 取标题会得到 undefined; + // 改为从 data-original-url 派生标题文件名(该属性在元素克隆时会被保留) + title: function (image, imageData) { + return getImageNameFromOriginalUrl(image); + } }); viewer.view(0); // 0 是图片的索引,如果你想点击第一张图片,索引为 0 } From 80b13dacba7467a4b5110adf370d5f99867f5731 Mon Sep 17 00:00:00 2001 From: wylovely <2016688030@qq.com> Date: Thu, 24 Sep 2026 15:27:12 +0800 Subject: [PATCH 2/4] fix(#787): derive Viewer title from data-original-url so image preview shows the filename (was empty for
) --- .../cn/keking/PictureFilePreviewTests.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 server/src/test/java/cn/keking/PictureFilePreviewTests.java diff --git a/server/src/test/java/cn/keking/PictureFilePreviewTests.java b/server/src/test/java/cn/keking/PictureFilePreviewTests.java new file mode 100644 index 000000000..9885b634a --- /dev/null +++ b/server/src/test/java/cn/keking/PictureFilePreviewTests.java @@ -0,0 +1,46 @@ +package cn.keking; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * kkFileView #787 回归测试:直接图片预览(picture.ftl)必须在 Viewer.js 标题里显示文件名。 + * + * 根因:v4.3.0 之后模板把图片元素从 <img> 改成 <div src>,而 HTMLDivElement 没有 DOM 的 + * src 属性,Viewer.js 用 image.src 取标题得到 undefined,导致标题为空。修复方式是在 Viewer 初始化时 + * 配置 title 函数,从 data-original-url 派生文件名(该属性在元素克隆时会被保留)。 + */ +public class PictureFilePreviewTests { + + @Test + void shouldDeriveViewerTitleFromOriginalUrl() throws IOException { + String pictureTemplate = readResource("/web/picture.ftl"); + + // 模板仍保留 data-original-url 供标题派生使用 + assertTrue(pictureTemplate.contains("data-original-url"), + () -> "picture.ftl must keep data-original-url for title derivation (see #787)"); + + // Viewer 初始化必须配置 title 函数,否则
的 image.src 为 undefined、标题为空 + assertTrue(pictureTemplate.contains("title: function"), + () -> "picture.ftl must configure a Viewer title function to show the filename (see #787)"); + + // 标题派生逻辑必须读取 data-original-url(而非反代 / CORS 代理 URL) + assertTrue(pictureTemplate.contains("getImageNameFromOriginalUrl"), + () -> "picture.ftl must derive the title from data-original-url (see #787)"); + assertTrue(pictureTemplate.contains("getAttribute('data-original-url')"), + () -> "title derivation must read data-original-url attribute (see #787)"); + } + + private String readResource(String resourcePath) throws IOException { + try (InputStream inputStream = getClass().getResourceAsStream(resourcePath)) { + assertNotNull(inputStream); + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); + } + } +} From 6b948fa6756074b2809a89a0c9be923f8b495183 Mon Sep 17 00:00:00 2001 From: wylovelyi Date: Sun, 27 Sep 2026 10:00:00 +0000 Subject: [PATCH 3/4] fix(#787): make the image-preview title actually derive from the original URL Verified against the bundled viewer.min.js: the `title(image, imageData)` callback receives the element Viewer.js creates internally (view(): `var image = document.createElement('img')`), NOT the original
. Viewer only forwards the attributes listed in `inheritedAttributes` (default: crossOrigin, decoding, isMap, loading, referrerPolicy, sizes, srcset, useMap) when building that , and `data-original-url` is not in that list. So the previous title function always read null and the title stayed 'image' -- the fix was a no-op at runtime. Fix: add `data-original-url` to Viewer's `inheritedAttributes` so the original URL is forwarded from the thumbnail to the title , letting `getImageNameFromOriginalUrl` return the real filename (and not the CORS-proxy URL). Also updates PictureFilePreviewTests to assert `inheritedAttributes` is configured with data-original-url, locking the regression. --- server/src/main/resources/web/picture.ftl | 10 ++++++++-- .../java/cn/keking/PictureFilePreviewTests.java | 14 +++++++++++++- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/server/src/main/resources/web/picture.ftl b/server/src/main/resources/web/picture.ftl index e6b3905d0..94e527953 100644 --- a/server/src/main/resources/web/picture.ftl +++ b/server/src/main/resources/web/picture.ftl @@ -77,8 +77,14 @@ button: false, backdrop: false, loop: true, - // #787:
没有 DOM 的 src 属性,Viewer.js 用 image.src 取标题会得到 undefined; - // 改为从 data-original-url 派生标题文件名(该属性在元素克隆时会被保留) + // #787: Viewer.js 的 title 回调拿到的 `image` 是它内部新建的 (见 viewer.min.js + // 的 view(): `var image = document.createElement('img')`),并不是原始
。Viewer + // 在构建缩略图与查看大图时只透传 `inheritedAttributes` 里的属性,而默认值不含 + // data-original-url,所以若不显式加入,getImageNameFromOriginalUrl 在该 上永远 + // getAttribute('data-original-url') == null,标题会恒为 'image'(修复形同虚设)。 + // 因此必须把 data-original-url 加进 inheritedAttributes,让原始 URL 随缩略图→大图透传。 + inheritedAttributes: ['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap', 'data-original-url'], + // 从 data-original-url 派生标题文件名(避免受反代 / CORS 代理 URL 影响) title: function (image, imageData) { return getImageNameFromOriginalUrl(image); } diff --git a/server/src/test/java/cn/keking/PictureFilePreviewTests.java b/server/src/test/java/cn/keking/PictureFilePreviewTests.java index 9885b634a..9be91c444 100644 --- a/server/src/test/java/cn/keking/PictureFilePreviewTests.java +++ b/server/src/test/java/cn/keking/PictureFilePreviewTests.java @@ -14,7 +14,12 @@ * * 根因:v4.3.0 之后模板把图片元素从 <img> 改成 <div src>,而 HTMLDivElement 没有 DOM 的 * src 属性,Viewer.js 用 image.src 取标题得到 undefined,导致标题为空。修复方式是在 Viewer 初始化时 - * 配置 title 函数,从 data-original-url 派生文件名(该属性在元素克隆时会被保留)。 + * 配置 title 函数,从 data-original-url 派生文件名。 + * + * 关键点(已核对 viewer.min.js 源码):title 回调拿到的 `image` 是 Viewer 内部新建的 <img> + * (view(): `var image = document.createElement('img')`),不是原始 <div>。Viewer 在构建缩略图 + * 与查看大图时只透传 `inheritedAttributes` 里的属性,默认值不含 data-original-url,所以必须把 + * data-original-url 加进 inheritedAttributes,否则 title 函数永远读不到它、标题恒为 'image'。 */ public class PictureFilePreviewTests { @@ -35,6 +40,13 @@ void shouldDeriveViewerTitleFromOriginalUrl() throws IOException { () -> "picture.ftl must derive the title from data-original-url (see #787)"); assertTrue(pictureTemplate.contains("getAttribute('data-original-url')"), () -> "title derivation must read data-original-url attribute (see #787)"); + + // 关键:Viewer.js 只会把 inheritedAttributes 透传到 title 回调拿到的 上,默认值不含 + // data-original-url。若不显式加入,title 函数读不到原始 URL、标题恒为 'image'(修复形同虚设)。 + assertTrue(pictureTemplate.contains("inheritedAttributes"), + () -> "picture.ftl must set Viewer inheritedAttributes so data-original-url reaches the title image (see #787)"); + assertTrue(pictureTemplate.contains("'data-original-url'"), + () -> "Viewer inheritedAttributes must include data-original-url (see #787)"); } private String readResource(String resourcePath) throws IOException { From 86c2b8d552c93a10e18ef40babcc51f63195efab Mon Sep 17 00:00:00 2001 From: kl Date: Thu, 8 Oct 2026 13:02:39 +0800 Subject: [PATCH 4/4] fix: retain original image titles when proxying previews --- server/src/main/resources/web/picture.ftl | 13 +++---------- .../java/cn/keking/PictureFilePreviewTests.java | 14 ++++---------- tests/e2e/specs/picture-title.spec.ts | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 20 deletions(-) create mode 100644 tests/e2e/specs/picture-title.spec.ts diff --git a/server/src/main/resources/web/picture.ftl b/server/src/main/resources/web/picture.ftl index 94e527953..854641302 100644 --- a/server/src/main/resources/web/picture.ftl +++ b/server/src/main/resources/web/picture.ftl @@ -77,16 +77,9 @@ button: false, backdrop: false, loop: true, - // #787: Viewer.js 的 title 回调拿到的 `image` 是它内部新建的 (见 viewer.min.js - // 的 view(): `var image = document.createElement('img')`),并不是原始
。Viewer - // 在构建缩略图与查看大图时只透传 `inheritedAttributes` 里的属性,而默认值不含 - // data-original-url,所以若不显式加入,getImageNameFromOriginalUrl 在该 上永远 - // getAttribute('data-original-url') == null,标题会恒为 'image'(修复形同虚设)。 - // 因此必须把 data-original-url 加进 inheritedAttributes,让原始 URL 随缩略图→大图透传。 - inheritedAttributes: ['crossOrigin', 'decoding', 'isMap', 'loading', 'referrerPolicy', 'sizes', 'srcset', 'useMap', 'data-original-url'], - // 从 data-original-url 派生标题文件名(避免受反代 / CORS 代理 URL 影响) - title: function (image, imageData) { - return getImageNameFromOriginalUrl(image); + // Viewer 会将克隆图片的 data-original-url 改为加载地址,因此从原始元素读取标题。 + title: function () { + return getImageNameFromOriginalUrl(this.images[this.index]); } }); viewer.view(0); // 0 是图片的索引,如果你想点击第一张图片,索引为 0 diff --git a/server/src/test/java/cn/keking/PictureFilePreviewTests.java b/server/src/test/java/cn/keking/PictureFilePreviewTests.java index 9be91c444..823377a3d 100644 --- a/server/src/test/java/cn/keking/PictureFilePreviewTests.java +++ b/server/src/test/java/cn/keking/PictureFilePreviewTests.java @@ -16,10 +16,8 @@ * src 属性,Viewer.js 用 image.src 取标题得到 undefined,导致标题为空。修复方式是在 Viewer 初始化时 * 配置 title 函数,从 data-original-url 派生文件名。 * - * 关键点(已核对 viewer.min.js 源码):title 回调拿到的 `image` 是 Viewer 内部新建的 <img> - * (view(): `var image = document.createElement('img')`),不是原始 <div>。Viewer 在构建缩略图 - * 与查看大图时只透传 `inheritedAttributes` 里的属性,默认值不含 data-original-url,所以必须把 - * data-original-url 加进 inheritedAttributes,否则 title 函数永远读不到它、标题恒为 'image'。 + * Viewer 会将内部克隆图片的 data-original-url 改为实际加载地址(可能是代理地址), + * 标题必须读取 this.images[this.index] 中保留原始 URL 的元素。 */ public class PictureFilePreviewTests { @@ -41,12 +39,8 @@ void shouldDeriveViewerTitleFromOriginalUrl() throws IOException { assertTrue(pictureTemplate.contains("getAttribute('data-original-url')"), () -> "title derivation must read data-original-url attribute (see #787)"); - // 关键:Viewer.js 只会把 inheritedAttributes 透传到 title 回调拿到的 上,默认值不含 - // data-original-url。若不显式加入,title 函数读不到原始 URL、标题恒为 'image'(修复形同虚设)。 - assertTrue(pictureTemplate.contains("inheritedAttributes"), - () -> "picture.ftl must set Viewer inheritedAttributes so data-original-url reaches the title image (see #787)"); - assertTrue(pictureTemplate.contains("'data-original-url'"), - () -> "Viewer inheritedAttributes must include data-original-url (see #787)"); + assertTrue(pictureTemplate.contains("getImageNameFromOriginalUrl(this.images[this.index])"), + () -> "Viewer title must use the original element rather than its proxy-backed clone (see #787)"); } private String readResource(String resourcePath) throws IOException { diff --git a/tests/e2e/specs/picture-title.spec.ts b/tests/e2e/specs/picture-title.spec.ts new file mode 100644 index 000000000..aff9b6fac --- /dev/null +++ b/tests/e2e/specs/picture-title.spec.ts @@ -0,0 +1,14 @@ +import { test, expect } from '@playwright/test'; + +const fixtureBase = process.env.FIXTURE_BASE_URL || 'http://127.0.0.1:18080'; + +for (const proxy of [false, true]) { + test(`image title retains the source filename with kkagent=${proxy}`, async ({ page }) => { + const source = `${fixtureBase}/sample.png`; + const encoded = encodeURIComponent(Buffer.from(source).toString('base64')); + const response = await page.goto(`/onlinePreview?url=${encoded}&kkagent=${proxy}`); + + expect(response?.status()).toBe(200); + await expect(page.locator('.viewer-title')).toHaveText('sample.png'); + }); +}