feat: add CORS handling and improve UI elements

- Added translations for CORS-related error messages in Chinese.
- Enhanced ImageViewer to display alt text in alerts for footnotes.
- Adjusted PopupAssist component styles for better layout and added emoji to questions.
- Updated PopupRefer to handle additional text formatting cases.
- Improved popupTrans styles with increased border-radius for buttons.
- Added emoji to sample questions in settings for better user experience.
- Implemented CORS testing in sync settings to ensure data source accessibility.
- Updated viewer component to include indentation setting from config.
- Changed login button text from "Log in" to "Continue" for consistency.
- Introduced testCORS function to validate CORS support for data sources.
This commit is contained in:
troyeguo
2025-08-28 15:46:18 +08:00
parent 31aa599f88
commit e8a0cf7440
13 changed files with 109 additions and 14 deletions

View File

File diff suppressed because one or more lines are too long

View File

File diff suppressed because one or more lines are too long

View File

@@ -412,6 +412,9 @@
"Reset sync records": "重置同步记录",
"Your Pro trial has expired, please renew it to continue using the Pro features": "您的专业版试用已过期,请续费以继续使用专业版功能",
"Support": "联系我们",
"Continue": "继续",
"This data source cannot be accessed from browser due to CORS policy. Please switch to another data source or CORS-enabled service provider.": "由于 CORS 策略,浏览器无法使用此数据源。请切换到其他数据源或使用启用了 CORS 的服务提供商。",
"This data source cannot be accessed due to browser's security policy. Please switch to another data source or HTTPS-based service provider.": "由于浏览器的安全策略,无法使用此数据源。请切换到其他数据源或使用基于 HTTPS 的服务提供商。",
"Hide page scale button": "不显示页面缩放按钮",
"Hide pdf to text button": "不显示 PDF 转文本按钮",
"With the integration of your cloud drive, WebDAV, and object storage, all your data remains securely in your control": "借助您绑定的网盘WebDAV 和对象存储实现,您所有的数据都由您掌握",

View File

@@ -4,6 +4,7 @@ import { ImageViewerProps, ImageViewerStates } from "./interface";
import { saveAs } from "file-saver";
import { getIframeDoc } from "../../utils/reader/docUtil";
import { getTargetHref } from "../../utils/common";
declare var window: any;
class ImageViewer extends React.Component<ImageViewerProps, ImageViewerStates> {
constructor(props: ImageViewerProps) {
super(props);
@@ -49,6 +50,17 @@ class ImageViewer extends React.Component<ImageViewerProps, ImageViewerStates> {
) {
return;
}
if (
event.target.tagName === "IMG" &&
event.target.getAttribute("alt") &&
((event.target.getAttribute("class") &&
event.target.getAttribute("class").indexOf("footnote") > -1) ||
(event.target.getAttribute("id") &&
event.target.getAttribute("id").indexOf("footnote") > -1))
) {
window.vex.dialog.alert(event.target.getAttribute("alt"));
return;
}
if (event.target.tagName === "IMG" && event.target.src) {
href = event.target.src;
}

View File

@@ -402,8 +402,8 @@ class PopupAssist extends React.Component<PopupAssistProps, PopupAssistState> {
style={{
marginTop: "60px",
width: "calc(100% + 20px)",
height: "225px",
paddingBottom: "60px",
height: "210px",
paddingBottom: "0px",
paddingLeft: "0px",
paddingRight: "20px",
}}
@@ -459,7 +459,7 @@ class PopupAssist extends React.Component<PopupAssistProps, PopupAssistState> {
style={{
marginLeft: "-25px",
marginRight: "-25px",
marginBottom: "-25px",
marginBottom: "-20px",
padding: "0px 25px",
}}
>
@@ -474,7 +474,7 @@ class PopupAssist extends React.Component<PopupAssistProps, PopupAssistState> {
this.handleNewQuestion(item.question);
}}
>
{this.props.t(item.question)}
{item.emoji + " " + this.props.t(item.question)}
</div>
);
})}

View File

