2012-12-07 20:52:39 +01:00
|
|
|
var hashchange = (function () {
|
|
|
|
|
|
|
|
var exports = {};
|
|
|
|
|
2013-04-09 19:17:42 +02:00
|
|
|
var expected_hash;
|
2013-04-03 20:44:26 +02:00
|
|
|
var changing_hash = false;
|
2012-12-07 20:52:39 +01:00
|
|
|
|
2013-03-20 00:22:51 +01:00
|
|
|
// Some browsers zealously URI-decode the contents of
|
|
|
|
// window.location.hash. So we hide our URI-encoding
|
|
|
|
// by replacing % with . (like MediaWiki).
|
|
|
|
|
2013-07-05 17:43:56 +02:00
|
|
|
exports.encodeHashComponent = function (str) {
|
2013-03-20 00:22:51 +01:00
|
|
|
return encodeURIComponent(str)
|
|
|
|
.replace(/\./g, '%2E')
|
|
|
|
.replace(/%/g, '.');
|
2013-05-02 16:45:38 +02:00
|
|
|
};
|
2013-03-20 00:22:51 +01:00
|
|
|
|
2017-01-05 23:36:02 +01:00
|
|
|
exports.encode_operand = function (operator, operand) {
|
2017-01-19 03:53:50 +01:00
|
|
|
if ((operator === 'pm-with') || (operator === 'sender')) {
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
var slug = people.emails_to_slug(operand);
|
|
|
|
if (slug) {
|
|
|
|
return slug;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-05 23:36:02 +01:00
|
|
|
return exports.encodeHashComponent(operand);
|
|
|
|
};
|
|
|
|
|
2013-03-20 00:22:51 +01:00
|
|
|
function decodeHashComponent(str) {
|
|
|
|
return decodeURIComponent(str.replace(/\./g, '%'));
|
|
|
|
}
|
|
|
|
|
2017-01-05 23:36:02 +01:00
|
|
|
exports.decode_operand = function (operator, operand) {
|
2017-01-19 03:53:50 +01:00
|
|
|
if ((operator === 'pm-with') || (operator === 'sender')) {
|
Make nicer slugs for "pm-with" narrows.
The slugs for PM-with narrows now have user ids in them, so they
are more resilient to email changes, and they have less escaping
characters and are generally prettier.
Examples:
narrow/pm-with/3-cordelia
narrow/pm-with/3,5-group
The part of the URL that is actionable is the comma-delimited
list of one or more userids.
When we decode the slugs, we only use the part before the dash; the
stuff after the dash is just for humans. If we don't see a number
before the dash, we fall back to the old decoding (which should only
matter during a transition period where folks may have old links).
For group PMS, we always say "group" after the dash. For single PMs,
we use the person's email userid, since it's usually fairly concise
and not noisy for a URL. We may tinker with this later.
Basically, the heart of this change is these two new methods:
people.emails_to_slug
people.slug_to_emails
And then we unify the encode codepath as follows:
narrow.pm_with_uri ->
hashchange.operators_to_hash ->
hashchange.encode_operand ->
people.emails_to_slug
The decode path didn't really require much modication in this commit,
other than to have hashchange.decode_operand call people.slug_to_emails
for the pm-with case.
2017-01-06 02:00:03 +01:00
|
|
|
var emails = people.slug_to_emails(operand);
|
|
|
|
if (emails) {
|
|
|
|
return emails;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-05 23:36:02 +01:00
|
|
|
return decodeHashComponent(operand);
|
|
|
|
};
|
|
|
|
|
2014-01-29 22:14:00 +01:00
|
|
|
function set_hash(hash) {
|
2014-02-13 05:38:02 +01:00
|
|
|
var location = window.location;
|
|
|
|
|
2014-01-29 22:14:00 +01:00
|
|
|
if (history.pushState) {
|
2014-02-12 21:52:57 +01:00
|
|
|
if (hash === '' || hash.charAt(0) !== '#') {
|
|
|
|
hash = '#' + hash;
|
|
|
|
}
|
2014-02-13 05:38:02 +01:00
|
|
|
|
|
|
|
// IE returns pathname as undefined and missing the leading /
|
|
|
|
var pathname = location.pathname;
|
|
|
|
if (pathname === undefined) {
|
|
|
|
pathname = '/';
|
|
|
|
} else if (pathname === '' || pathname.charAt(0) !== '/') {
|
|
|
|
pathname = '/' + pathname;
|
|
|
|
}
|
|
|
|
|
2014-01-29 22:14:00 +01:00
|
|
|
// Build a full URL to not have same origin problems
|
2014-02-13 05:38:02 +01:00
|
|
|
var url = location.protocol + '//' + location.host + pathname + hash;
|
2014-01-29 22:14:00 +01:00
|
|
|
history.pushState(null, null, url);
|
|
|
|
} else {
|
2014-02-13 05:38:02 +01:00
|
|
|
location.hash = hash;
|
2014-01-29 22:14:00 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-12-07 20:52:39 +01:00
|
|
|
exports.changehash = function (newhash) {
|
2013-04-03 20:44:26 +02:00
|
|
|
if (changing_hash) {
|
|
|
|
return;
|
|
|
|
}
|
2016-06-30 02:39:29 +02:00
|
|
|
$(document).trigger($.Event('zuliphashchange.zulip'));
|
2014-01-29 22:14:00 +01:00
|
|
|
set_hash(newhash);
|
2014-03-13 17:44:43 +01:00
|
|
|
favicon.reset();
|
2012-12-07 20:52:39 +01:00
|
|
|
};
|
|
|
|
|
2013-05-09 21:12:53 +02:00
|
|
|
// Encodes an operator list into the
|
|
|
|
// corresponding hash: the # component
|
|
|
|
// of the narrow URL
|
|
|
|
exports.operators_to_hash = function (operators) {
|
|
|
|
var hash = '#';
|
|
|
|
|
|
|
|
if (operators !== undefined) {
|
|
|
|
hash = '#narrow';
|
2013-07-30 00:35:44 +02:00
|
|
|
_.each(operators, function (elem) {
|
2014-02-03 19:48:10 +01:00
|
|
|
// Support legacy tuples.
|
2014-02-10 23:32:44 +01:00
|
|
|
var operator = elem.operator;
|
|
|
|
var operand = elem.operand;
|
|
|
|
|
2014-02-11 21:36:59 +01:00
|
|
|
var sign = elem.negated ? '-' : '';
|
|
|
|
hash += '/' + sign + hashchange.encodeHashComponent(operator)
|
2017-01-05 23:36:02 +01:00
|
|
|
+ '/' + hashchange.encode_operand(operator, operand);
|
2013-05-09 21:12:53 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return hash;
|
|
|
|
};
|
|
|
|
|
2012-12-12 19:00:50 +01:00
|
|
|
exports.save_narrow = function (operators) {
|
2013-04-03 20:44:26 +02:00
|
|
|
if (changing_hash) {
|
|
|
|
return;
|
|
|
|
}
|
2013-05-09 21:12:53 +02:00
|
|
|
var new_hash = exports.operators_to_hash(operators);
|
|
|
|
exports.changehash(new_hash);
|
2012-12-12 19:00:50 +01:00
|
|
|
};
|
|
|
|
|
2017-01-05 22:04:33 +01:00
|
|
|
exports.parse_narrow = function (hash) {
|
2016-12-02 17:09:31 +01:00
|
|
|
var i;
|
|
|
|
var operators = [];
|
2012-12-12 19:00:50 +01:00
|
|
|
for (i=1; i<hash.length; i+=2) {
|
|
|
|
// We don't construct URLs with an odd number of components,
|
|
|
|
// but the user might write one.
|
2013-05-02 17:38:29 +02:00
|
|
|
try {
|
|
|
|
var operator = decodeHashComponent(hash[i]);
|
2017-01-05 23:36:02 +01:00
|
|
|
var operand = exports.decode_operand(operator, hash[i+1] || '');
|
2014-02-11 21:36:59 +01:00
|
|
|
var negated = false;
|
|
|
|
if (operator[0] === '-') {
|
|
|
|
negated = true;
|
|
|
|
operator = operator.slice(1);
|
|
|
|
}
|
|
|
|
operators.push({negated: negated, operator: operator, operand: operand});
|
2013-05-02 17:38:29 +02:00
|
|
|
} catch (err) {
|
|
|
|
return undefined;
|
|
|
|
}
|
2012-12-12 19:00:50 +01:00
|
|
|
}
|
2013-05-02 17:38:29 +02:00
|
|
|
return operators;
|
2017-01-05 22:04:33 +01:00
|
|
|
};
|
2013-05-02 17:38:29 +02:00
|
|
|
|
|
|
|
function activate_home_tab() {
|
|
|
|
ui.change_tab_to("#home");
|
|
|
|
narrow.deactivate();
|
2014-03-13 21:14:33 +01:00
|
|
|
floating_recipient_bar.update();
|
2012-12-12 19:00:50 +01:00
|
|
|
}
|
|
|
|
|
2012-12-21 01:04:27 +01:00
|
|
|
// Returns true if this function performed a narrow
|
2014-02-12 20:03:05 +01:00
|
|
|
function do_hashchange(from_reload) {
|
2012-12-07 20:52:39 +01:00
|
|
|
// If window.location.hash changed because our app explicitly
|
|
|
|
// changed it, then we don't need to do anything.
|
|
|
|
// (This function only neds to jump into action if it changed
|
|
|
|
// because e.g. the back button was pressed by the user)
|
2013-04-09 19:17:42 +02:00
|
|
|
//
|
|
|
|
// The second case is for handling the fact that some browsers
|
|
|
|
// automatically convert '#' to '' when you change the hash to '#'.
|
|
|
|
if (window.location.hash === expected_hash ||
|
|
|
|
(expected_hash !== undefined &&
|
|
|
|
window.location.hash.replace(/^#/, '') === '' &&
|
|
|
|
expected_hash.replace(/^#/, '') === '')) {
|
2012-12-21 01:04:27 +01:00
|
|
|
return false;
|
2012-12-07 20:52:39 +01:00
|
|
|
}
|
|
|
|
|
2016-06-30 02:39:29 +02:00
|
|
|
$(document).trigger($.Event('zuliphashchange.zulip'));
|
2013-04-23 21:59:49 +02:00
|
|
|
|
2013-03-20 00:22:51 +01:00
|
|
|
// NB: In Firefox, window.location.hash is URI-decoded.
|
|
|
|
// Even if the URL bar says #%41%42%43%44, the value here will
|
|
|
|
// be #ABCD.
|
2012-12-07 20:52:39 +01:00
|
|
|
var hash = window.location.hash.split("/");
|
2012-12-19 21:19:29 +01:00
|
|
|
switch (hash[0]) {
|
2014-02-12 20:03:53 +01:00
|
|
|
case "#narrow":
|
|
|
|
ui.change_tab_to("#home");
|
2017-01-05 22:04:33 +01:00
|
|
|
var operators = exports.parse_narrow(hash);
|
2014-02-12 20:03:53 +01:00
|
|
|
if (operators === undefined) {
|
|
|
|
// If the narrow URL didn't parse, clear
|
|
|
|
// window.location.hash and send them to the home tab
|
2014-01-29 22:14:00 +01:00
|
|
|
set_hash('');
|
2013-05-02 17:38:29 +02:00
|
|
|
activate_home_tab();
|
2014-02-12 20:03:53 +01:00
|
|
|
return false;
|
|
|
|
}
|
2014-02-12 20:03:05 +01:00
|
|
|
var narrow_opts = {
|
2014-02-12 20:03:53 +01:00
|
|
|
select_first_unread: true,
|
|
|
|
change_hash: false, // already set
|
2017-01-12 00:17:43 +01:00
|
|
|
trigger: 'hash change',
|
2014-02-12 20:03:05 +01:00
|
|
|
};
|
|
|
|
if (from_reload !== undefined && page_params.initial_narrow_pointer !== undefined) {
|
|
|
|
narrow_opts.from_reload = true;
|
2014-03-03 23:37:22 +01:00
|
|
|
narrow_opts.first_unread_from_server = true;
|
2014-02-12 20:03:05 +01:00
|
|
|
}
|
|
|
|
narrow.activate(operators, narrow_opts);
|
2014-03-13 21:14:33 +01:00
|
|
|
floating_recipient_bar.update();
|
2014-02-12 20:03:53 +01:00
|
|
|
return true;
|
|
|
|
case "":
|
|
|
|
case "#":
|
|
|
|
activate_home_tab();
|
|
|
|
break;
|
2017-03-09 00:20:22 +01:00
|
|
|
case "#streams":
|
|
|
|
ui.change_tab_to("#streams");
|
2014-02-12 20:03:53 +01:00
|
|
|
break;
|
2017-02-22 02:34:05 +01:00
|
|
|
case "#drafts":
|
|
|
|
ui.change_tab_to("#drafts");
|
|
|
|
break;
|
2014-02-12 20:03:53 +01:00
|
|
|
case "#administration":
|
|
|
|
ui.change_tab_to("#administration");
|
|
|
|
break;
|
|
|
|
case "#settings":
|
|
|
|
ui.change_tab_to("#settings");
|
|
|
|
break;
|
2012-12-07 20:52:39 +01:00
|
|
|
}
|
2012-12-21 01:04:27 +01:00
|
|
|
return false;
|
2012-12-07 20:52:39 +01:00
|
|
|
}
|
|
|
|
|
2016-11-07 22:39:25 +01:00
|
|
|
// -- -- -- -- -- -- READ THIS BEFORE TOUCHING ANYTHING BELOW -- -- -- -- -- -- //
|
|
|
|
// HOW THE HASH CHANGE MECHANISM WORKS:
|
|
|
|
// When going from a normal view (eg. `narrow/is/private`) to a settings panel
|
|
|
|
// (eg. `settings/your-bots`) it should trigger the `should_ignore` function and
|
|
|
|
// return `true` for the current state -- we want to ignore hash changes from
|
2016-12-03 01:12:52 +01:00
|
|
|
// within the settings page. The previous hash however should return `false` as it
|
|
|
|
// was outside of the scope of settings.
|
2017-03-13 19:42:10 +01:00
|
|
|
// there is then an `exit_modal` function that allows the hash to change exactly
|
2016-11-07 22:39:25 +01:00
|
|
|
// once without triggering any events. This allows the hash to reset back from
|
|
|
|
// a settings page to the previous view available before the settings page
|
|
|
|
// (eg. narrow/is/private). This saves the state, scroll position, and makes the
|
|
|
|
// hash change functionally inert.
|
|
|
|
// -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - -- //
|
|
|
|
var ignore = {
|
|
|
|
flag: false,
|
2017-01-12 00:17:43 +01:00
|
|
|
prev: null,
|
2017-02-02 01:51:48 +01:00
|
|
|
old_hash: typeof window !== "undefined" ? window.location.hash : "#",
|
2016-12-03 01:12:52 +01:00
|
|
|
group: null,
|
2016-11-07 22:39:25 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
function get_main_hash(hash) {
|
2016-12-03 01:12:52 +01:00
|
|
|
return hash ? hash.replace(/^#/, "").split(/\//)[0] : "";
|
2016-11-07 22:39:25 +01:00
|
|
|
}
|
|
|
|
|
2017-02-21 18:13:32 +01:00
|
|
|
function get_hash_components() {
|
|
|
|
var hash = window.location.hash.split(/\//);
|
|
|
|
|
|
|
|
return {
|
|
|
|
base: hash.shift(),
|
|
|
|
arguments: hash,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-12-03 01:12:52 +01:00
|
|
|
// different groups require different reloads. The grouped elements don't
|
|
|
|
// require a reload or overlay change to run.
|
|
|
|
var get_hash_group = (function () {
|
|
|
|
var groups = [
|
2017-03-09 00:20:22 +01:00
|
|
|
["streams"],
|
2016-12-03 01:12:52 +01:00
|
|
|
["settings", "administration"],
|
|
|
|
];
|
|
|
|
|
|
|
|
return function (value) {
|
|
|
|
var idx = null;
|
|
|
|
|
|
|
|
_.find(groups, function (o, i) {
|
|
|
|
if (o.indexOf(value) !== -1) {
|
|
|
|
idx = i;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
return idx;
|
|
|
|
};
|
|
|
|
}());
|
|
|
|
|
2016-12-05 07:02:18 +01:00
|
|
|
function should_ignore(hash) {
|
2017-03-09 00:20:22 +01:00
|
|
|
// Hash changes within this list are overlaws and should not unnarrow (etc.)
|
|
|
|
var ignore_list = ["streams", "drafts", "settings", "administration"];
|
2016-11-07 22:39:25 +01:00
|
|
|
var main_hash = get_main_hash(hash);
|
|
|
|
|
|
|
|
return (ignore_list.indexOf(main_hash) > -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
function hashchanged(from_reload, e) {
|
|
|
|
var old_hash;
|
|
|
|
if (e) {
|
2017-02-02 01:51:48 +01:00
|
|
|
old_hash = "#" + (e.oldURL || ignore.old_hash).split(/#/).slice(1).join("");
|
2016-11-07 22:39:25 +01:00
|
|
|
ignore.last = old_hash;
|
2017-02-02 01:51:48 +01:00
|
|
|
ignore.old_hash = window.location.hash;
|
2016-11-07 22:39:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var base = get_main_hash(window.location.hash);
|
2016-12-03 01:12:52 +01:00
|
|
|
|
2016-11-07 22:39:25 +01:00
|
|
|
if (should_ignore(window.location.hash)) {
|
2016-12-03 01:12:52 +01:00
|
|
|
// if the old has was a standard non-ignore hash OR the ignore hash
|
|
|
|
// base has changed, something needs to run again.
|
|
|
|
|
|
|
|
if (!should_ignore(old_hash || "#") || ignore.group !== get_hash_group(base)) {
|
|
|
|
if (ignore.group !== get_hash_group(base)) {
|
|
|
|
exports.close_modals();
|
|
|
|
}
|
|
|
|
|
|
|
|
// now only if the previous one should not have been ignored.
|
|
|
|
if (!should_ignore(old_hash || "#")) {
|
|
|
|
ignore.prev = old_hash;
|
|
|
|
}
|
|
|
|
|
2017-03-09 00:20:22 +01:00
|
|
|
if (base === "streams") {
|
2017-02-21 18:13:32 +01:00
|
|
|
subs.launch(get_hash_components());
|
2017-02-22 02:34:05 +01:00
|
|
|
} else if (base === "drafts") {
|
|
|
|
drafts.launch();
|
2016-12-03 01:12:52 +01:00
|
|
|
} else if (/settings|administration/.test(base)) {
|
|
|
|
settings.setup_page();
|
|
|
|
admin.setup_page();
|
2016-11-01 22:32:10 +01:00
|
|
|
}
|
|
|
|
|
2016-12-03 01:12:52 +01:00
|
|
|
ignore.group = get_hash_group(base);
|
2017-02-21 18:13:32 +01:00
|
|
|
} else {
|
|
|
|
subs.change_state(get_hash_components());
|
2016-11-07 22:39:25 +01:00
|
|
|
}
|
|
|
|
} else if (!should_ignore(window.location.hash) && !ignore.flag) {
|
2016-12-03 01:12:52 +01:00
|
|
|
exports.close_modals();
|
2016-11-07 22:39:25 +01:00
|
|
|
changing_hash = true;
|
|
|
|
var ret = do_hashchange(from_reload);
|
|
|
|
changing_hash = false;
|
|
|
|
return ret;
|
|
|
|
// once we unignore the hash, we have to set the hash back to what it was
|
|
|
|
// originally (eg. '#narrow/stream/Denmark' instead of '#settings'). We
|
|
|
|
// therefore ignore the hash change once more while we change it back for
|
|
|
|
// no iterruptions.
|
|
|
|
} else if (ignore.flag) {
|
|
|
|
ignore.flag = false;
|
|
|
|
}
|
2013-04-03 20:44:26 +02:00
|
|
|
}
|
|
|
|
|
2012-12-07 20:52:39 +01:00
|
|
|
exports.initialize = function () {
|
2013-04-03 18:55:28 +02:00
|
|
|
// jQuery doesn't have a hashchange event, so we manually wrap
|
|
|
|
// our event handler
|
2016-11-07 22:39:25 +01:00
|
|
|
window.onhashchange = blueslip.wrap_function(function (e) {
|
|
|
|
hashchanged(false, e);
|
2016-10-23 07:07:09 +02:00
|
|
|
});
|
2014-02-12 20:03:05 +01:00
|
|
|
hashchanged(true);
|
2012-12-07 20:52:39 +01:00
|
|
|
};
|
|
|
|
|
2016-12-03 01:12:52 +01:00
|
|
|
exports.close_modals = function () {
|
|
|
|
$("[data-overlay]").removeClass("show");
|
|
|
|
};
|
|
|
|
|
2017-03-13 19:42:10 +01:00
|
|
|
exports.exit_modal = function (callback) {
|
2016-11-07 22:39:25 +01:00
|
|
|
if (should_ignore(window.location.hash)) {
|
2016-12-14 23:25:49 +01:00
|
|
|
ui.blur_active_element();
|
2016-11-07 22:39:25 +01:00
|
|
|
ignore.flag = true;
|
|
|
|
window.location.hash = ignore.prev || "#";
|
|
|
|
if (typeof callback === "function") {
|
|
|
|
callback();
|
|
|
|
}
|
2016-12-03 01:12:52 +01:00
|
|
|
|
|
|
|
exports.close_modals();
|
2016-11-07 22:39:25 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-12-07 20:52:39 +01:00
|
|
|
return exports;
|
|
|
|
|
|
|
|
}());
|
2013-11-26 16:39:58 +01:00
|
|
|
if (typeof module !== 'undefined') {
|
|
|
|
module.exports = hashchange;
|
|
|
|
}
|