From e4a47d8455ccd8f01aca626b33c017552fbf283a Mon Sep 17 00:00:00 2001
From: 7ui77
Date: Tue, 6 Oct 2026 20:03:01 +0700
Subject: [PATCH 1/2] fix(english): fix WTR-LAB only showing [image] instead of
images
---
plugins/english/wtrlab.ts | 70 +++++++++++++++++++++++++++++++++++----
1 file changed, 63 insertions(+), 7 deletions(-)
diff --git a/plugins/english/wtrlab.ts b/plugins/english/wtrlab.ts
index 0bb43044c..4ebc45d8b 100644
--- a/plugins/english/wtrlab.ts
+++ b/plugins/english/wtrlab.ts
@@ -9,7 +9,7 @@ class WTRLAB implements Plugin.PluginBase {
id = 'WTRLAB';
name = 'WTR-LAB';
site = 'https://wtr-lab.com/';
- version = '1.2.3';
+ version = '1.2.4';
icon = 'src/en/wtrlab/icon.png';
sourceLang = 'en/';
baggage = '';
@@ -831,10 +831,18 @@ class WTRLAB implements Plugin.PluginBase {
}
let chapterContent: any = content.body;
- const chapterTitle: string | undefined =
- parsedJson?.chapter?.title || content.title;
const chapterGlossary: ChapterContent['glossary_data'] | undefined =
content.glossary_data;
+ const dictionary = chapterGlossary?.terms?.map(t => t[0]) || [];
+
+ let chapterTitle: string | undefined =
+ parsedJson?.chapter?.title || content.title;
+ if (chapterTitle && dictionary.length > 0) {
+ chapterTitle = chapterTitle.replaceAll(
+ /(?:wtr-lab\s+)?※([0-9]+)[⛬〓]/g,
+ (m: string, index: string) => dictionary[parseInt(index)] || m,
+ );
+ }
let htmlString = '';
if (chapterTitle) {
@@ -870,16 +878,63 @@ class WTRLAB implements Plugin.PluginBase {
)}
`;
}
- const dictionary = chapterGlossary?.terms?.map(t => t[0]) || [];
+ const images: string[] = Array.isArray(content.images)
+ ? content.images
+ : Array.isArray((parsedJson as any)?.images)
+ ? (parsedJson as any).images
+ : [];
+ let imageIndex = 0;
+
+ const resolveImageUrl = (imgUrl: string): string => {
+ if (!imgUrl) return '';
+ if (/^https?:\/\//i.test(imgUrl)) return imgUrl;
+ if (imgUrl.startsWith('//')) return 'https:' + imgUrl;
+ return this.site.replace(/\/$/, '') + '/' + imgUrl.replace(/^\//, '');
+ };
+
+ const paragraphs: string[] = Array.isArray(chapterContent)
+ ? chapterContent
+ : typeof chapterContent === 'string'
+ ? chapterContent.split('\n')
+ : [];
- for (let text of chapterContent) {
+ for (let text of paragraphs) {
+ if (typeof text !== 'string') continue;
if (dictionary.length > 0) {
text = text.replaceAll(
/(?:wtr-lab\s+)?※([0-9]+)[⛬〓]/g,
(m: string, index: string) => dictionary[parseInt(index)] || m,
);
}
- htmlString += `${text}
`;
+
+ if (/\[\s*image\s*\]/i.test(text)) {
+ const parts = text.split(/(\[\s*image\s*\])/gi);
+ let paragraphHtml = '';
+ for (const part of parts) {
+ if (/^\[\s*image\s*\]$/i.test(part.trim())) {
+ const nextImg = images[imageIndex++];
+ if (nextImg) {
+ paragraphHtml += `
`;
+ } else {
+ paragraphHtml += '[image]';
+ }
+ } else if (part) {
+ paragraphHtml += part;
+ }
+ }
+ if (paragraphHtml.trim()) {
+ htmlString += `${paragraphHtml}
`;
+ }
+ } else {
+ htmlString += `${text}
`;
+ }
+ }
+
+ while (imageIndex < images.length) {
+ const remainingImg = images[imageIndex++];
+ if (remainingImg) {
+ htmlString += `})
`;
+ }
}
return htmlString;
@@ -2045,7 +2100,8 @@ type ReaderResponse = {
type ChapterContent = {
title: string;
- body: string;
+ body: string | string[];
+ images?: string[];
glossary_data?: {
terms: string[][];
};
From 53b566947cf96d66dac76bf6ba6e1921f009168b Mon Sep 17 00:00:00 2001
From: 7ui77
Date: Tue, 6 Oct 2026 20:33:44 +0700
Subject: [PATCH 2/2] fix(english): escape image URLs in HTML attributes
---
plugins/english/wtrlab.ts | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/plugins/english/wtrlab.ts b/plugins/english/wtrlab.ts
index 4ebc45d8b..dfa4050af 100644
--- a/plugins/english/wtrlab.ts
+++ b/plugins/english/wtrlab.ts
@@ -892,6 +892,14 @@ class WTRLAB implements Plugin.PluginBase {
return this.site.replace(/\/$/, '') + '/' + imgUrl.replace(/^\//, '');
};
+ const escapeAttr = (str: string): string =>
+ str
+ .replace(/&/g, '&')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''')
+ .replace(//g, '>');
+
const paragraphs: string[] = Array.isArray(chapterContent)
? chapterContent
: typeof chapterContent === 'string'
@@ -914,7 +922,9 @@ class WTRLAB implements Plugin.PluginBase {
if (/^\[\s*image\s*\]$/i.test(part.trim())) {
const nextImg = images[imageIndex++];
if (nextImg) {
- paragraphHtml += `
`;
+ paragraphHtml += `
`;
} else {
paragraphHtml += '[image]';
}
@@ -933,7 +943,9 @@ class WTRLAB implements Plugin.PluginBase {
while (imageIndex < images.length) {
const remainingImg = images[imageIndex++];
if (remainingImg) {
- htmlString += `})
`;
+ htmlString += `,
+ )})
`;
}
}