mirror of
https://github.com/wavetermdev/waveterm.git
synced 2024-12-22 16:48:23 +01:00
webview fixes (#88)
- Added restart button - Handle localhost URLs - Handle in-page navigation(SPA)
This commit is contained in:
parent
e162960c9e
commit
4c45bca56b
@ -12,6 +12,19 @@
|
|||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-top-left-radius: 4px;
|
border-top-left-radius: 4px;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 6px 12px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 16px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
&.fa-rotate-right {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.url-input-wrapper {
|
.url-input-wrapper {
|
||||||
|
@ -17,12 +17,14 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
const [url, setUrl] = useState(initialUrl);
|
const [url, setUrl] = useState(initialUrl);
|
||||||
const [inputUrl, setInputUrl] = useState(initialUrl); // Separate state for the input field
|
const [inputUrl, setInputUrl] = useState(initialUrl); // Separate state for the input field
|
||||||
const [webViewHeight, setWebViewHeight] = useState(0);
|
const [webViewHeight, setWebViewHeight] = useState(0);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const webviewRef = useRef<WebviewTag>(null);
|
const webviewRef = useRef<WebviewTag>(null);
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const historyStack = useRef<string[]>([]);
|
const historyStack = useRef<string[]>([]);
|
||||||
const historyIndex = useRef<number>(-1);
|
const historyIndex = useRef<number>(-1);
|
||||||
|
const recentUrls = useRef<{ [key: string]: number }>({});
|
||||||
|
|
||||||
const getWebViewHeight = () => {
|
const getWebViewHeight = () => {
|
||||||
const inputHeight = inputRef.current?.getBoundingClientRect().height;
|
const inputHeight = inputRef.current?.getBoundingClientRect().height;
|
||||||
@ -44,11 +46,12 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
const normalizedLastUrl = normalizeUrl(historyStack.current[historyIndex.current]);
|
const normalizedLastUrl = normalizeUrl(historyStack.current[historyIndex.current]);
|
||||||
|
|
||||||
if (normalizedLastUrl !== normalizedNewUrl) {
|
if (normalizedLastUrl !== normalizedNewUrl) {
|
||||||
setUrl(newUrl);
|
setUrl(normalizedNewUrl);
|
||||||
setInputUrl(newUrl); // Update input field as well
|
setInputUrl(normalizedNewUrl); // Update input field as well
|
||||||
historyIndex.current += 1;
|
historyIndex.current += 1;
|
||||||
historyStack.current = historyStack.current.slice(0, historyIndex.current);
|
historyStack.current = historyStack.current.slice(0, historyIndex.current);
|
||||||
historyStack.current.push(newUrl);
|
historyStack.current.push(normalizedNewUrl);
|
||||||
|
updateRecentUrls(normalizedNewUrl);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -59,6 +62,8 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
|
|
||||||
webview.addEventListener("did-navigate", navigateListener);
|
webview.addEventListener("did-navigate", navigateListener);
|
||||||
webview.addEventListener("did-navigate-in-page", navigateListener);
|
webview.addEventListener("did-navigate-in-page", navigateListener);
|
||||||
|
webview.addEventListener("did-start-loading", () => setIsLoading(true));
|
||||||
|
webview.addEventListener("did-stop-loading", () => setIsLoading(false));
|
||||||
|
|
||||||
// Handle new-window event
|
// Handle new-window event
|
||||||
webview.addEventListener("new-window", (event: any) => {
|
webview.addEventListener("new-window", (event: any) => {
|
||||||
@ -102,6 +107,11 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
inputRef.current.focus();
|
inputRef.current.focus();
|
||||||
inputRef.current.select();
|
inputRef.current.select();
|
||||||
}
|
}
|
||||||
|
} else if ((event.ctrlKey || event.metaKey) && event.key === "r") {
|
||||||
|
event.preventDefault();
|
||||||
|
if (webviewRef.current) {
|
||||||
|
webviewRef.current.reload();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -135,7 +145,11 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const ensureUrlScheme = (url: string) => {
|
const ensureUrlScheme = (url: string) => {
|
||||||
if (!/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) {
|
if (/^(localhost|(\d{1,3}\.){3}\d{1,3})(:\d+)?/.test(url)) {
|
||||||
|
// If the URL starts with localhost or an IP address (with optional port)
|
||||||
|
return `http://${url}`;
|
||||||
|
} else if (!/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(url)) {
|
||||||
|
// If the URL doesn't start with a protocol
|
||||||
return `https://${url}`;
|
return `https://${url}`;
|
||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
@ -147,11 +161,25 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
if (parsedUrl.hostname.startsWith("www.")) {
|
if (parsedUrl.hostname.startsWith("www.")) {
|
||||||
parsedUrl.hostname = parsedUrl.hostname.slice(4);
|
parsedUrl.hostname = parsedUrl.hostname.slice(4);
|
||||||
}
|
}
|
||||||
parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, ""); // Remove trailing slashes
|
|
||||||
parsedUrl.search = ""; // Remove query parameters
|
// Ensure pathname ends with a trailing slash
|
||||||
|
if (!parsedUrl.pathname.endsWith("/")) {
|
||||||
|
parsedUrl.pathname += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure hash fragments end with a trailing slash
|
||||||
|
if (parsedUrl.hash && !parsedUrl.hash.endsWith("/")) {
|
||||||
|
parsedUrl.hash += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure search parameters end with a trailing slash
|
||||||
|
if (parsedUrl.search && !parsedUrl.search.endsWith("/")) {
|
||||||
|
parsedUrl.search += "/";
|
||||||
|
}
|
||||||
|
|
||||||
return parsedUrl.href;
|
return parsedUrl.href;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return url.replace(/\/+$/, ""); // Fallback for invalid URLs
|
return url.replace(/\/+$/, "") + "/";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -161,20 +189,24 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
const normalizedLastUrl = normalizeUrl(historyStack.current[historyIndex.current]);
|
const normalizedLastUrl = normalizeUrl(historyStack.current[historyIndex.current]);
|
||||||
|
|
||||||
if (normalizedLastUrl !== normalizedFinalUrl) {
|
if (normalizedLastUrl !== normalizedFinalUrl) {
|
||||||
setUrl(finalUrl);
|
setUrl(normalizedFinalUrl);
|
||||||
setInputUrl(finalUrl);
|
setInputUrl(normalizedFinalUrl);
|
||||||
historyIndex.current += 1;
|
historyIndex.current += 1;
|
||||||
historyStack.current = historyStack.current.slice(0, historyIndex.current);
|
historyStack.current = historyStack.current.slice(0, historyIndex.current);
|
||||||
historyStack.current.push(finalUrl);
|
historyStack.current.push(normalizedFinalUrl);
|
||||||
if (webviewRef.current) {
|
if (webviewRef.current) {
|
||||||
webviewRef.current.src = finalUrl;
|
webviewRef.current.src = normalizedFinalUrl;
|
||||||
}
|
}
|
||||||
|
updateRecentUrls(normalizedFinalUrl);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBack = () => {
|
const handleBack = () => {
|
||||||
if (historyIndex.current > 0) {
|
if (historyIndex.current > 0) {
|
||||||
historyIndex.current -= 1;
|
do {
|
||||||
|
historyIndex.current -= 1;
|
||||||
|
} while (historyIndex.current > 0 && isRecentUrl(historyStack.current[historyIndex.current]));
|
||||||
|
|
||||||
const prevUrl = historyStack.current[historyIndex.current];
|
const prevUrl = historyStack.current[historyIndex.current];
|
||||||
setUrl(prevUrl);
|
setUrl(prevUrl);
|
||||||
setInputUrl(prevUrl);
|
setInputUrl(prevUrl);
|
||||||
@ -186,7 +218,13 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
|
|
||||||
const handleForward = () => {
|
const handleForward = () => {
|
||||||
if (historyIndex.current < historyStack.current.length - 1) {
|
if (historyIndex.current < historyStack.current.length - 1) {
|
||||||
historyIndex.current += 1;
|
do {
|
||||||
|
historyIndex.current += 1;
|
||||||
|
} while (
|
||||||
|
historyIndex.current < historyStack.current.length - 1 &&
|
||||||
|
isRecentUrl(historyStack.current[historyIndex.current])
|
||||||
|
);
|
||||||
|
|
||||||
const nextUrl = historyStack.current[historyIndex.current];
|
const nextUrl = historyStack.current[historyIndex.current];
|
||||||
setUrl(nextUrl);
|
setUrl(nextUrl);
|
||||||
setInputUrl(nextUrl);
|
setInputUrl(nextUrl);
|
||||||
@ -196,6 +234,16 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleRefresh = () => {
|
||||||
|
if (webviewRef.current) {
|
||||||
|
if (isLoading) {
|
||||||
|
webviewRef.current.stop();
|
||||||
|
} else {
|
||||||
|
webviewRef.current.reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleUrlChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleUrlChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setInputUrl(event.target.value);
|
setInputUrl(event.target.value);
|
||||||
};
|
};
|
||||||
@ -210,6 +258,22 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
event.target.select();
|
event.target.select();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateRecentUrls = (url: string) => {
|
||||||
|
if (recentUrls.current[url]) {
|
||||||
|
recentUrls.current[url]++;
|
||||||
|
} else {
|
||||||
|
recentUrls.current[url] = 1;
|
||||||
|
}
|
||||||
|
// Clean up old entries after a certain threshold
|
||||||
|
if (Object.keys(recentUrls.current).length > 50) {
|
||||||
|
recentUrls.current = {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isRecentUrl = (url: string) => {
|
||||||
|
return recentUrls.current[url] > 1;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="webview-wrapper">
|
<div className="webview-wrapper">
|
||||||
<div className="toolbar">
|
<div className="toolbar">
|
||||||
@ -224,6 +288,9 @@ const WebView = ({ parentRef, initialUrl }: WebViewProps) => {
|
|||||||
>
|
>
|
||||||
<i className="fa-sharp fa-regular fa-arrow-right"></i>
|
<i className="fa-sharp fa-regular fa-arrow-right"></i>
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button onClick={handleRefresh} className="secondary ghost refresh">
|
||||||
|
<i className={`fa-sharp fa-regular ${isLoading ? "fa-xmark" : "fa-rotate-right"}`}></i>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className="url-input-wrapper">
|
<div className="url-input-wrapper">
|
||||||
<input
|
<input
|
||||||
|
Loading…
Reference in New Issue
Block a user