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 committed 2025-08-28 15:46:18 +08:00
1 parent 31aa599f88
commit e8a0cf7440
13 files changed
+109 -14

No files matched your search

File diff suppressed because one or more lines are too long.
+1 -1
View File
File diff suppressed because one or more lines are too long.
@@ -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 和对象存储实现,您所有的数据都由您掌握",
+12
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;
}
@@ -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>
);
})}
@@ -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;
@@ -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") {
@@ -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;
+4
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",
},
];
@@ -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
+1
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),
+1 -1
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(
+40
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;