more work on #hash navigation

This commit is contained in:
or-else
2018-04-22 14:24:26 -07:00
parent 18ef1e88fd
commit e5fc770369
+63 -31
View File
@@ -262,7 +262,7 @@
// path and arguments. // path and arguments.
function parseUrlHash(hash) { function parseUrlHash(hash) {
var parser = document.createElement('a'); var parser = document.createElement('a');
parser.href = '/' + location.hash.substr(1); parser.href = '/' + hash.substr(1);
var params = {}; var params = {};
parser.search.substr(1).split("&").forEach(function(part) { parser.search.substr(1).split("&").forEach(function(part) {
var item = part.split("="); var item = part.split("=");
@@ -270,11 +270,11 @@
params[decodeURIComponent(item[0])] = decodeURIComponent(item[1]); params[decodeURIComponent(item[0])] = decodeURIComponent(item[1]);
} }
}); });
return {path: parser.pathname, params: params}; return {path: parser.pathname.substr(1).split("/"), params: params};
} }
function composeUrlHash(path, params) { function composeUrlHash(path, params) {
var url = path; var url = path.join("/");
var args = []; var args = [];
for (var key in params) { for (var key in params) {
if (params.hasOwnProperty(key)) { if (params.hasOwnProperty(key)) {
@@ -284,7 +284,24 @@
if (args.length > 0) { if (args.length > 0) {
url += "?" + args.join("&"); url += "?" + args.join("&");
} }
return url. return url;
}
function addParamToHash(hash, key, value) {
var hash = parseUrlHash(hash);
if (!hash.params) {
hash.params = {};
}
hash.params[key] = value;
return composeUrlHash(hash.path, hash.params);
}
function removeParamFromHash(hash, key) {
var hash = parseUrlHash(hash);
if (hash.params) {
delete hash.params[key];
}
return composeUrlHash(hash.path, hash.params);
} }
// Detect server address from the URL // Detect server address from the URL
@@ -543,7 +560,7 @@
render() { render() {
return ( return (
<a href="#" onClick={this.props.onCancel}><i className="material-icons">close</i></a> <a href="javascript:;" onClick={this.props.onCancel}><i className="material-icons">close</i></a>
); );
} }
} }
@@ -1005,7 +1022,7 @@
} }
<span>{title}</span> <span>{title}</span>
{this.props.onCancel && !this.props.required ? {this.props.onCancel && !this.props.required ?
<a href="#" onClick={this.handleCancel} >&times;</a> <a href="javascript:;" onClick={this.handleCancel} >&times;</a>
: <span className="spacer" />} : <span className="spacer" />}
</div> </div>
); );
@@ -1311,9 +1328,9 @@
render: function() { render: function() {
return ( return (
<div> <div>
<a href="#" onClick={this.props.onSignUp}><i className="material-icons">person_add</i></a> <a href="javascript:;" onClick={this.props.onSignUp}><i className="material-icons">person_add</i></a>
&nbsp; &nbsp;
<a href="#" onClick={this.props.onSettings}><i className="material-icons">settings</i></a> <a href="javascript:;" onClick={this.props.onSettings}><i className="material-icons">settings</i></a>
</div> </div>
); );
} }
@@ -1323,9 +1340,9 @@
render: function() { render: function() {
return ( return (
<div> <div>
<a href="#" onClick={this.props.onNewTopic}><i className="material-icons">chat</i></a> <a href="javascript:;" onClick={this.props.onNewTopic}><i className="material-icons">chat</i></a>
&nbsp; &nbsp;
<a href="#" onClick={this.props.onSettings}><i className="material-icons">settings</i></a> <a href="javascript:;" onClick={this.props.onSettings}><i className="material-icons">settings</i></a>
</div> </div>
); );
} }
@@ -1590,7 +1607,7 @@
onSubmit={this.handleTagsUpdated} /> onSubmit={this.handleTagsUpdated} />
<div className="hr" /> <div className="hr" />
<div className="panel-form-column"> <div className="panel-form-column">
<a href="#" className="red flat-button" onClick={this.props.onLogout}> <a href="javascript:;" className="red flat-button" onClick={this.props.onLogout}>
<i className="material-icons">exit_to_app</i> Logout <i className="material-icons">exit_to_app</i> Logout
</a> </a>
</div> </div>
@@ -1705,7 +1722,7 @@
</div> </div>
: :
<div> <div>
<a href="#" className="flat-button" onClick={this.handleShowTagManager}> <a href="javascript:;" className="flat-button" onClick={this.handleShowTagManager}>
<i className="material-icons">edit</i> Manage tags <i className="material-icons">edit</i> Manage tags
</a> </a>
<span>{tags}</span> <span>{tags}</span>
@@ -1777,7 +1794,7 @@
</div> </div>
{this.props.showContextMenu ? {this.props.showContextMenu ?
<span className="menuTrigger"> <span className="menuTrigger">
<a href="#" onClick={this.handleContextClick}> <a href="javascript:;" onClick={this.handleContextClick}>
<i className="material-icons">expand_more</i> <i className="material-icons">expand_more</i>
</a> </a>
</span> : null} </span> : null}
@@ -2195,13 +2212,13 @@
<div className="flex-column"> <div className="flex-column">
<ul className="tabbar"> <ul className="tabbar">
<li className={this.state.tabSelected === "p2p" ? "active" : null}> <li className={this.state.tabSelected === "p2p" ? "active" : null}>
<a href="#" data-id="p2p" onClick={this.handleTabClick}>1:1</a> <a href="javascript:;" data-id="p2p" onClick={this.handleTabClick}>1:1</a>
</li> </li>
<li className={this.state.tabSelected === "grp" ? "active" : null}> <li className={this.state.tabSelected === "grp" ? "active" : null}>
<a href="#" data-id="grp" onClick={this.handleTabClick}>group</a> <a href="javascript:;" data-id="grp" onClick={this.handleTabClick}>group</a>
</li> </li>
<li className={this.state.tabSelected === "byId" ? "active" : null}> <li className={this.state.tabSelected === "byId" ? "active" : null}>
<a href="#" data-id="byId" onClick={this.handleTabClick}>by id</a> <a href="javascript:;" data-id="byId" onClick={this.handleTabClick}>by id</a>
</li> </li>
</ul> </ul>
{this.state.tabSelected === "grp" ? {this.state.tabSelected === "grp" ?
@@ -2323,7 +2340,7 @@
<input type="text" placeholder="List like email:alice@example.com, tel:17025550003..." <input type="text" placeholder="List like email:alice@example.com, tel:17025550003..."
value={this.state.search} onChange={this.handleSearchChange} value={this.state.search} onChange={this.handleSearchChange}
onKeyPress={this.handleKeyPress} required autoFocus /> onKeyPress={this.handleKeyPress} required autoFocus />
<a href="#" onClick={this.handleSearch}> <a href="javascript:;" onClick={this.handleSearch}>
<i className="material-icons">search</i> <i className="material-icons">search</i>
</a> </a>
</div> </div>
@@ -2728,6 +2745,7 @@
this.props.onTopicTagsUpdate(this.props.topic, tags); this.props.onTopicTagsUpdate(this.props.topic, tags);
} }
}, },
handleContextMenu: function(params) { handleContextMenu: function(params) {
var instance = this; var instance = this;
var topic = Tinode.getTopic(this.props.topic); var topic = Tinode.getTopic(this.props.topic);
@@ -2900,12 +2918,12 @@
</div> </div>
<div className="panel-form-row"> <div className="panel-form-row">
{this.state.sharer ? {this.state.sharer ?
<a href="#" className="flat-button" onClick={this.handleShowAddMembers}> <a href="javascript:;" className="flat-button" onClick={this.handleShowAddMembers}>
<i className="material-icons">person_add</i> Add members <i className="material-icons">person_add</i> Add members
</a> </a>
: null} : null}
{!this.state.owner ? {!this.state.owner ?
<a href="#" className="red flat-button" onClick={this.handleLeave}> <a href="javascript:;" className="red flat-button" onClick={this.handleLeave}>
<i className="material-icons">exit_to_app</i> Leave <i className="material-icons">exit_to_app</i> Leave
</a> </a>
: null} : null}
@@ -2924,7 +2942,7 @@
</div> </div>
: :
<div className="panel-form-row"> <div className="panel-form-row">
<a href="#" className="red flat-button" onClick={this.handleLeave}> <a href="javascript:;" className="red flat-button" onClick={this.handleLeave}>
<i className="material-icons">exit_to_app</i> Leave <i className="material-icons">exit_to_app</i> Leave
</a> </a>
</div> </div>
@@ -3024,7 +3042,7 @@
timestamp={this.props.timestamp} received={this.props.received} /> timestamp={this.props.timestamp} received={this.props.received} />
</p> </p>
<span className="menuTrigger"> <span className="menuTrigger">
<a href="#" onClick={this.handleContextClick}> <a href="javascript:;" onClick={this.handleContextClick}>
<i className="material-icons">expand_more</i> <i className="material-icons">expand_more</i>
</a> </a>
</span> </span>
@@ -3433,7 +3451,7 @@
<div id="topic-view" className={this.props.hideSelf ? 'nodisplay' : null}> <div id="topic-view" className={this.props.hideSelf ? 'nodisplay' : null}>
<div id="topic-caption-panel" className="caption-panel"> <div id="topic-caption-panel" className="caption-panel">
{this.props.displayMobile ? {this.props.displayMobile ?
<a href="#" id="hide-message-view" onClick={this.handleBackNavigation}> <a href="javascript:;" id="hide-message-view" onClick={this.handleBackNavigation}>
<i className="material-icons">arrow_back</i> <i className="material-icons">arrow_back</i>
</a> </a>
: :
@@ -3453,7 +3471,7 @@
<div id="topic-users" /> <div id="topic-users" />
} }
<div> <div>
<a href="#" onClick={this.handleContextClick}> <a href="javascript:;" onClick={this.handleContextClick}>
<i className="material-icons">more_vert</i> <i className="material-icons">more_vert</i>
</a> </a>
</div> </div>
@@ -3599,12 +3617,12 @@
<div id="send-message-panel"> <div id="send-message-panel">
{this.props.disabled ? {this.props.disabled ?
<i className="material-icons disabled">photo</i> : <i className="material-icons disabled">photo</i> :
<a href="#" onClick={function(e) {instance.attachImage.click();}} title="Add image"> <a href="javascript:;" onClick={function(e) {instance.attachImage.click();}} title="Add image">
<i className="material-icons secondary">photo</i> <i className="material-icons secondary">photo</i>
</a>} </a>}
{this.props.disabled ? {this.props.disabled ?
<i className="material-icons disabled">attach_file</i> : <i className="material-icons disabled">attach_file</i> :
<a href="#" onClick={function(e) {instance.attachFile.click();}} title="Attach file"> <a href="javascript:;" onClick={function(e) {instance.attachFile.click();}} title="Attach file">
<i className="material-icons secondary">attach_file</i> <i className="material-icons secondary">attach_file</i>
</a>} </a>}
<textarea id="sendMessage" placeholder={prompt} <textarea id="sendMessage" placeholder={prompt}
@@ -3613,7 +3631,7 @@
autoFocus /> autoFocus />
{this.props.disabled ? {this.props.disabled ?
<i className="material-icons disabled">send</i> : <i className="material-icons disabled">send</i> :
<a href="#" onClick={this.handleSend} title="Send"><i className="material-icons">send</i></a>} <a href="javascript:;" onClick={this.handleSend} title="Send"><i className="material-icons">send</i></a>}
<input type="file" ref={function(ref) {instance.attachFile = ref;}} <input type="file" ref={function(ref) {instance.attachFile = ref;}}
onChange={this.handleAttachFile} style={{display: 'none'}} /> onChange={this.handleAttachFile} style={{display: 'none'}} />
<input type="file" ref={function(ref) {instance.attachImage = ref;}} accept="image/*" <input type="file" ref={function(ref) {instance.attachImage = ref;}} accept="image/*"
@@ -3676,10 +3694,10 @@
<div id="image-preview" ref={function(ref) {instance.container = ref;}} <div id="image-preview" ref={function(ref) {instance.container = ref;}}
onClick={this.props.onClose}> onClick={this.props.onClose}>
<div id="image-preview-caption-panel"> <div id="image-preview-caption-panel">
<a href="#" download={this.props.content.filename}> <a href="javascript:;" download={this.props.content.filename}>
<i className="material-icons">file_download</i> download <i className="material-icons">file_download</i> download
</a> </a>
<a href="#" onClick={this.props.onClose}><i className="material-icons gray">close</i></a> <a href="javascript:;" onClick={this.props.onClose}><i className="material-icons gray">close</i></a>
</div> </div>
<div id="image-preview-container"> <div id="image-preview-container">
<img src={this.props.content.url} style={size} /> <img src={this.props.content.url} style={size} />
@@ -3772,7 +3790,7 @@
} }
}, },
handleHashRoute: function(e) { handleHashRoute: function(e) {
console.log("hashchange", location.hash); console.log("hashchange", parseUrlHash(window.location.hash));
}, },
handleOnline: function(online) { handleOnline: function(online) {
var newState = {liveConnection: online}; var newState = {liveConnection: online};
@@ -3846,6 +3864,7 @@
}, },
handleCredentialsRequest: function(params) { handleCredentialsRequest: function(params) {
window.location.hash = "validation/" + this.state.topicSelected + "/" + params.cred;
this.setState({ this.setState({
sidePanelSelected: 'validation', sidePanelSelected: 'validation',
validation: params.cred validation: params.cred
@@ -3861,6 +3880,7 @@
// Logged in fine, subscribe to 'me' attaching callbacks from the contacts view. // Logged in fine, subscribe to 'me' attaching callbacks from the contacts view.
var me = Tinode.getMeTopic(); var me = Tinode.getMeTopic();
me.onMetaDesc = instance.tnMeMetaDesc; me.onMetaDesc = instance.tnMeMetaDesc;
window.location.hash = "";
instance.setState({ instance.setState({
connected: true, connected: true,
sidePanelSelected: "contacts", sidePanelSelected: "contacts",
@@ -3954,7 +3974,9 @@
handleTopicSelected: function(topicName, unused_index, online, acs) { handleTopicSelected: function(topicName, unused_index, online, acs) {
if (topicName) { if (topicName) {
// Contact selected // Contact selected
location.hash = topicName; if (this.state.topicSelected != topicName) {
window.location.hash = this.state.sidePanelSelected + "/" + topicName;
}
this.setState({ this.setState({
topicSelected: topicName, topicSelected: topicName,
mobilePanel: 'topic-view', mobilePanel: 'topic-view',
@@ -3965,7 +3987,10 @@
infoPanelShowMembers: Tinode.getTopicType(topicName) === "grp" infoPanelShowMembers: Tinode.getTopicType(topicName) === "grp"
}); });
} else { } else {
location.hash = null; if (this.state.topicSelected) {
console.log("topic deleted");
window.history.replaceState({}, "", this.state.sidePanelSelected);
}
// Currently selected contact deleted // Currently selected contact deleted
this.setState({ this.setState({
topicSelected: null, topicSelected: null,
@@ -4014,6 +4039,7 @@
}, },
// User chose a Sign Up menu item. // User chose a Sign Up menu item.
handleNewAccount: function() { handleNewAccount: function() {
window.location.hash = "register";
this.setState({sidePanelSelected: 'register'}); this.setState({sidePanelSelected: 'register'});
}, },
// Actual registration of a new account. // Actual registration of a new account.
@@ -4057,6 +4083,7 @@
// User chose Settings menu item. // User chose Settings menu item.
handleSettings: function() { handleSettings: function() {
window.location.hash = this.state.myUserId ? 'edit' : 'settings';
this.setState({sidePanelSelected: this.state.myUserId ? 'edit-account' : 'settings'}); this.setState({sidePanelSelected: this.state.myUserId ? 'edit-account' : 'settings'});
}, },
// User updated connection parameters - transport and server address // User updated connection parameters - transport and server address
@@ -4069,13 +4096,16 @@
// User clicked Cancel button in Setting or Sign Up panel. // User clicked Cancel button in Setting or Sign Up panel.
handleSidepanelCancel: function() { handleSidepanelCancel: function() {
if (this.state.myUserId) { if (this.state.myUserId) {
window.location.hash = "/" + this.state.topicSelected;
this.setState({sidePanelSelected: 'contacts', errorText: '', errorLevel: null}); this.setState({sidePanelSelected: 'contacts', errorText: '', errorLevel: null});
} else { } else {
window.location.hash = '';
this.setState({sidePanelSelected: 'login', errorText: '', errorLevel: null}); this.setState({sidePanelSelected: 'login', errorText: '', errorLevel: null});
} }
}, },
// User clicked a (+) menu item. // User clicked a (+) menu item.
handleNewTopic: function() { handleNewTopic: function() {
window.location.hash = 'new';
this.setState({sidePanelSelected: 'new-topic'}); this.setState({sidePanelSelected: 'new-topic'});
}, },
// Create of a new topic. New P2P topic requires peer's name. // Create of a new topic. New P2P topic requires peer's name.
@@ -4214,9 +4244,11 @@
}, },
handleShowInfoView: function() { handleShowInfoView: function() {
window.location.hash = addParamToHash(window.location.hash, 'info', true);
this.setState({showInfoPanel: true, infoPanelShowMembers: false}); this.setState({showInfoPanel: true, infoPanelShowMembers: false});
}, },
handleHideInfoView: function() { handleHideInfoView: function() {
window.location.hash = removeParamFromHash(window.location.hash, 'info');
this.setState({showInfoPanel: false, infoPanelShowMembers: false}); this.setState({showInfoPanel: false, infoPanelShowMembers: false});
}, },
handleMemberUpdateRequest: function(topicName, added, removed) { handleMemberUpdateRequest: function(topicName, added, removed) {