@@ -10,16 +10,20 @@
}
.popup-assist-shortcut {
margin-right: 20px;
color: #f16464;
line-height: 1.25;
opacity: 0.8;
cursor: pointer;
margin-top: 10px;
}
.popup-assist-shortcut:hover {
color: #f16464;
opacity: 1;
}
.trans-add-content-box::placeholder {
opacity: 0.8;
}
.popup-message-user {
background-color: #f0f0f0;
background-color: rgba(75, 75, 75, 0.05);
border-radius: 8px;
padding: 10px;
margin-bottom: 10px;

View File

@@ -53,7 +53,11 @@ class PopupRefer extends React.Component<PopupReferProps, PopupReferStates> {
handleShowMenu = async (node, targetElement, rect) => {
if (
(node.textContent.trim() === targetElement.textContent.trim() ||
!node.textContent.trim()) &&
!node.textContent.trim() ||
"[" + node.textContent.trim() + "]" ===
targetElement.textContent.trim() ||
node.textContent.trim() ===
"[" + targetElement.textContent.trim() + "]") &&
node.parentElement
) {
if (node.parentElement.tagName !== "BODY") {

View File

@@ -83,7 +83,7 @@
float: left;
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
border-radius: 8px;
margin-right: 10px;
text-align: center;
line-height: 34px;
@@ -107,7 +107,7 @@
height: 34px;
padding-left: 10px;
padding-right: 10px;
border-radius: 5px;
border-radius: 8px;
margin-right: 10px;
text-align: center;
line-height: 34px;

View File

@@ -382,18 +382,22 @@ export const officialDictList = [
export const sampleQuestion = [
{
mode: "ask",
emoji: "📖",
question: "Summarize this chapter for me",
},
{
mode: "ask",
emoji: "📃",
question: "What are the key points of this chapter",
},
{
mode: "chat",
emoji: "📰",
question: "Recommend me some books from Colleen Hoover",
},
{
mode: "chat",
emoji: "🗞️",
question: "Explain Stoicism and its principles to me",
},
];

View File

@@ -14,6 +14,7 @@ import {
openExternalUrl,
openInBrowser,
testConnection,
testCORS,
WEBSITE_URL,
} from "../../../utils/common";
import { getStorageLocation } from "../../../utils/common";
@@ -388,16 +389,29 @@ class SyncSetting extends React.Component<SettingInfoProps, SettingInfoState> {
<div
className="voice-add-confirm"
onClick={async () => {
if (
this.props.settingDrive === "webdav" ||
this.props.settingDrive === "s3compatible"
) {
let corsonResult = await testCORS(
this.props.settingDrive === "webdav"
? this.state.driveConfig.url
: this.state.driveConfig.endpoint
);
if (!corsonResult) {
return;
}
}
if (
this.props.settingDrive === "docker" ||
this.props.settingDrive === "webdav" ||
this.props.settingDrive === "s3compatible"
) {
let result = await testConnection(
let connectionResult = await testConnection(
this.props.settingDrive,
this.state.driveConfig
);
if (!result) {
if (!connectionResult) {
return;
}
}
@@ -452,6 +466,19 @@ class SyncSetting extends React.Component<SettingInfoProps, SettingInfoState> {
className="voice-add-confirm"
style={{ marginRight: "10px" }}
onClick={async () => {
if (
this.props.settingDrive === "webdav" ||
this.props.settingDrive === "s3compatible"
) {
let corsonResult = await testCORS(
this.props.settingDrive === "webdav"
? this.state.driveConfig.url
: this.state.driveConfig.endpoint
);
if (!corsonResult) {
return;
}
}
testConnection(
this.props.settingDrive,
this.state.driveConfig

View File

@@ -200,6 +200,7 @@ class Viewer extends React.Component<ViewerProps, ViewerState> {
: "no",
backgroundColor: ConfigService.getReaderConfig("backgroundColor"),
isMobile: "no",
isIndent: ConfigService.getReaderConfig("isIndent"),
isStartFromEven: ConfigService.getReaderConfig("isStartFromEven"),
password: getPdfPassword(this.props.currentBook),
scale: parseFloat(this.state.scale),

View File

@@ -771,7 +771,7 @@ class Login extends React.Component<LoginProps, LoginState> {
marginTop: "10px",
}}
>
{this.props.t("Log in")}
{this.props.t("Continue")}
</div>
<div className="login-term">
{this.props.t(

View File

@@ -692,6 +692,46 @@ export const testConnection = async (driveName: string, driveConfig: any) => {
return await syncUtil.deleteFile("test.txt", "config");
}
};
export const testCORS = async (url: string) => {
if (isElectron) return true;
if (window.location.href.startsWith("https://")) {
if (url.startsWith("http://")) {
toast.error(
i18n.t(
"This data source cannot be accessed due to browser's security policy. Please switch to another data source or HTTPS-based service provider."
)
);
return false;
}
}
try {
const response = await fetch(url, {
method: "GET", // 或 'POST' 等
mode: "cors", // 明确指定跨域模式
headers: {
"Content-Type": "application/json", // 如果是POST可添加
},
// body: JSON.stringify({ test: 'data' }) // 如果是POST
});
if (response.ok) {
const data = await response.text();
console.log("CORS supported:", data);
return true;
} else {
console.log("Request failed but CORS may be supported");
return true;
}
} catch (error) {
toast.error(
i18n.t(
"This data source cannot be accessed from browser due to CORS policy. Please switch to another data source or CORS-enabled service provider."
)
);
console.error("CORS not supported:", error);
return false;
}
};
export const getTargetHref = (event: any) => {
let href = "";
if (!event || !event.target) return href;