路由从 hash 换成 History API(pushState/replaceState/popstate),不再依赖 location.hash

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>
This commit is contained in:
Your Name
2026-09-07 21:00:39 +08:00
co-authored by Claude Sonnet 5
parent 97a6b88d59
commit 4e8f45d7d8
46 changed files with 398 additions and 330 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[686],{3686:function(e,a,t){t.r(a),t.d(a,{default:function(){return c}});var s=t(1594),l=t.n(s),i=t(8181),r=t(184),n=t(3272),d=t(7849),h=t(1781),p=t(4848);class c extends l().PureComponent{constructor(e){super(e);const a=(0,d.MC)(this.props.wallpaper),t=(0,d.t7)(this.props.wallpaper);this.state={tab:a||n.S.type,selectedType:a||n.S.type,wallpaper:t||h[n.S.type][n.S.index].name,blur:0|this.props.wallpaperBlur},this.blurValues=[0,1,2,4,8,16],this.handleTabClick=this.handleTabClick.bind(this),this.handleWallpaperSelected=this.handleWallpaperSelected.bind(this),this.handleBlurChanged=this.handleBlurChanged.bind(this),this.hasChanged=this.hasChanged.bind(this)}handleTabClick(e){e.preventDefault(),r.A.navigateTo(r.A.addUrlParam(window.location.hash,"tab",e.currentTarget.dataset.id)),this.setState({tab:e.currentTarget.dataset.id})}handleWallpaperSelected(e){let a,t,s;if(e)e.preventDefault(),t=this.state.tab,a=e.currentTarget.dataset.id,s="patt"==t?0:this.state.blur;else{if(!this.hasChanged())return;t=n.S.type,a=n.S.index,s=0}const l=h[t][a].name,i="patt"==t?h[t][a].size:0;this.setState({tab:t,wallpaper:l,selectedType:t,blur:s}),this.props.onWallpaperSelected(`../${n.S.path}${l}`,i,this.blurValues[s])}handleBlurChanged(e){e.preventDefault();const a=e.currentTarget.value;this.setState({blur:a}),"img"==this.state.selectedType&&this.props.onWallpaperSelected(`../${n.S.path}${this.state.wallpaper}`,0,this.blurValues[a])}hasChanged(){return this.state.selectedType!=n.S.type||this.state.wallpaper!=h[n.S.type][n.S.index].name||0!=this.state.blur}render(){return(0,p.jsxs)("div",{className:"flex-column",children:[(0,p.jsx)("div",{className:"panel-form-row"+(this.hasChanged()?" clean-clickable":""),children:(0,p.jsxs)("a",{className:"flat-button"+(this.hasChanged()?"":" disabled"),onClick:()=>this.handleWallpaperSelected(null),children:[(0,p.jsx)("i",{className:"material-icons",children:"undo"})," ",(0,p.jsx)(i.FormattedMessage,{id:"button_restore",defaultMessage:"Restore default",description:"Reset setting to default"})]})}),(0,p.jsxs)("ul",{className:"tabbar",children:[(0,p.jsx)("li",{className:"patt"===this.state.tab?"active":null,children:(0,p.jsx)("a",{href:"#","data-id":"patt",onClick:this.handleTabClick,children:(0,p.jsx)(i.FormattedMessage,{id:"tabtitle_pattern",defaultMessage:"pattern",description:"Tab title Pattern"})})}),(0,p.jsx)("li",{className:"img"===this.state.tab?"active":null,children:(0,p.jsx)("a",{href:"#","data-id":"img",onClick:this.handleTabClick,children:(0,p.jsx)(i.FormattedMessage,{id:"tabtitle_image",defaultMessage:"image",description:"Tab title Image"})})})]}),(0,p.jsx)("div",{id:"settings-form",className:"scrollable-panel",children:"patt"===this.state.tab?(0,p.jsx)("div",{className:"image-grid",children:h.patt.map((e,a)=>{const t=this.state.wallpaper===e.name?" selected":"",s="dark"==this.props.colorSchema?"inverted":null;return(0,p.jsx)("div",{"data-id":a,className:`image-grid-cell${t}`,onClick:this.handleWallpaperSelected,children:(0,p.jsx)("img",{src:n.S.path+e.name,alt:e.name,className:s,style:{width:`${e.size}px`,height:`${e.size}px`}})},a)})}):(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)("div",{className:"panel-form-column",children:[(0,p.jsx)("div",{className:"panel-form-row",children:(0,p.jsx)("label",{className:"small",children:(0,p.jsx)(i.FormattedMessage,{id:"label_blur_wallpaper",defaultMessage:"Blur:",description:"Label adjusting blur amount"})})}),(0,p.jsx)("div",{className:"panel-form-row",children:(0,p.jsx)("input",{type:"range",id:"blur",name:"blur",min:"0",max:"5",step:"1",value:this.state.blur,onChange:this.handleBlurChanged})})]}),(0,p.jsx)("div",{className:"image-grid",children:h.img.map((e,a)=>{const t=this.state.wallpaper===e.name?" selected":"";return(0,p.jsx)("div",{"data-id":a,className:`image-grid-cell${t}`,onClick:this.handleWallpaperSelected,children:(0,p.jsx)("img",{src:n.S.path+e.pr,alt:e.name,style:{width:"100%",height:"100%"}})},a)})})]})})]})}}}}]);
"use strict";(self.webpackChunktinode_webapp=self.webpackChunktinode_webapp||[]).push([[686],{3686:function(e,a,t){t.r(a),t.d(a,{default:function(){return c}});var s=t(1594),l=t.n(s),i=t(8181),r=t(184),n=t(3272),d=t(7849),h=t(1781),p=t(4848);class c extends l().PureComponent{constructor(e){super(e);const a=(0,d.MC)(this.props.wallpaper),t=(0,d.t7)(this.props.wallpaper);this.state={tab:a||n.S.type,selectedType:a||n.S.type,wallpaper:t||h[n.S.type][n.S.index].name,blur:0|this.props.wallpaperBlur},this.blurValues=[0,1,2,4,8,16],this.handleTabClick=this.handleTabClick.bind(this),this.handleWallpaperSelected=this.handleWallpaperSelected.bind(this),this.handleBlurChanged=this.handleBlurChanged.bind(this),this.hasChanged=this.hasChanged.bind(this)}handleTabClick(e){e.preventDefault(),r.A.navigateReplace(r.A.addUrlParam(r.A.currentUrl(),"tab",e.currentTarget.dataset.id)),this.setState({tab:e.currentTarget.dataset.id})}handleWallpaperSelected(e){let a,t,s;if(e)e.preventDefault(),t=this.state.tab,a=e.currentTarget.dataset.id,s="patt"==t?0:this.state.blur;else{if(!this.hasChanged())return;t=n.S.type,a=n.S.index,s=0}const l=h[t][a].name,i="patt"==t?h[t][a].size:0;this.setState({tab:t,wallpaper:l,selectedType:t,blur:s}),this.props.onWallpaperSelected(`../${n.S.path}${l}`,i,this.blurValues[s])}handleBlurChanged(e){e.preventDefault();const a=e.currentTarget.value;this.setState({blur:a}),"img"==this.state.selectedType&&this.props.onWallpaperSelected(`../${n.S.path}${this.state.wallpaper}`,0,this.blurValues[a])}hasChanged(){return this.state.selectedType!=n.S.type||this.state.wallpaper!=h[n.S.type][n.S.index].name||0!=this.state.blur}render(){return(0,p.jsxs)("div",{className:"flex-column",children:[(0,p.jsx)("div",{className:"panel-form-row"+(this.hasChanged()?" clean-clickable":""),children:(0,p.jsxs)("a",{className:"flat-button"+(this.hasChanged()?"":" disabled"),onClick:()=>this.handleWallpaperSelected(null),children:[(0,p.jsx)("i",{className:"material-icons",children:"undo"})," ",(0,p.jsx)(i.FormattedMessage,{id:"button_restore",defaultMessage:"Restore default",description:"Reset setting to default"})]})}),(0,p.jsxs)("ul",{className:"tabbar",children:[(0,p.jsx)("li",{className:"patt"===this.state.tab?"active":null,children:(0,p.jsx)("a",{href:"#","data-id":"patt",onClick:this.handleTabClick,children:(0,p.jsx)(i.FormattedMessage,{id:"tabtitle_pattern",defaultMessage:"pattern",description:"Tab title Pattern"})})}),(0,p.jsx)("li",{className:"img"===this.state.tab?"active":null,children:(0,p.jsx)("a",{href:"#","data-id":"img",onClick:this.handleTabClick,children:(0,p.jsx)(i.FormattedMessage,{id:"tabtitle_image",defaultMessage:"image",description:"Tab title Image"})})})]}),(0,p.jsx)("div",{id:"settings-form",className:"scrollable-panel",children:"patt"===this.state.tab?(0,p.jsx)("div",{className:"image-grid",children:h.patt.map((e,a)=>{const t=this.state.wallpaper===e.name?" selected":"",s="dark"==this.props.colorSchema?"inverted":null;return(0,p.jsx)("div",{"data-id":a,className:`image-grid-cell${t}`,onClick:this.handleWallpaperSelected,children:(0,p.jsx)("img",{src:n.S.path+e.name,alt:e.name,className:s,style:{width:`${e.size}px`,height:`${e.size}px`}})},a)})}):(0,p.jsxs)(p.Fragment,{children:[(0,p.jsxs)("div",{className:"panel-form-column",children:[(0,p.jsx)("div",{className:"panel-form-row",children:(0,p.jsx)("label",{className:"small",children:(0,p.jsx)(i.FormattedMessage,{id:"label_blur_wallpaper",defaultMessage:"Blur:",description:"Label adjusting blur amount"})})}),(0,p.jsx)("div",{className:"panel-form-row",children:(0,p.jsx)("input",{type:"range",id:"blur",name:"blur",min:"0",max:"5",step:"1",value:this.state.blur,onChange:this.handleBlurChanged})})]}),(0,p.jsx)("div",{className:"image-grid",children:h.img.map((e,a)=>{const t=this.state.wallpaper===e.name?" selected":"";return(0,p.jsx)("div",{"data-id":a,className:`image-grid-cell${t}`,onClick:this.handleWallpaperSelected,children:(0,p.jsx)("img",{src:n.S.path+e.pr,alt:e.name,style:{width:"100%",height:"100%"}})},a)})})]})})]})}}}}]);
//# sourceMappingURL=686.prod.js.map