mirror of
https://github.com/koodo-reader/koodo-reader.git
synced 2026-06-18 21:00:35 -04:00
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:
2
src/assets/lib/kookit-extra-browser.min.js
vendored
2
src/assets/lib/kookit-extra-browser.min.js
vendored
File diff suppressed because one or more lines are too long
2
src/assets/lib/kookit.min.js
vendored
2
src/assets/lib/kookit.min.js
vendored
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 和对象存储实现,您所有的数据都由您掌握",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user