Files
haorwen dfe6a7e193 refactor: 重构聊天显示部分以提升大量消息时web响应速度和在低性能设备上的表现 (#297)
* refactor: optimize message feed rendering and state management

- Removed unnecessary initialization state in VirtualMessageFeed.
- Updated message data subscription to only track visible messages, improving performance.
- Simplified props in Layout component for VirtualMessageFeed and Send components.

* refactor: 针对低性能设备进行更多优化,包括消息隔离等

- Added a script to suppress ResizeObserver errors in index.html.
- Introduced memoized selectors for efficient message retrieval in message selectors.
- Optimized message insertion logic in message slices to reduce unnecessary sorting.
- Improved memoization in Message component for better rendering performance.
- Enhanced MessageInput to reset editor state on chat changes.
- Updated VirtualMessageFeed to manage visible messages more efficiently and reduce initial load for better performance on low-end devices.

* chore: bump version to v0.9.50
2026-01-17 17:01:46 +08:00

231 lines
7.6 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta name="theme-color" content="#527ff1" />
<meta name="description" content="Your private chat APP" />
<!-- Suppress ResizeObserver errors - must be first script -->
<script>
// Suppress benign ResizeObserver errors from virtualized lists
// This is a known browser issue and doesn't affect functionality
// Reference: https://github.com/WICG/resize-observer/issues/38
(function() {
function isResizeObserverError(message) {
return message && (
message.includes('ResizeObserver loop') ||
message.includes('ResizeObserver loop completed') ||
message.includes('ResizeObserver loop limit exceeded')
);
}
// Intercept window.onerror
const originalOnError = window.onerror;
window.onerror = function(message, source, lineno, colno, error) {
if (typeof message === 'string' && isResizeObserverError(message)) {
return true; // Suppress ResizeObserver errors
}
if (originalOnError) {
return originalOnError.apply(this, arguments);
}
return false;
};
// Intercept error events (capture phase to catch early)
window.addEventListener('error', function(e) {
if (e.message && isResizeObserverError(e.message)) {
e.stopImmediatePropagation();
e.preventDefault();
return true;
}
}, true);
// Intercept console.error
const originalConsoleError = console.error;
console.error = function() {
const args = Array.from(arguments);
if (args.length > 0 && typeof args[0] === 'string' && isResizeObserverError(args[0])) {
return; // Suppress
}
originalConsoleError.apply(console, args);
};
})();
</script>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
<!-- splash -->
<link
href="%PUBLIC_URL%/splash/iphone5_splash.png"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image"
/>
<link
href="%PUBLIC_URL%/splash/iphone6_splash.png"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image"
/>
<link
href="%PUBLIC_URL%/splash/iphoneplus_splash.png"
media="(device-width: 621px) and (device-height: 1104px) and (-webkit-device-pixel-ratio: 3)"
rel="apple-touch-startup-image"
/>
<link
href="%PUBLIC_URL%/splash/iphonex_splash.png"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3)"
rel="apple-touch-startup-image"
/>
<link
href="%PUBLIC_URL%/splash/iphonexr_splash.png"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image"
/>
<link
href="%PUBLIC_URL%/splash/iphonexsmax_splash.png"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3)"
rel="apple-touch-startup-image"
/>
<link
href="%PUBLIC_URL%/splash/ipad_splash.png"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image"
/>
<link
href="%PUBLIC_URL%/splash/ipadpro1_splash.png"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image"
/>
<link
href="%PUBLIC_URL%/splash/ipadpro3_splash.png"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image"
/>
<link
href="%PUBLIC_URL%/splash/ipadpro2_splash.png"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2)"
rel="apple-touch-startup-image"
/>
<!-- /splash -->
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!-- <link rel="manifest" id="my-manifest-placeholder"> -->
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>VoceChat WebAPP</title>
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.0/styles/github.min.css"> -->
<style>
html {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif !important;
}
</style>
</head>
<style>
#root-modal,
#modal-modal {
pointer-events: none;
position: fixed;
z-index: 999;
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#root-modal > .wrapper,
#modal-modal > .wrapper {
display: flex;
justify-content: center;
align-items: center;
}
#root-modal.mask > .wrapper,
#modal-modal.mask > .wrapper {
background-color: rgba(2, 2, 2, 0.4);
pointer-events: all;
width: 100vw;
height: 100dvh;
}
</style>
<style>
/* reset */
body {
overflow: hidden;
width: 100%;
height: 100%;
}
/* iOS safari */
@supports (-webkit-touch-callout: none) {
body {
height: -webkit-fill-available;
}
.h-screen {
height: -webkit-fill-available !important ;
overflow: auto;
}
}
/* iframe 样式覆盖 */
body.iframe {
position: fixed;
top: 0;
}
body.iframe .guest-container {
padding: 0;
}
body.iframe .guest-container .left-container,
body.iframe .guest-container .right-container {
border-radius: 0;
}
/* electron */
body.electron .guest-container {
padding: 0;
}
body.electron .guest-container .left-container,
body.electron .guest-container .right-container {
border-radius: 0;
}
</style>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<div id="root-modal"></div>
<div id="modal-modal"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
<script>
// electron
if (navigator.userAgent.indexOf("Electron/") > -1) {
document.body.classList.add("electron");
}
if (window.location !== window.parent.location) {
// The page is in an iframe
document.body.classList.add("iframe");
}
</script>
</html>