2021-03-11 05:43:45 +01:00
|
|
|
import $ from "jquery";
|
|
|
|
|
2021-03-16 23:38:59 +01:00
|
|
|
import * as blueslip from "./blueslip";
|
2021-03-30 02:21:21 +02:00
|
|
|
import * as message_lists from "./message_lists";
|
2021-02-28 01:10:03 +01:00
|
|
|
import * as message_store from "./message_store";
|
2021-02-28 00:58:04 +01:00
|
|
|
import * as rows from "./rows";
|
2021-02-28 01:14:36 +01:00
|
|
|
import * as timerender from "./timerender";
|
2021-02-28 00:42:30 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let is_floating_recipient_bar_showing = false;
|
2014-03-13 21:14:33 +01:00
|
|
|
|
2019-02-08 21:11:55 +01:00
|
|
|
function top_offset(elem) {
|
2020-07-08 23:44:01 +02:00
|
|
|
return elem.offset().top - $("#message_view_header").safeOuterHeight();
|
2019-02-08 21:11:55 +01:00
|
|
|
}
|
|
|
|
|
2021-02-28 00:58:04 +01:00
|
|
|
export function first_visible_message(bar) {
|
2019-02-08 21:11:55 +01:00
|
|
|
// The first truly visible message would be computed using the
|
|
|
|
// bottom of the floating recipient bar; but we want the date from
|
|
|
|
// the first visible message were the floating recipient bar not
|
|
|
|
// displayed, which will always be the first messages whose bottom
|
|
|
|
// overlaps the floating recipient bar's space (since you ).
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const messages = bar.children(".message_row");
|
2021-02-28 00:58:04 +01:00
|
|
|
const frb_bottom = get_frb_bottom();
|
2019-11-02 00:06:25 +01:00
|
|
|
const frb_top = frb_bottom - 25;
|
|
|
|
let result;
|
2019-02-08 21:11:55 +01:00
|
|
|
|
2020-10-07 10:41:58 +02:00
|
|
|
for (const message_element of messages) {
|
2019-02-08 21:11:55 +01:00
|
|
|
// The details of this comparison function are sensitive, since we're
|
|
|
|
// balancing between three possible bugs:
|
|
|
|
//
|
|
|
|
// * If we compare against the bottom of the floating
|
|
|
|
// recipient bar, we end up with a bug where if the floating
|
|
|
|
// recipient bar is just above a normal recipient bar while
|
|
|
|
// overlapping a series of 1-line messages, there might be 2
|
|
|
|
// messages occluded by the recipient bar, and we want the
|
|
|
|
// second one, not the first.
|
|
|
|
//
|
|
|
|
// * If we compare the message bottom against the top of the
|
|
|
|
// floating recipient bar, and the floating recipient bar is
|
|
|
|
// over a "Yesterday/Today" message date row, we might
|
|
|
|
// confusingly have the floating recipient bar display
|
|
|
|
// e.g. "Yesterday" even though all messages in view were
|
|
|
|
// actually sent "Today".
|
|
|
|
//
|
|
|
|
// * If the the floating recipient bar is over a
|
|
|
|
// between-message groups date separator or similar widget,
|
|
|
|
// there might be no message overlap with the floating
|
|
|
|
// recipient bar.
|
|
|
|
//
|
|
|
|
// Careful testing of these two corner cases with
|
|
|
|
// message_viewport.scrollTop() to set precise scrolling
|
|
|
|
// positions determines the value for date_bar_height_offset.
|
|
|
|
|
2020-10-07 10:41:58 +02:00
|
|
|
let message = $(message_element);
|
2019-11-02 00:06:25 +01:00
|
|
|
const message_bottom = top_offset(message) + message.safeOuterHeight();
|
|
|
|
const date_bar_height_offset = 10;
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
if (message_bottom > frb_top) {
|
|
|
|
result = message;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Important: This will break if we ever have things that are
|
|
|
|
// not message rows inside a recipient_row block.
|
2020-07-15 01:29:15 +02:00
|
|
|
message = message.next(".message_row");
|
2020-12-22 11:26:39 +01:00
|
|
|
if (
|
|
|
|
message.length > 0 &&
|
|
|
|
result &&
|
2019-02-08 21:11:55 +01:00
|
|
|
// Before returning a result, we check whether the next
|
|
|
|
// message's top is actually below the bottom of the
|
|
|
|
// floating recipient bar; this is different from the
|
|
|
|
// bottom of our current message because there may be a
|
|
|
|
// between-messages date separator row in between.
|
2020-12-22 11:26:39 +01:00
|
|
|
top_offset(message) < frb_bottom - date_bar_height_offset
|
|
|
|
) {
|
|
|
|
result = message;
|
2019-02-08 21:11:55 +01:00
|
|
|
}
|
|
|
|
if (result) {
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If none of the messages are visible, just take the last message.
|
|
|
|
return $(messages[messages.length - 1]);
|
2021-02-28 00:58:04 +01:00
|
|
|
}
|
2019-02-08 21:11:55 +01:00
|
|
|
|
2021-02-28 00:58:04 +01:00
|
|
|
export function get_date(elem) {
|
|
|
|
const message_row = first_visible_message(elem);
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
if (!message_row || !message_row.length) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2019-02-08 21:11:55 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const msg_id = rows.id(message_row);
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
if (msg_id === undefined) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2019-02-08 21:11:55 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const message = message_store.get(msg_id);
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
if (!message) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2019-02-08 21:11:55 +01:00
|
|
|
}
|
|
|
|
|
2021-02-05 21:20:14 +01:00
|
|
|
const time = new Date(message.timestamp * 1000);
|
|
|
|
const today = new Date();
|
2019-11-02 00:06:25 +01:00
|
|
|
const rendered_date = timerender.render_date(time, undefined, today)[0].outerHTML;
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
return rendered_date;
|
2021-02-28 00:58:04 +01:00
|
|
|
}
|
2019-02-08 21:11:55 +01:00
|
|
|
|
2021-02-28 00:58:04 +01:00
|
|
|
export function get_frb_bottom() {
|
2019-11-02 00:06:25 +01:00
|
|
|
const bar = $("#floating_recipient_bar");
|
|
|
|
const bar_top = top_offset(bar);
|
|
|
|
const bar_bottom = bar_top + bar.safeOuterHeight();
|
2018-12-25 21:48:06 +01:00
|
|
|
|
|
|
|
return bar_bottom;
|
2021-02-28 00:58:04 +01:00
|
|
|
}
|
2018-12-25 21:48:06 +01:00
|
|
|
|
2021-02-28 00:58:04 +01:00
|
|
|
export function relevant_recipient_bars() {
|
2019-11-02 00:06:25 +01:00
|
|
|
let elems = [];
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
// This line of code does a reverse traversal
|
|
|
|
// from the selected message, which should be
|
|
|
|
// in the visible part of the feed, but is sometimes
|
|
|
|
// not exactly where we want. The value we get
|
|
|
|
// may be be too far up in the feed, but we can
|
|
|
|
// deal with that later.
|
2021-02-28 00:58:04 +01:00
|
|
|
let first_elem = candidate_recipient_bar();
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
if (!first_elem) {
|
2020-07-15 01:29:15 +02:00
|
|
|
first_elem = $(".focused_table").find(".recipient_row").first();
|
2019-02-08 21:11:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (first_elem.length === 0) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
elems.push(first_elem);
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const max_offset = top_offset($("#compose"));
|
|
|
|
let header_height = first_elem.find(".message_header").safeOuterHeight();
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
// It's okay to overestimate header_height a bit, as we don't
|
|
|
|
// really need an FRB for a section that barely shows.
|
|
|
|
header_height += 10;
|
|
|
|
|
|
|
|
function next(elem) {
|
2019-02-12 01:45:37 +01:00
|
|
|
elem = elem.next();
|
|
|
|
while (elem.length !== 0 && !elem.hasClass("recipient_row")) {
|
|
|
|
elem = elem.next();
|
|
|
|
}
|
|
|
|
return elem;
|
2019-02-08 21:11:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Now start the forward traversal of recipient bars.
|
|
|
|
// We'll stop when we go below the fold.
|
2019-11-02 00:06:25 +01:00
|
|
|
let elem = next(first_elem);
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
while (elem.length) {
|
|
|
|
if (top_offset(elem) < header_height) {
|
|
|
|
// If we are close to the top, then the prior
|
|
|
|
// elements we found are no longer relevant,
|
|
|
|
// because either the selected item we started
|
|
|
|
// with in our reverse traversal was too high,
|
|
|
|
// or there's simply not enough room to draw
|
|
|
|
// a recipient bar without it being ugly.
|
|
|
|
elems = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (top_offset(elem) > max_offset) {
|
|
|
|
// Out of sight, out of mind!
|
|
|
|
// (The element is below the fold, so we stop the
|
|
|
|
// traversal.)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
elems.push(elem);
|
|
|
|
elem = next(elem);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (elems.length === 0) {
|
2020-07-15 01:29:15 +02:00
|
|
|
blueslip.warn("Unexpected situation--maybe viewport height is very short.");
|
2019-02-08 21:11:55 +01:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
js: Convert _.map(a, …) to a.map(…).
And convert the corresponding function expressions to arrow style
while we’re here.
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 K from "ast-types/gen/kinds";
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);
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;
recast.visit(ast, {
visitCallExpression(path) {
const { callee, arguments: args } = path.node;
if (
n.MemberExpression.check(callee) &&
!callee.computed &&
n.Identifier.check(callee.object) &&
callee.object.name === "_" &&
n.Identifier.check(callee.property) &&
callee.property.name === "map" &&
args.length === 2 &&
checkExpression(args[0]) &&
checkExpression(args[1])
) {
const [arr, fn] = args;
path.replace(
b.callExpression(b.memberExpression(arr, b.identifier("map")), [
n.FunctionExpression.check(fn) ||
n.ArrowFunctionExpression.check(fn)
? b.arrowFunctionExpression(
fn.params,
n.BlockStatement.check(fn.body) &&
fn.body.body.length === 1 &&
n.ReturnStatement.check(fn.body.body[0])
? fn.body.body[0].argument || b.identifier("undefined")
: fn.body
)
: fn,
])
);
changed = true;
}
this.traverse(path);
},
});
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-08 02:43:49 +01:00
|
|
|
const items = elems.map((elem, i) => {
|
2019-11-02 00:06:25 +01:00
|
|
|
let date_html;
|
|
|
|
let need_frb;
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
if (i === 0) {
|
2021-02-28 00:58:04 +01:00
|
|
|
date_html = get_date(elem);
|
2019-02-08 21:11:55 +01:00
|
|
|
need_frb = top_offset(elem) < 0;
|
|
|
|
} else {
|
2020-07-15 01:29:15 +02:00
|
|
|
date_html = elem.find(".recipient_row_date").html();
|
2019-02-08 21:11:55 +01:00
|
|
|
need_frb = false;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const date_text = $(date_html).text();
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
// Add title here to facilitate troubleshooting.
|
2020-07-15 01:29:15 +02:00
|
|
|
const title = elem.find(".message_label_clickable").last().attr("title");
|
2019-02-08 21:11:55 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const item = {
|
2020-07-20 22:18:43 +02:00
|
|
|
elem,
|
|
|
|
title,
|
|
|
|
date_html,
|
|
|
|
date_text,
|
|
|
|
need_frb,
|
2019-02-08 21:11:55 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
return item;
|
|
|
|
});
|
|
|
|
|
|
|
|
items[0].show_date = true;
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
for (let i = 1; i < items.length; i += 1) {
|
2019-02-08 21:11:55 +01:00
|
|
|
items[i].show_date = items[i].date_text !== items[i - 1].date_text;
|
|
|
|
}
|
|
|
|
|
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 item of items) {
|
2019-02-08 21:11:55 +01:00
|
|
|
if (!item.need_frb) {
|
|
|
|
delete item.date_html;
|
|
|
|
}
|
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
|
|
|
}
|
2019-02-08 21:11:55 +01:00
|
|
|
|
|
|
|
return items;
|
2021-02-28 00:58:04 +01:00
|
|
|
}
|
2019-02-08 21:11:55 +01:00
|
|
|
|
2021-02-28 00:58:04 +01:00
|
|
|
export function candidate_recipient_bar() {
|
2019-02-08 21:11:55 +01:00
|
|
|
// Find a recipient bar that is close to being onscreen
|
|
|
|
// but above the "top". This function is guaranteed to
|
|
|
|
// return **some** recipient bar that is above the fold,
|
|
|
|
// if there is one, but it may not be the optimal one if
|
|
|
|
// our pointer is messed up. Starting with the pointer
|
|
|
|
// is just an optimization here, and our caller will do
|
|
|
|
// a forward traversal and clean up as necessary.
|
|
|
|
// In most cases we find the bottom-most of recipient
|
|
|
|
// bars that is still above the fold.
|
2018-12-25 21:48:06 +01:00
|
|
|
|
|
|
|
// Start with the pointer's current location.
|
2021-03-30 02:21:21 +02:00
|
|
|
const selected_row = message_lists.current.selected_row();
|
2018-12-25 21:48:06 +01:00
|
|
|
|
|
|
|
if (selected_row === undefined || selected_row.length === 0) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-12-25 21:48:06 +01:00
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let candidate = rows.get_message_recipient_row(selected_row);
|
2018-12-25 21:48:06 +01:00
|
|
|
if (candidate === undefined) {
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2018-12-25 21:48:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
while (candidate.length) {
|
2019-02-08 21:11:55 +01:00
|
|
|
if (candidate.hasClass("recipient_row") && top_offset(candidate) < 0) {
|
|
|
|
return candidate;
|
2018-12-25 21:48:06 +01:00
|
|
|
}
|
2019-02-08 21:11:55 +01:00
|
|
|
// We cannot use .prev(".recipient_row") here, because that
|
|
|
|
// returns nothing if the previous element is not a recipient
|
|
|
|
// row, rather than finding the first recipient_row.
|
2018-12-25 21:48:06 +01:00
|
|
|
candidate = candidate.prev();
|
|
|
|
}
|
2020-09-24 07:50:36 +02:00
|
|
|
|
|
|
|
return undefined;
|
2021-02-28 00:58:04 +01:00
|
|
|
}
|
2018-12-25 21:48:06 +01:00
|
|
|
|
2014-03-13 21:14:33 +01:00
|
|
|
function show_floating_recipient_bar() {
|
|
|
|
if (!is_floating_recipient_bar_showing) {
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#floating_recipient_bar").css("visibility", "visible");
|
2014-03-13 21:14:33 +01:00
|
|
|
is_floating_recipient_bar_showing = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let old_source;
|
2019-02-08 21:11:55 +01:00
|
|
|
function replace_floating_recipient_bar(source_info) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const source_recipient_bar = source_info.elem;
|
2019-02-08 21:11:55 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let new_label;
|
|
|
|
let other_label;
|
|
|
|
let header;
|
2019-02-08 21:11:55 +01:00
|
|
|
|
2018-12-26 15:42:17 +01:00
|
|
|
if (source_recipient_bar !== old_source) {
|
|
|
|
if (source_recipient_bar.children(".message_header_stream").length !== 0) {
|
2014-03-13 21:14:33 +01:00
|
|
|
new_label = $("#current_label_stream");
|
|
|
|
other_label = $("#current_label_private_message");
|
2018-12-26 15:42:17 +01:00
|
|
|
header = source_recipient_bar.children(".message_header_stream");
|
2014-03-13 21:14:33 +01:00
|
|
|
} else {
|
|
|
|
new_label = $("#current_label_private_message");
|
|
|
|
other_label = $("#current_label_stream");
|
2018-12-26 15:42:17 +01:00
|
|
|
header = source_recipient_bar.children(".message_header_private_message");
|
2014-03-13 21:14:33 +01:00
|
|
|
}
|
|
|
|
new_label.find(".message_header").replaceWith(header.clone());
|
2020-07-15 01:29:15 +02:00
|
|
|
other_label.css("display", "none");
|
|
|
|
new_label.css("display", "block");
|
2018-12-26 15:42:17 +01:00
|
|
|
new_label.attr("zid", rows.id(rows.first_message_in_group(source_recipient_bar)));
|
2014-03-13 21:14:33 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
new_label.toggleClass("message-fade", source_recipient_bar.hasClass("message-fade"));
|
2018-12-26 15:42:17 +01:00
|
|
|
old_source = source_recipient_bar;
|
2014-03-13 21:14:33 +01:00
|
|
|
}
|
2019-02-08 21:11:55 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const rendered_date = source_info.date_html || "";
|
2019-02-08 21:11:55 +01:00
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#floating_recipient_bar").find(".recipient_row_date").html(rendered_date);
|
2019-02-08 21:11:55 +01:00
|
|
|
|
2014-03-13 21:14:33 +01:00
|
|
|
show_floating_recipient_bar();
|
|
|
|
}
|
|
|
|
|
2021-02-28 00:58:04 +01:00
|
|
|
export function hide() {
|
2014-03-13 21:14:33 +01:00
|
|
|
if (is_floating_recipient_bar_showing) {
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#floating_recipient_bar").css("visibility", "hidden");
|
2014-03-13 21:14:33 +01:00
|
|
|
is_floating_recipient_bar_showing = false;
|
|
|
|
}
|
2021-02-28 00:58:04 +01:00
|
|
|
}
|
2014-03-13 21:14:33 +01:00
|
|
|
|
2021-02-28 00:58:04 +01:00
|
|
|
export function de_clutter_dates(items) {
|
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 item of items) {
|
2020-07-15 01:29:15 +02:00
|
|
|
item.elem.find(".recipient_row_date").toggle(item.show_date);
|
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
|
|
|
}
|
2021-02-28 00:58:04 +01:00
|
|
|
}
|
2014-03-13 21:14:33 +01:00
|
|
|
|
2021-02-28 00:58:04 +01:00
|
|
|
export function update() {
|
|
|
|
const items = relevant_recipient_bars();
|
2014-03-13 21:14:33 +01:00
|
|
|
|
2019-02-08 21:11:55 +01:00
|
|
|
if (!items || items.length === 0) {
|
2021-02-28 00:58:04 +01:00
|
|
|
hide();
|
2014-03-13 21:14:33 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-02-28 00:58:04 +01:00
|
|
|
de_clutter_dates(items);
|
2014-03-13 21:14:33 +01:00
|
|
|
|
2019-02-08 21:11:55 +01:00
|
|
|
if (!items[0].need_frb) {
|
2021-02-28 00:58:04 +01:00
|
|
|
hide();
|
2014-03-13 21:14:33 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-02-08 21:11:55 +01:00
|
|
|
replace_floating_recipient_bar(items[0]);
|
2021-02-28 00:58:04 +01:00
|
|
|
}
|