2019-11-02 00:06:25 +01:00
|
|
|
const render_stream_privacy = require('../templates/stream_privacy.hbs');
|
|
|
|
const render_stream_sidebar_row = require('../templates/stream_sidebar_row.hbs');
|
2020-02-01 04:54:53 +01:00
|
|
|
const IntDict = require('./int_dict').IntDict;
|
2019-02-08 11:56:33 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let has_scrolled = false;
|
2018-07-06 20:23:20 +02:00
|
|
|
|
2017-06-14 00:03:00 +02:00
|
|
|
exports.update_count_in_dom = function (unread_count_elem, count) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const count_span = unread_count_elem.find('.count');
|
|
|
|
const value_span = count_span.find('.value');
|
2017-01-15 17:02:29 +01:00
|
|
|
|
|
|
|
if (count === 0) {
|
|
|
|
count_span.hide();
|
|
|
|
if (count_span.parent().hasClass("subscription_block")) {
|
|
|
|
count_span.parent(".subscription_block").removeClass("stream-with-count");
|
|
|
|
}
|
|
|
|
value_span.text('');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
count_span.show();
|
|
|
|
|
|
|
|
if (count_span.parent().hasClass("subscription_block")) {
|
|
|
|
count_span.parent(".subscription_block").addClass("stream-with-count");
|
|
|
|
}
|
|
|
|
value_span.text(count);
|
2017-06-14 00:03:00 +02:00
|
|
|
};
|
|
|
|
|
2017-01-15 17:02:29 +01:00
|
|
|
|
2016-11-11 14:20:19 +01:00
|
|
|
exports.stream_sidebar = (function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const self = {};
|
2016-11-11 14:20:19 +01:00
|
|
|
|
2020-02-01 04:54:53 +01:00
|
|
|
self.rows = new IntDict(); // stream id -> row widget
|
2016-11-11 14:20:19 +01:00
|
|
|
|
|
|
|
self.set_row = function (stream_id, widget) {
|
|
|
|
self.rows.set(stream_id, widget);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.get_row = function (stream_id) {
|
|
|
|
return self.rows.get(stream_id);
|
|
|
|
};
|
|
|
|
|
|
|
|
self.has_row_for = function (stream_id) {
|
|
|
|
return self.rows.has(stream_id);
|
|
|
|
};
|
|
|
|
|
2016-11-11 15:09:08 +01:00
|
|
|
self.remove_row = function (stream_id) {
|
2017-04-18 19:59:35 +02:00
|
|
|
// This only removes the row from our data structure.
|
|
|
|
// Our caller should use build_stream_list() to re-draw
|
|
|
|
// the sidebar, so that we don't have to deal with edge
|
|
|
|
// cases like removing the last pinned stream (and removing
|
|
|
|
// the divider).
|
2017-04-10 22:09:54 +02:00
|
|
|
|
2020-02-03 07:41:38 +01:00
|
|
|
self.rows.delete(stream_id);
|
2016-11-11 15:09:08 +01:00
|
|
|
};
|
|
|
|
|
2016-11-11 14:20:19 +01:00
|
|
|
return self;
|
|
|
|
}());
|
|
|
|
|
2017-04-18 17:08:59 +02:00
|
|
|
function get_search_term() {
|
2019-11-02 00:06:25 +01:00
|
|
|
const search_box = $(".stream-list-filter");
|
|
|
|
const search_term = search_box.expectOne().val().trim();
|
2017-04-18 17:08:59 +02:00
|
|
|
return search_term;
|
|
|
|
}
|
|
|
|
|
2017-02-17 18:45:14 +01:00
|
|
|
exports.remove_sidebar_row = function (stream_id) {
|
|
|
|
exports.stream_sidebar.remove_row(stream_id);
|
2017-04-18 19:59:35 +02:00
|
|
|
exports.build_stream_list();
|
2018-04-24 16:59:01 +02:00
|
|
|
exports.stream_cursor.redraw();
|
2017-02-17 18:45:14 +01:00
|
|
|
};
|
|
|
|
|
Clean up startup code for streams.
The startup code in subs.js used to intermingle data
stuff and UI stuff in a loop inside a called function,
which made the code hard to reason about.
Now there is a clear separation of concerns, with these methods
being called in succession:
stream_data.initialize_from_page_params();
stream_list.create_initial_sidebar_rows();
The first method was mostly extracted from subs.js, but I simplified
some things, like not needing to make a copy of the hashes
we were passed in, plus I now garbage collect email_dict. Also,
the code path that initialize_from_page_params() mostly replaces
used to call create_sub(), which fired a trigger, but now it
just does data stuff.
Once the data structure is built up, it's a very simple matter
to build the initial sidebar rows, and that's what the second
method does.
2016-10-17 19:34:58 +02:00
|
|
|
exports.create_initial_sidebar_rows = function () {
|
|
|
|
// This code is slightly opaque, but it ends up building
|
|
|
|
// up list items and attaching them to the "sub" data
|
|
|
|
// structures that are kept in stream_data.js.
|
2019-11-02 00:06:25 +01:00
|
|
|
const subs = stream_data.subscribed_subs();
|
Clean up startup code for streams.
The startup code in subs.js used to intermingle data
stuff and UI stuff in a loop inside a called function,
which made the code hard to reason about.
Now there is a clear separation of concerns, with these methods
being called in succession:
stream_data.initialize_from_page_params();
stream_list.create_initial_sidebar_rows();
The first method was mostly extracted from subs.js, but I simplified
some things, like not needing to make a copy of the hashes
we were passed in, plus I now garbage collect email_dict. Also,
the code path that initialize_from_page_params() mostly replaces
used to call create_sub(), which fired a trigger, but now it
just does data stuff.
Once the data structure is built up, it's a very simple matter
to build the initial sidebar rows, and that's what the second
method does.
2016-10-17 19:34:58 +02:00
|
|
|
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const sub of subs) {
|
Clean up startup code for streams.
The startup code in subs.js used to intermingle data
stuff and UI stuff in a loop inside a called function,
which made the code hard to reason about.
Now there is a clear separation of concerns, with these methods
being called in succession:
stream_data.initialize_from_page_params();
stream_list.create_initial_sidebar_rows();
The first method was mostly extracted from subs.js, but I simplified
some things, like not needing to make a copy of the hashes
we were passed in, plus I now garbage collect email_dict. Also,
the code path that initialize_from_page_params() mostly replaces
used to call create_sub(), which fired a trigger, but now it
just does data stuff.
Once the data structure is built up, it's a very simple matter
to build the initial sidebar rows, and that's what the second
method does.
2016-10-17 19:34:58 +02:00
|
|
|
exports.create_sidebar_row(sub);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
Clean up startup code for streams.
The startup code in subs.js used to intermingle data
stuff and UI stuff in a loop inside a called function,
which made the code hard to reason about.
Now there is a clear separation of concerns, with these methods
being called in succession:
stream_data.initialize_from_page_params();
stream_list.create_initial_sidebar_rows();
The first method was mostly extracted from subs.js, but I simplified
some things, like not needing to make a copy of the hashes
we were passed in, plus I now garbage collect email_dict. Also,
the code path that initialize_from_page_params() mostly replaces
used to call create_sub(), which fired a trigger, but now it
just does data stuff.
Once the data structure is built up, it's a very simple matter
to build the initial sidebar rows, and that's what the second
method does.
2016-10-17 19:34:58 +02:00
|
|
|
};
|
|
|
|
|
2013-11-22 20:05:34 +01:00
|
|
|
exports.build_stream_list = function () {
|
Clean up startup code for streams.
The startup code in subs.js used to intermingle data
stuff and UI stuff in a loop inside a called function,
which made the code hard to reason about.
Now there is a clear separation of concerns, with these methods
being called in succession:
stream_data.initialize_from_page_params();
stream_list.create_initial_sidebar_rows();
The first method was mostly extracted from subs.js, but I simplified
some things, like not needing to make a copy of the hashes
we were passed in, plus I now garbage collect email_dict. Also,
the code path that initialize_from_page_params() mostly replaces
used to call create_sub(), which fired a trigger, but now it
just does data stuff.
Once the data structure is built up, it's a very simple matter
to build the initial sidebar rows, and that's what the second
method does.
2016-10-17 19:34:58 +02:00
|
|
|
// This function assumes we have already created the individual
|
|
|
|
// sidebar rows. Our job here is to build the bigger widget,
|
|
|
|
// which largely is a matter of arranging the individual rows in
|
|
|
|
// the right order.
|
2019-11-02 00:06:25 +01:00
|
|
|
const streams = stream_data.subscribed_streams();
|
2013-05-06 23:36:22 +02:00
|
|
|
if (streams.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-04-18 17:08:59 +02:00
|
|
|
// The main logic to build the list is in stream_sort.js, and
|
|
|
|
// we get three lists of streams (pinned/normal/dormant).
|
2019-12-27 21:45:28 +01:00
|
|
|
const stream_groups = stream_sort.sort_groups(streams, get_search_term());
|
2017-04-18 17:08:59 +02:00
|
|
|
|
|
|
|
if (stream_groups.same_as_before) {
|
|
|
|
return;
|
|
|
|
}
|
2016-06-13 22:06:12 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const parent = $('#stream_filters');
|
|
|
|
const elems = [];
|
2013-05-06 23:36:22 +02:00
|
|
|
|
2016-07-01 07:26:09 +02:00
|
|
|
function add_sidebar_li(stream) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const sub = stream_data.get_sub(stream);
|
|
|
|
const sidebar_row = exports.stream_sidebar.get_row(sub.stream_id);
|
2017-04-18 19:59:35 +02:00
|
|
|
sidebar_row.update_whether_active();
|
2017-07-18 13:30:14 +02:00
|
|
|
elems.push(sidebar_row.get_li());
|
2016-07-01 07:26:09 +02:00
|
|
|
}
|
|
|
|
|
2017-04-18 17:08:59 +02:00
|
|
|
parent.empty();
|
2016-07-01 07:26:09 +02:00
|
|
|
|
2020-02-08 05:56:44 +01:00
|
|
|
stream_groups.pinned_streams.forEach(add_sidebar_li);
|
left-sidebar: Sort pinned streams by lowercase stream name.
The pinned streams were sorted in alphabetic order (i.e. Verona appears
before devel). The reason is that after we plucked pinned streams out from
stream_data.subscribed_streams(), we didn't sort them again, so they
remained in the alphabetic order used in stream_data.
However, we did sort unpinned streams explicitly by using custom compare
function in stream_list.js (by default sort by lowercase stream name,
but when there are more than 40 subscribed streams, sort active streams
first). That's why this issue only relates to pinned streams.
Changes were made to sort pinned streams by lowercase stream name, always,
whether they are active or not (different from unpinned streams).
Tests were added to ensure this overall sort order is correct, i.e.
1. pinned streams are always sorted by lowercase stream name.
2. pinned streams are always before unpinned streams.
3. unpinned streams are sorted by lowercase stream name, if there are more
than 40 subscribed streams, sort active streams at the top, among active
and inactive streams, still sorted by lowercase stream name.
Fixes #3701
2017-02-19 15:24:27 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const any_pinned_streams = stream_groups.pinned_streams.length > 0;
|
|
|
|
const any_normal_streams = stream_groups.normal_streams.length > 0;
|
|
|
|
const any_dormant_streams = stream_groups.dormant_streams.length > 0;
|
2017-11-13 17:17:34 +01:00
|
|
|
|
|
|
|
if (any_pinned_streams && (any_normal_streams || any_dormant_streams)) {
|
2017-07-18 13:30:14 +02:00
|
|
|
elems.push('<hr class="stream-split">');
|
2017-04-18 17:08:59 +02:00
|
|
|
}
|
2013-05-06 02:54:15 +02:00
|
|
|
|
2020-02-08 05:56:44 +01:00
|
|
|
stream_groups.normal_streams.forEach(add_sidebar_li);
|
2016-07-01 07:26:09 +02:00
|
|
|
|
2017-11-13 17:17:34 +01:00
|
|
|
if (any_dormant_streams && any_normal_streams) {
|
2017-07-18 13:30:14 +02:00
|
|
|
elems.push('<hr class="stream-split">');
|
2017-04-19 18:18:29 +02:00
|
|
|
}
|
2013-05-06 02:54:15 +02:00
|
|
|
|
2020-02-08 05:56:44 +01:00
|
|
|
stream_groups.dormant_streams.forEach(add_sidebar_li);
|
2016-07-01 07:26:09 +02:00
|
|
|
|
2017-06-02 01:04:14 +02:00
|
|
|
parent.append(elems);
|
2013-05-06 02:54:15 +02:00
|
|
|
};
|
|
|
|
|
2017-05-13 17:41:10 +02:00
|
|
|
exports.get_stream_li = function (stream_id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const row = exports.stream_sidebar.get_row(stream_id);
|
2017-06-03 00:13:57 +02:00
|
|
|
if (!row) {
|
2017-06-03 14:16:45 +02:00
|
|
|
// Not all streams are in the sidebar, so we don't report
|
|
|
|
// an error here, and it's up for the caller to error if
|
|
|
|
// they expected otherwise.
|
2017-06-03 00:13:57 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const li = row.get_li();
|
2017-06-03 00:13:57 +02:00
|
|
|
if (!li) {
|
|
|
|
blueslip.error('Cannot find li for id ' + stream_id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (li.length > 1) {
|
|
|
|
blueslip.error('stream_li has too many elements for ' + stream_id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return li;
|
2017-05-13 16:56:29 +02:00
|
|
|
};
|
|
|
|
|
2019-12-30 12:51:16 +01:00
|
|
|
function stream_id_for_elt(elt) {
|
|
|
|
return parseInt(elt.attr('data-stream-id'), 10);
|
|
|
|
}
|
|
|
|
|
2018-09-10 14:52:58 +02:00
|
|
|
exports.zoom_in_topics = function (options) {
|
|
|
|
// This only does stream-related tasks related to zooming
|
|
|
|
// in to more topics, which is basically hiding all the
|
|
|
|
// other streams.
|
|
|
|
|
2015-12-11 08:24:00 +01:00
|
|
|
$("#streams_list").expectOne().removeClass("zoom-out").addClass("zoom-in");
|
Rewrite topic zoom to fix bugs and make cleaner.
In the first cut at topic zoom, I was re-rendering the
streams list, but this created glitches with orphaned
list items. The reproducible bug was that unread counts
on unshown streams weren't updating.
In the new approach, I keep the elements more permanent, and
I just hide and show them as needed, either through jQuery
show/hide or permanent CSS selectors.
I got rid of toggle_zoom(), so that we just explicitly zoom
in and zoom out in all situations. In particular, when we
narrow, it's more clear now that only stay zoomed in when
we're narrowing to the same stream as before (including topic
narrows within that stream).
When you zoom in, the number of topics is no longer limited
to 30, since that was kind of arbitrary anyway. (In practice,
the number of topics is usually well under 30, anyway, due to
the way we track them on the client.)
(imported from commit 5b6c143dee9ba9fe557d8cc36335ff28efb4b0de)
2013-11-26 23:06:39 +01:00
|
|
|
|
2016-07-01 07:26:09 +02:00
|
|
|
// Hide stream list titles and pinned stream splitter
|
|
|
|
$(".stream-filters-label").each(function () {
|
|
|
|
$(this).hide();
|
|
|
|
});
|
2017-04-18 19:59:35 +02:00
|
|
|
$(".stream-split").each(function () {
|
2016-07-01 07:26:09 +02:00
|
|
|
$(this).hide();
|
|
|
|
});
|
|
|
|
|
Rewrite topic zoom to fix bugs and make cleaner.
In the first cut at topic zoom, I was re-rendering the
streams list, but this created glitches with orphaned
list items. The reproducible bug was that unread counts
on unshown streams weren't updating.
In the new approach, I keep the elements more permanent, and
I just hide and show them as needed, either through jQuery
show/hide or permanent CSS selectors.
I got rid of toggle_zoom(), so that we just explicitly zoom
in and zoom out in all situations. In particular, when we
narrow, it's more clear now that only stay zoomed in when
we're narrowing to the same stream as before (including topic
narrows within that stream).
When you zoom in, the number of topics is no longer limited
to 30, since that was kind of arbitrary anyway. (In practice,
the number of topics is usually well under 30, anyway, due to
the way we track them on the client.)
(imported from commit 5b6c143dee9ba9fe557d8cc36335ff28efb4b0de)
2013-11-26 23:06:39 +01:00
|
|
|
$("#stream_filters li.narrow-filter").each(function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const elt = $(this);
|
2019-12-30 12:51:16 +01:00
|
|
|
const stream_id = options.stream_id;
|
Rewrite topic zoom to fix bugs and make cleaner.
In the first cut at topic zoom, I was re-rendering the
streams list, but this created glitches with orphaned
list items. The reproducible bug was that unread counts
on unshown streams weren't updating.
In the new approach, I keep the elements more permanent, and
I just hide and show them as needed, either through jQuery
show/hide or permanent CSS selectors.
I got rid of toggle_zoom(), so that we just explicitly zoom
in and zoom out in all situations. In particular, when we
narrow, it's more clear now that only stay zoomed in when
we're narrowing to the same stream as before (including topic
narrows within that stream).
When you zoom in, the number of topics is no longer limited
to 30, since that was kind of arbitrary anyway. (In practice,
the number of topics is usually well under 30, anyway, due to
the way we track them on the client.)
(imported from commit 5b6c143dee9ba9fe557d8cc36335ff28efb4b0de)
2013-11-26 23:06:39 +01:00
|
|
|
|
2019-12-30 12:51:16 +01:00
|
|
|
if (stream_id_for_elt(elt) === stream_id) {
|
Rewrite topic zoom to fix bugs and make cleaner.
In the first cut at topic zoom, I was re-rendering the
streams list, but this created glitches with orphaned
list items. The reproducible bug was that unread counts
on unshown streams weren't updating.
In the new approach, I keep the elements more permanent, and
I just hide and show them as needed, either through jQuery
show/hide or permanent CSS selectors.
I got rid of toggle_zoom(), so that we just explicitly zoom
in and zoom out in all situations. In particular, when we
narrow, it's more clear now that only stay zoomed in when
we're narrowing to the same stream as before (including topic
narrows within that stream).
When you zoom in, the number of topics is no longer limited
to 30, since that was kind of arbitrary anyway. (In practice,
the number of topics is usually well under 30, anyway, due to
the way we track them on the client.)
(imported from commit 5b6c143dee9ba9fe557d8cc36335ff28efb4b0de)
2013-11-26 23:06:39 +01:00
|
|
|
elt.show();
|
|
|
|
} else {
|
|
|
|
elt.hide();
|
|
|
|
}
|
|
|
|
});
|
2018-09-10 14:52:58 +02:00
|
|
|
};
|
2017-08-10 12:04:01 +02:00
|
|
|
|
2018-09-10 14:52:58 +02:00
|
|
|
exports.zoom_out_topics = function () {
|
2016-07-01 07:26:09 +02:00
|
|
|
// Show stream list titles and pinned stream splitter
|
|
|
|
$(".stream-filters-label").each(function () {
|
|
|
|
$(this).show();
|
|
|
|
});
|
2017-04-18 19:59:35 +02:00
|
|
|
$(".stream-split").each(function () {
|
2016-07-01 07:26:09 +02:00
|
|
|
$(this).show();
|
|
|
|
});
|
|
|
|
|
2015-12-11 08:24:00 +01:00
|
|
|
$("#streams_list").expectOne().removeClass("zoom-in").addClass("zoom-out");
|
Rewrite topic zoom to fix bugs and make cleaner.
In the first cut at topic zoom, I was re-rendering the
streams list, but this created glitches with orphaned
list items. The reproducible bug was that unread counts
on unshown streams weren't updating.
In the new approach, I keep the elements more permanent, and
I just hide and show them as needed, either through jQuery
show/hide or permanent CSS selectors.
I got rid of toggle_zoom(), so that we just explicitly zoom
in and zoom out in all situations. In particular, when we
narrow, it's more clear now that only stay zoomed in when
we're narrowing to the same stream as before (including topic
narrows within that stream).
When you zoom in, the number of topics is no longer limited
to 30, since that was kind of arbitrary anyway. (In practice,
the number of topics is usually well under 30, anyway, due to
the way we track them on the client.)
(imported from commit 5b6c143dee9ba9fe557d8cc36335ff28efb4b0de)
2013-11-26 23:06:39 +01:00
|
|
|
$("#stream_filters li.narrow-filter").show();
|
2017-08-11 00:28:16 +02:00
|
|
|
};
|
Rewrite topic zoom to fix bugs and make cleaner.
In the first cut at topic zoom, I was re-rendering the
streams list, but this created glitches with orphaned
list items. The reproducible bug was that unread counts
on unshown streams weren't updating.
In the new approach, I keep the elements more permanent, and
I just hide and show them as needed, either through jQuery
show/hide or permanent CSS selectors.
I got rid of toggle_zoom(), so that we just explicitly zoom
in and zoom out in all situations. In particular, when we
narrow, it's more clear now that only stay zoomed in when
we're narrowing to the same stream as before (including topic
narrows within that stream).
When you zoom in, the number of topics is no longer limited
to 30, since that was kind of arbitrary anyway. (In practice,
the number of topics is usually well under 30, anyway, due to
the way we track them on the client.)
(imported from commit 5b6c143dee9ba9fe557d8cc36335ff28efb4b0de)
2013-11-26 23:06:39 +01:00
|
|
|
|
2017-05-13 17:41:10 +02:00
|
|
|
exports.set_in_home_view = function (stream_id, in_home) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const li = exports.get_stream_li(stream_id);
|
2017-06-03 00:13:57 +02:00
|
|
|
if (!li) {
|
|
|
|
blueslip.error('passed in bad stream id ' + stream_id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-06-12 00:07:35 +02:00
|
|
|
if (in_home) {
|
|
|
|
li.removeClass("out_of_home_view");
|
|
|
|
} else {
|
|
|
|
li.addClass("out_of_home_view");
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2016-11-11 02:39:22 +01:00
|
|
|
function build_stream_sidebar_li(sub) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const name = sub.name;
|
|
|
|
const args = {
|
2018-05-06 21:43:17 +02:00
|
|
|
name: name,
|
|
|
|
id: sub.stream_id,
|
2018-12-14 19:18:24 +01:00
|
|
|
uri: hash_util.by_stream_uri(sub.stream_id),
|
2019-05-21 09:33:21 +02:00
|
|
|
is_muted: stream_data.is_muted(sub.stream_id) === true,
|
2018-05-06 21:43:17 +02:00
|
|
|
invite_only: sub.invite_only,
|
2019-04-07 20:29:25 +02:00
|
|
|
is_web_public: sub.is_web_public,
|
2019-12-27 21:52:21 +01:00
|
|
|
color: sub.color,
|
2018-05-06 21:43:17 +02:00
|
|
|
pin_to_top: sub.pin_to_top,
|
|
|
|
};
|
2013-08-27 21:05:32 +02:00
|
|
|
args.dark_background = stream_color.get_color_class(args.color);
|
2019-11-02 00:06:25 +01:00
|
|
|
const list_item = $(render_stream_sidebar_row(args));
|
2013-05-06 23:36:22 +02:00
|
|
|
return list_item;
|
2013-05-06 22:18:01 +02:00
|
|
|
}
|
2013-05-06 02:54:15 +02:00
|
|
|
|
2016-11-11 02:39:22 +01:00
|
|
|
function build_stream_sidebar_row(sub) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const self = {};
|
|
|
|
const list_item = build_stream_sidebar_li(sub);
|
2016-11-11 02:39:22 +01:00
|
|
|
|
|
|
|
self.update_whether_active = function () {
|
2019-04-11 08:25:50 +02:00
|
|
|
if (stream_data.is_active(sub) || sub.pin_to_top === true) {
|
2016-11-11 02:39:22 +01:00
|
|
|
list_item.removeClass('inactive_stream');
|
|
|
|
} else {
|
|
|
|
list_item.addClass('inactive_stream');
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
self.get_li = function () {
|
|
|
|
return list_item;
|
|
|
|
};
|
|
|
|
|
2016-11-11 15:09:08 +01:00
|
|
|
self.remove = function () {
|
|
|
|
list_item.remove();
|
|
|
|
};
|
|
|
|
|
2017-01-15 17:09:16 +01:00
|
|
|
|
|
|
|
self.update_unread_count = function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const count = unread.num_unread_for_stream(sub.stream_id);
|
2017-06-14 00:03:00 +02:00
|
|
|
exports.update_count_in_dom(list_item, count);
|
2017-01-15 17:09:16 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
self.update_unread_count();
|
|
|
|
|
2016-11-11 14:20:19 +01:00
|
|
|
exports.stream_sidebar.set_row(sub.stream_id, self);
|
2016-11-11 02:39:22 +01:00
|
|
|
}
|
|
|
|
|
2016-10-17 20:02:32 +02:00
|
|
|
exports.create_sidebar_row = function (sub) {
|
2016-11-11 14:20:19 +01:00
|
|
|
if (exports.stream_sidebar.has_row_for(sub.stream_id)) {
|
2013-10-21 22:26:19 +02:00
|
|
|
// already exists
|
2016-11-11 14:20:19 +01:00
|
|
|
blueslip.warn('Dup try to build sidebar row for stream ' + sub.stream_id);
|
2016-10-17 20:02:32 +02:00
|
|
|
return;
|
|
|
|
}
|
2016-11-11 02:39:22 +01:00
|
|
|
build_stream_sidebar_row(sub);
|
2014-01-16 21:38:40 +01:00
|
|
|
};
|
|
|
|
|
2017-06-14 16:33:30 +02:00
|
|
|
exports.redraw_stream_privacy = function (sub) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const li = exports.get_stream_li(sub.stream_id);
|
2017-06-03 00:13:57 +02:00
|
|
|
if (!li) {
|
2018-01-06 12:25:03 +01:00
|
|
|
// We don't want to raise error here, if we can't find stream in subscription
|
|
|
|
// stream list. Cause we allow org admin to update stream privacy
|
|
|
|
// even if they don't subscribe to public stream.
|
2017-06-03 00:13:57 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const div = li.find('.stream-privacy');
|
|
|
|
const dark_background = stream_color.get_color_class(sub.color);
|
2014-01-17 18:23:39 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const args = {
|
2014-01-17 18:23:39 +01:00
|
|
|
invite_only: sub.invite_only,
|
2017-01-12 00:17:43 +01:00
|
|
|
dark_background: dark_background,
|
2014-01-17 18:23:39 +01:00
|
|
|
};
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const html = render_stream_privacy(args);
|
2014-01-17 18:23:39 +01:00
|
|
|
div.html(html);
|
|
|
|
};
|
|
|
|
|
2017-05-13 19:26:54 +02:00
|
|
|
function set_stream_unread_count(stream_id, count) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const unread_count_elem = exports.get_stream_li(stream_id);
|
2017-06-03 00:13:57 +02:00
|
|
|
if (!unread_count_elem) {
|
2017-06-03 14:16:45 +02:00
|
|
|
// This can happen for legitimate reasons, but we warn
|
|
|
|
// just in case.
|
|
|
|
blueslip.warn('stream id no longer in sidebar: ' + stream_id);
|
2017-06-03 00:13:57 +02:00
|
|
|
return;
|
|
|
|
}
|
2017-06-14 00:03:00 +02:00
|
|
|
exports.update_count_in_dom(unread_count_elem, count);
|
2017-05-13 16:47:45 +02:00
|
|
|
}
|
|
|
|
|
2013-05-06 02:54:15 +02:00
|
|
|
exports.update_streams_sidebar = function () {
|
2020-01-15 15:05:44 +01:00
|
|
|
const finish = blueslip.start_timing('build_stream_list');
|
2013-11-22 20:05:34 +01:00
|
|
|
exports.build_stream_list();
|
2020-01-15 15:05:44 +01:00
|
|
|
finish();
|
2018-04-24 16:59:01 +02:00
|
|
|
exports.stream_cursor.redraw();
|
2018-02-12 22:56:37 +01:00
|
|
|
|
2018-06-04 21:09:11 +02:00
|
|
|
if (!narrow_state.active()) {
|
2013-05-06 02:54:15 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const filter = narrow_state.filter();
|
2017-05-14 20:58:11 +02:00
|
|
|
|
2017-08-11 00:30:23 +02:00
|
|
|
exports.update_stream_sidebar_for_narrow(filter);
|
2013-05-06 02:54:15 +02:00
|
|
|
};
|
|
|
|
|
2013-05-14 03:58:07 +02:00
|
|
|
exports.update_dom_with_unread_counts = function (counts) {
|
|
|
|
// counts.stream_count maps streams to counts
|
2020-02-03 09:26:53 +01:00
|
|
|
for (const [stream_id, count] of counts.stream_count) {
|
2017-05-13 19:26:54 +02:00
|
|
|
set_stream_unread_count(stream_id, count);
|
2020-02-03 09:26:53 +01:00
|
|
|
}
|
2013-05-14 03:58:07 +02:00
|
|
|
};
|
|
|
|
|
2016-12-02 14:06:06 +01:00
|
|
|
exports.rename_stream = function (sub) {
|
|
|
|
// The sub object is expected to already have the updated name
|
2016-11-11 02:39:22 +01:00
|
|
|
build_stream_sidebar_row(sub);
|
2017-05-14 22:48:14 +02:00
|
|
|
exports.update_streams_sidebar(); // big hammer
|
2013-10-21 22:26:19 +02:00
|
|
|
};
|
|
|
|
|
2016-10-26 06:56:10 +02:00
|
|
|
exports.refresh_pinned_or_unpinned_stream = function (sub) {
|
|
|
|
// Pinned/unpinned streams require re-ordering.
|
|
|
|
// We use kind of brute force now, which is probably fine.
|
2016-11-11 02:39:22 +01:00
|
|
|
build_stream_sidebar_row(sub);
|
2016-07-01 07:26:09 +02:00
|
|
|
exports.update_streams_sidebar();
|
2017-05-02 01:22:18 +02:00
|
|
|
|
|
|
|
// Only scroll pinned topics into view. If we're unpinning
|
|
|
|
// a topic, we may be literally trying to get it out of
|
|
|
|
// our sight.
|
|
|
|
if (sub.pin_to_top) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_li = exports.get_stream_li(sub.stream_id);
|
2017-06-03 00:13:57 +02:00
|
|
|
if (!stream_li) {
|
|
|
|
blueslip.error('passed in bad stream id ' + sub.stream_id);
|
|
|
|
return;
|
|
|
|
}
|
2017-08-10 12:32:01 +02:00
|
|
|
exports.scroll_stream_into_view(stream_li);
|
2017-05-02 01:22:18 +02:00
|
|
|
}
|
2016-07-01 07:26:09 +02:00
|
|
|
};
|
|
|
|
|
2017-08-10 18:31:17 +02:00
|
|
|
exports.get_sidebar_stream_topic_info = function (filter) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const result = {
|
2017-08-10 18:31:17 +02:00
|
|
|
stream_id: undefined,
|
|
|
|
topic_selected: false,
|
|
|
|
};
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const op_stream = filter.operands('stream');
|
2017-08-10 18:31:17 +02:00
|
|
|
if (op_stream.length === 0) {
|
|
|
|
return result;
|
2017-05-14 20:30:15 +02:00
|
|
|
}
|
2017-08-10 18:31:17 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_name = op_stream[0];
|
|
|
|
const stream_id = stream_data.get_stream_id(stream_name);
|
2017-08-10 18:31:17 +02:00
|
|
|
|
|
|
|
if (!stream_id) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!stream_data.id_is_subscribed(stream_id)) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
result.stream_id = stream_id;
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const op_topic = filter.operands('topic');
|
2018-11-13 16:23:44 +01:00
|
|
|
result.topic_selected = op_topic.length === 1;
|
2017-08-10 18:31:17 +02:00
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2017-08-12 16:48:34 +02:00
|
|
|
function deselect_stream_items() {
|
2020-01-31 20:15:46 +01:00
|
|
|
$("ul#stream_filters li").removeClass('active-filter');
|
2017-08-12 16:48:34 +02:00
|
|
|
}
|
|
|
|
|
2017-08-11 00:30:23 +02:00
|
|
|
exports.update_stream_sidebar_for_narrow = function (filter) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const info = exports.get_sidebar_stream_topic_info(filter);
|
2017-08-10 18:31:17 +02:00
|
|
|
|
2017-08-12 16:48:34 +02:00
|
|
|
deselect_stream_items();
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_id = info.stream_id;
|
2017-08-10 18:31:17 +02:00
|
|
|
|
|
|
|
if (!stream_id) {
|
2018-09-10 23:15:55 +02:00
|
|
|
topic_zoom.clear_topics();
|
2017-08-10 18:31:17 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_li = exports.get_stream_li(stream_id);
|
2017-08-10 18:31:17 +02:00
|
|
|
|
|
|
|
if (!stream_li) {
|
|
|
|
// It should be the case then when we have a subscribed
|
|
|
|
// stream, there will always be a stream list item
|
|
|
|
// corresponding to that stream in our sidebar. We have
|
|
|
|
// evidence that this assumption breaks down for some users,
|
|
|
|
// but we are not clear why it happens.
|
|
|
|
blueslip.error('No stream_li for subscribed stream ' + stream_id);
|
2018-09-10 23:15:55 +02:00
|
|
|
topic_zoom.clear_topics();
|
2017-08-10 18:31:17 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!info.topic_selected) {
|
|
|
|
stream_li.addClass('active-filter');
|
|
|
|
}
|
|
|
|
|
2017-08-11 00:30:23 +02:00
|
|
|
if (stream_id !== topic_list.active_stream_id()) {
|
2018-09-10 23:15:55 +02:00
|
|
|
topic_zoom.clear_topics();
|
2017-08-11 00:30:23 +02:00
|
|
|
}
|
|
|
|
|
2017-08-10 18:31:17 +02:00
|
|
|
topic_list.rebuild(stream_li, stream_id);
|
|
|
|
|
|
|
|
return stream_li;
|
2017-05-14 20:30:15 +02:00
|
|
|
};
|
|
|
|
|
2017-08-12 16:49:10 +02:00
|
|
|
exports.handle_narrow_activated = function (filter) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_li = exports.update_stream_sidebar_for_narrow(filter);
|
2017-08-12 16:49:10 +02:00
|
|
|
if (stream_li) {
|
|
|
|
exports.scroll_stream_into_view(stream_li);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.handle_narrow_deactivated = function () {
|
|
|
|
deselect_stream_items();
|
2018-09-10 23:15:55 +02:00
|
|
|
topic_zoom.clear_topics();
|
2017-08-12 16:49:10 +02:00
|
|
|
};
|
|
|
|
|
2018-04-15 12:29:17 +02:00
|
|
|
function focus_stream_filter(e) {
|
2018-04-24 16:59:01 +02:00
|
|
|
exports.stream_cursor.reset();
|
2018-04-15 12:29:17 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
}
|
|
|
|
|
|
|
|
function keydown_enter_key() {
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_id = exports.stream_cursor.get_key();
|
2018-04-24 16:59:01 +02:00
|
|
|
|
|
|
|
if (stream_id === undefined) {
|
|
|
|
// This can happen for empty searches, no need to warn.
|
|
|
|
return;
|
|
|
|
}
|
2018-04-15 12:29:17 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const sub = stream_data.get_sub_by_id(stream_id);
|
2018-04-15 12:29:17 +02:00
|
|
|
|
2018-04-24 16:59:01 +02:00
|
|
|
if (sub === undefined) {
|
|
|
|
blueslip.error('Unknown stream_id for search/enter: ' + stream_id);
|
|
|
|
return;
|
2018-04-15 12:29:17 +02:00
|
|
|
}
|
|
|
|
|
2018-04-24 16:59:01 +02:00
|
|
|
exports.clear_and_hide_search();
|
|
|
|
narrow.by('stream', sub.name, {trigger: 'sidebar enter key'});
|
2018-04-15 12:29:17 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function actually_update_streams_for_search() {
|
|
|
|
exports.update_streams_sidebar();
|
|
|
|
resize.resize_page_components();
|
2018-04-24 16:59:01 +02:00
|
|
|
exports.stream_cursor.reset();
|
2018-04-15 12:29:17 +02:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const update_streams_for_search = _.throttle(actually_update_streams_for_search, 50);
|
2018-04-15 12:29:17 +02:00
|
|
|
|
2017-06-02 00:36:28 +02:00
|
|
|
exports.initialize = function () {
|
2019-10-25 09:45:13 +02:00
|
|
|
exports.create_initial_sidebar_rows();
|
2019-03-22 17:26:51 +01:00
|
|
|
|
|
|
|
// We build the stream_list now. It may get re-built again very shortly
|
|
|
|
// when new messages come in, but it's fairly quick.
|
2019-10-25 09:45:13 +02:00
|
|
|
exports.build_stream_list();
|
2019-03-22 17:26:51 +01:00
|
|
|
exports.set_event_handlers();
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.set_event_handlers = function () {
|
2013-07-25 22:48:55 +02:00
|
|
|
$(document).on('subscription_add_done.zulip', function (event) {
|
2016-10-17 20:02:32 +02:00
|
|
|
exports.create_sidebar_row(event.sub);
|
2013-11-22 20:05:34 +01:00
|
|
|
exports.build_stream_list();
|
2018-04-24 16:59:01 +02:00
|
|
|
exports.stream_cursor.redraw();
|
2013-05-06 02:54:15 +02:00
|
|
|
});
|
|
|
|
|
2013-07-25 22:48:55 +02:00
|
|
|
$(document).on('subscription_remove_done.zulip', function (event) {
|
2017-02-17 18:45:14 +01:00
|
|
|
exports.remove_sidebar_row(event.sub.stream_id);
|
2013-05-06 02:54:15 +02:00
|
|
|
});
|
2013-11-22 22:25:31 +01:00
|
|
|
|
2017-02-17 18:45:14 +01:00
|
|
|
|
2013-11-22 22:25:31 +01:00
|
|
|
$('#stream_filters').on('click', 'li .subscription_block', function (e) {
|
|
|
|
if (e.metaKey || e.ctrlKey) {
|
|
|
|
return;
|
|
|
|
}
|
2019-12-30 12:51:16 +01:00
|
|
|
const stream_id = stream_id_for_elt($(e.target).parents('li'));
|
2019-11-02 00:06:25 +01:00
|
|
|
const sub = stream_data.get_sub_by_id(stream_id);
|
2016-12-19 23:38:19 +01:00
|
|
|
popovers.hide_all();
|
2018-04-23 06:02:11 +02:00
|
|
|
narrow.by('stream', sub.name, {trigger: 'sidebar'});
|
2013-11-22 22:25:31 +01:00
|
|
|
|
2018-01-25 20:44:30 +01:00
|
|
|
exports.clear_and_hide_search();
|
|
|
|
|
2013-11-22 22:25:31 +01:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
});
|
|
|
|
|
2018-04-15 12:29:17 +02:00
|
|
|
$('#clear_search_stream_button').on('click', exports.clear_search);
|
2016-06-13 22:06:12 +02:00
|
|
|
|
2018-04-15 12:29:17 +02:00
|
|
|
$("#streams_header").expectOne().click(function (e) {
|
|
|
|
exports.toggle_filter_displayed(e);
|
|
|
|
});
|
2018-04-24 16:59:01 +02:00
|
|
|
|
2018-07-06 20:23:20 +02:00
|
|
|
// check for user scrolls on streams list for first time
|
2019-03-01 01:40:05 +01:00
|
|
|
ui.get_scroll_element($('#stream-filters-container')).on('scroll', function () {
|
2018-07-06 20:23:20 +02:00
|
|
|
has_scrolled = true;
|
|
|
|
// remove listener once user has scrolled
|
|
|
|
$(this).off('scroll');
|
|
|
|
});
|
|
|
|
|
2018-04-24 16:59:01 +02:00
|
|
|
exports.stream_cursor = list_cursor({
|
|
|
|
list: {
|
2018-07-26 22:13:37 +02:00
|
|
|
scroll_container_sel: '#stream-filters-container',
|
2018-04-24 16:59:01 +02:00
|
|
|
find_li: function (opts) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_id = opts.key;
|
|
|
|
const li = exports.get_stream_li(stream_id);
|
2018-04-24 16:59:01 +02:00
|
|
|
return li;
|
|
|
|
},
|
|
|
|
first_key: stream_sort.first_stream_id,
|
|
|
|
prev_key: stream_sort.prev_stream_id,
|
|
|
|
next_key: stream_sort.next_stream_id,
|
|
|
|
},
|
|
|
|
highlight_class: 'highlighted_stream',
|
|
|
|
});
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const $search_input = $('.stream-list-filter').expectOne();
|
2018-04-24 16:59:01 +02:00
|
|
|
|
|
|
|
keydown_util.handle({
|
|
|
|
elem: $search_input,
|
|
|
|
handlers: {
|
|
|
|
enter_key: function () {
|
|
|
|
keydown_enter_key();
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
up_arrow: function () {
|
|
|
|
exports.stream_cursor.prev();
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
down_arrow: function () {
|
|
|
|
exports.stream_cursor.next();
|
|
|
|
return true;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
});
|
|
|
|
|
|
|
|
$search_input.on('click', focus_stream_filter);
|
|
|
|
$search_input.on('focusout', exports.stream_cursor.clear);
|
|
|
|
$search_input.on('input', update_streams_for_search);
|
2018-04-15 12:29:17 +02:00
|
|
|
};
|
2016-06-13 22:06:12 +02:00
|
|
|
|
|
|
|
exports.searching = function () {
|
|
|
|
return $('.stream-list-filter').expectOne().is(':focus');
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.escape_search = function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const filter = $('.stream-list-filter').expectOne();
|
2016-06-13 22:06:12 +02:00
|
|
|
if (filter.val() === '') {
|
|
|
|
exports.clear_and_hide_search();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
filter.val('');
|
|
|
|
update_streams_for_search();
|
|
|
|
};
|
|
|
|
|
2019-02-23 15:10:12 +01:00
|
|
|
exports.clear_search = function (e) {
|
|
|
|
e.stopPropagation();
|
2019-11-02 00:06:25 +01:00
|
|
|
const filter = $('.stream-list-filter').expectOne();
|
2017-04-05 14:18:35 +02:00
|
|
|
if (filter.val() === '') {
|
|
|
|
exports.clear_and_hide_search();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
filter.val('');
|
|
|
|
filter.blur();
|
|
|
|
update_streams_for_search();
|
|
|
|
};
|
|
|
|
|
bug fix: Move stream search out of scroll container.
We want the search widget, when visible, to be
outside the scroll container for the stream list.
One obvious use case is if you start scrolling, and
then realize it might be less effort to search.
Also, for user search, it already worked this way.
We have to add a couple resizing hooks here, but
it's not necessary to change the actual resize
calculation, since we move the section inside
of #streams_header, which is already accounted
for.
The only markup change here is to add
a `stream_search_section` class. I don't
know why we use `notdisplayed` here instead of
jQuery, or what `input-append` is for, but I
considered them outside the scope of this change.
We can also remove some crufty CSS that was
compensating for it being inside the container.
2019-02-09 19:09:56 +01:00
|
|
|
exports.show_search_section = function () {
|
|
|
|
$('.stream_search_section').expectOne().removeClass('notdisplayed');
|
|
|
|
resize.resize_stream_filters_container();
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.hide_search_section = function () {
|
|
|
|
$('.stream_search_section').expectOne().addClass('notdisplayed');
|
|
|
|
resize.resize_stream_filters_container();
|
|
|
|
};
|
|
|
|
|
2016-06-13 22:06:12 +02:00
|
|
|
exports.initiate_search = function () {
|
bug fix: Move stream search out of scroll container.
We want the search widget, when visible, to be
outside the scroll container for the stream list.
One obvious use case is if you start scrolling, and
then realize it might be less effort to search.
Also, for user search, it already worked this way.
We have to add a couple resizing hooks here, but
it's not necessary to change the actual resize
calculation, since we move the section inside
of #streams_header, which is already accounted
for.
The only markup change here is to add
a `stream_search_section` class. I don't
know why we use `notdisplayed` here instead of
jQuery, or what `input-append` is for, but I
considered them outside the scope of this change.
We can also remove some crufty CSS that was
compensating for it being inside the container.
2019-02-09 19:09:56 +01:00
|
|
|
exports.show_search_section();
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const filter = $('.stream-list-filter').expectOne();
|
bug fix: Move stream search out of scroll container.
We want the search widget, when visible, to be
outside the scroll container for the stream list.
One obvious use case is if you start scrolling, and
then realize it might be less effort to search.
Also, for user search, it already worked this way.
We have to add a couple resizing hooks here, but
it's not necessary to change the actual resize
calculation, since we move the section inside
of #streams_header, which is already accounted
for.
The only markup change here is to add
a `stream_search_section` class. I don't
know why we use `notdisplayed` here instead of
jQuery, or what `input-append` is for, but I
considered them outside the scope of this change.
We can also remove some crufty CSS that was
compensating for it being inside the container.
2019-02-09 19:09:56 +01:00
|
|
|
|
2017-10-12 05:07:12 +02:00
|
|
|
if (!$(".app-main .column-left").hasClass("expanded")) {
|
|
|
|
popovers.hide_all();
|
|
|
|
stream_popover.show_streamlist_sidebar();
|
|
|
|
}
|
|
|
|
filter.focus();
|
2018-02-12 22:56:37 +01:00
|
|
|
|
2018-04-24 16:59:01 +02:00
|
|
|
exports.stream_cursor.reset();
|
2016-06-13 22:06:12 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
exports.clear_and_hide_search = function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const filter = $('.stream-list-filter');
|
2016-06-13 22:06:12 +02:00
|
|
|
if (filter.val() !== '') {
|
|
|
|
filter.val('');
|
|
|
|
update_streams_for_search();
|
|
|
|
}
|
2018-04-24 16:59:01 +02:00
|
|
|
exports.stream_cursor.clear();
|
2016-06-13 22:06:12 +02:00
|
|
|
filter.blur();
|
bug fix: Move stream search out of scroll container.
We want the search widget, when visible, to be
outside the scroll container for the stream list.
One obvious use case is if you start scrolling, and
then realize it might be less effort to search.
Also, for user search, it already worked this way.
We have to add a couple resizing hooks here, but
it's not necessary to change the actual resize
calculation, since we move the section inside
of #streams_header, which is already accounted
for.
The only markup change here is to add
a `stream_search_section` class. I don't
know why we use `notdisplayed` here instead of
jQuery, or what `input-append` is for, but I
considered them outside the scope of this change.
We can also remove some crufty CSS that was
compensating for it being inside the container.
2019-02-09 19:09:56 +01:00
|
|
|
|
|
|
|
exports.hide_search_section();
|
2016-06-13 22:06:12 +02:00
|
|
|
};
|
|
|
|
|
2017-09-09 15:00:00 +02:00
|
|
|
exports.toggle_filter_displayed = function (e) {
|
bug fix: Move stream search out of scroll container.
We want the search widget, when visible, to be
outside the scroll container for the stream list.
One obvious use case is if you start scrolling, and
then realize it might be less effort to search.
Also, for user search, it already worked this way.
We have to add a couple resizing hooks here, but
it's not necessary to change the actual resize
calculation, since we move the section inside
of #streams_header, which is already accounted
for.
The only markup change here is to add
a `stream_search_section` class. I don't
know why we use `notdisplayed` here instead of
jQuery, or what `input-append` is for, but I
considered them outside the scope of this change.
We can also remove some crufty CSS that was
compensating for it being inside the container.
2019-02-09 19:09:56 +01:00
|
|
|
if ($('.stream_search_section.notdisplayed').length === 0) {
|
2016-06-13 22:06:12 +02:00
|
|
|
exports.clear_and_hide_search();
|
|
|
|
} else {
|
|
|
|
exports.initiate_search();
|
|
|
|
}
|
|
|
|
e.preventDefault();
|
2017-09-09 15:00:00 +02:00
|
|
|
};
|
2016-06-13 22:06:12 +02:00
|
|
|
|
2017-08-10 12:32:01 +02:00
|
|
|
exports.scroll_stream_into_view = function (stream_li) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const container = $('#stream-filters-container');
|
2017-05-02 01:08:31 +02:00
|
|
|
|
|
|
|
if (stream_li.length !== 1) {
|
|
|
|
blueslip.error('Invalid stream_li was passed in');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2018-04-24 14:09:16 +02:00
|
|
|
scroll_util.scroll_element_into_container(stream_li, container);
|
2017-05-02 01:08:31 +02:00
|
|
|
};
|
|
|
|
|
2018-07-06 20:23:20 +02:00
|
|
|
exports.maybe_scroll_narrow_into_view = function () {
|
|
|
|
// we don't want to interfere with user scrolling once the page loads
|
|
|
|
if (has_scrolled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_li = exports.get_current_stream_li();
|
2018-07-06 20:23:20 +02:00
|
|
|
if (stream_li) {
|
2019-10-25 09:45:13 +02:00
|
|
|
exports.scroll_stream_into_view(stream_li);
|
2018-07-06 20:23:20 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.get_current_stream_li = function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_id = topic_list.active_stream_id();
|
2018-07-06 20:23:20 +02:00
|
|
|
|
|
|
|
if (!stream_id) {
|
|
|
|
// stream_id is undefined in non-stream narrows
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const stream_li = exports.get_stream_li(stream_id);
|
2018-07-06 20:23:20 +02:00
|
|
|
|
|
|
|
if (!stream_li) {
|
|
|
|
// This code path shouldn't ever be reached.
|
|
|
|
blueslip.warn('No active stream_li found for defined id ' + stream_id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
return stream_li;
|
|
|
|
};
|
|
|
|
|
2019-10-25 09:45:13 +02:00
|
|
|
window.stream_list = exports;
|