HashNavigation 改名 Navigation(src/lib/navigation.js),接口不变,内部改用真实路径: parseUrlHash/composeUrlHash 改名 parseUrl/composeUrl;navigateTo/navigateReplace 改调 pushState/replaceState 后手动派发 popstate(pushState/replaceState 不会像赋值 location.hash 那样自动触发事件,这里复用 navigateReplace 早就在用的同一个手动 dispatch 手法);goBack() 不用改,history.back() 本来就是真实浏览器历史机制。新增 Navigation.currentUrl() 替代调用方 原本直接读的 window.location.hash(这个类从来没包装过这个读取)。 一处需要专门处理的边界情况:当 path[0] 是空字符串("不选中侧栏视图、只开一个会话"这个仓库里 的合法状态)时,拼出的真实路径开头会变成双斜杠,被浏览器当协议相对地址解析、试图跳到一个叫 会话id的host,pushState 直接抛 SecurityError。composeUrl/parseUrl 内部用占位 token "_" 处理 这种情况,对外的调用方(19个文件)完全无感。 tinode-web.jsx 的 handleHashRoute 改名 handleRouteChange,监听从 hashchange 换成 popstate; service-worker.js 的推送通知点击跳转手动改写成同样的路径格式(这个文件在打包之外单独执行, 没法 import Navigation)。index.html/index-dev.html 加了 <base href="/">——所有静态资源引用 都是相对路径,hash 路由下页面路径永远是 /,换真实路径后深层地址刷新会按当前目录解析出错误 资源地址导致白屏,这一行是标准修法。nginx.conf 的 try_files 兜底规则本来就适用两种路由,只 更新了注释。 实机验证:地址栏不再有 #;点击会话/设置等各类视图正常跳转;浏览器前进后退和应用内"返回" 按钮都正常;收藏页内部的类型 tab 切换(navigateReplace)确认不产生新的历史记录;在会话页/ 个人中心页刷新、直接用完整深层路径打开新标签页都能正确恢复到位,资源正常加载不白屏。 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
79 lines
3.0 KiB
JavaScript
79 lines
3.0 KiB
JavaScript
// Put all packages together.
|
|
// Used to generate umd/index.prod.js
|
|
|
|
import React from 'react';
|
|
import { createRoot } from 'react-dom/client';
|
|
|
|
import { IntlProvider } from 'react-intl';
|
|
|
|
import TinodeWeb from './views/tinode-web.jsx';
|
|
import Navigation from './lib/navigation.js';
|
|
|
|
// Insert google analytics script and tag if configured.
|
|
if (typeof FIREBASE_INIT != 'undefined' && FIREBASE_INIT && FIREBASE_INIT.measurementId) {
|
|
const head = document.getElementsByTagName('head')[0];
|
|
let script = document.createElement('script');
|
|
script.src = 'https://www.googletagmanager.com/gtag/js?id=' + FIREBASE_INIT.measurementId;
|
|
script.async = true;
|
|
head.prepend(script);
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(){dataLayer.push(arguments);}
|
|
gtag('js', new Date());
|
|
gtag('config', FIREBASE_INIT.measurementId);
|
|
}
|
|
|
|
// Allow loading translation strings for just one language.
|
|
const messageLoader = {
|
|
'ar': _ => import('./i18n.min/ar.json'),
|
|
'de': _ => import('./i18n.min/de.json'),
|
|
'en': _ => import('./i18n.min/en.json'),
|
|
'es': _ => import('./i18n.min/es.json'),
|
|
'fr': _ => import('./i18n.min/fr.json'),
|
|
'it': _ => import('./i18n.min/it.json'),
|
|
'ko': _ => import('./i18n.min/ko.json'),
|
|
'ro': _ => import('./i18n.min/ro.json'),
|
|
'ru': _ => import('./i18n.min/ru.json'),
|
|
'th': _ => import('./i18n.min/th.json'),
|
|
'uk': _ => import('./i18n.min/uk.json'),
|
|
'vi': _ => import('./i18n.min/vi.json'),
|
|
'zh': _ => import('./i18n.min/zh.json'),
|
|
'zh-TW': _ => import('./i18n.min/zh-TW.json')
|
|
};
|
|
|
|
// List of languages that are written right-to-left.
|
|
const rtl = ['ar', 'fa', 'he', 'ur'];
|
|
|
|
// Detect human language to use in the UI:
|
|
// Check parameters from URL hash #?hl=ru, then browser, then use 'en' as a fallback.
|
|
const { params } = Navigation.parseUrl(Navigation.currentUrl());
|
|
const language = (params && params.hl) ||
|
|
(navigator.languages && navigator.languages[0]) ||
|
|
navigator.language ||
|
|
navigator.userLanguage ||
|
|
'en';
|
|
|
|
// Make sure names like 'en_GB' and 'en-GB' consistently use '-'.
|
|
const normalized = language.replace('_', '-');
|
|
// Get the base language 'en' from a more specific 'en-US' as a partial fallback.
|
|
const baseLanguage = normalized.split('-')[0].toLowerCase();
|
|
|
|
// Try the full locale first, then the locale without the region code, fallback to 'en'.
|
|
const htmlLang = messageLoader[normalized] ? language : messageLoader[baseLanguage] ? baseLanguage : 'en';
|
|
|
|
const html = document.getElementsByTagName('html')[0];
|
|
// Set lang attribute of the HTML element: <html lang="XX">
|
|
html.setAttribute('lang', htmlLang);
|
|
if (rtl.includes(baseLanguage)) {
|
|
// Set right-to-left dir attribute of the HTML element: <html dir="rtl">
|
|
html.setAttribute('dir', 'rtl');
|
|
}
|
|
// Render the app.
|
|
const root = createRoot(document.getElementById('mountPoint'));
|
|
messageLoader[htmlLang]().then(messages =>
|
|
root.render(
|
|
React.createElement(IntlProvider,
|
|
{ locale: language, messages: messages, textComponent: React.Fragment },
|
|
React.createElement(TinodeWeb, null)
|
|
)
|
|
));
|