2020-02-06 07:07:10 +01:00
|
|
|
const emoji_codes = require("../generated/emoji/emoji_codes.json");
|
2020-01-28 17:13:16 +01:00
|
|
|
const typeahead = require("../shared/js/typeahead");
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const render_emoji_popover = require('../templates/emoji_popover.hbs');
|
|
|
|
const render_emoji_popover_content = require('../templates/emoji_popover_content.hbs');
|
|
|
|
const render_emoji_popover_search_results = require('../templates/emoji_popover_search_results.hbs');
|
|
|
|
const render_emoji_showcase = require("../templates/emoji_showcase.hbs");
|
2019-07-09 21:24:00 +02:00
|
|
|
|
2017-08-25 19:59:23 +02:00
|
|
|
// Emoji picker is of fixed width and height. Update these
|
|
|
|
// whenever these values are changed in `reactions.css`.
|
2019-11-02 00:06:25 +01:00
|
|
|
const APPROX_HEIGHT = 375;
|
|
|
|
const APPROX_WIDTH = 255;
|
2017-08-25 19:59:23 +02:00
|
|
|
|
2017-04-28 22:26:22 +02:00
|
|
|
// The functionalities for reacting to a message with an emoji
|
|
|
|
// and composing a message with an emoji share a single widget,
|
|
|
|
// implemented as the emoji_popover.
|
2017-08-16 22:28:59 +02:00
|
|
|
exports.complete_emoji_catalog = [];
|
2018-08-04 14:44:16 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let current_message_emoji_popover_elem;
|
|
|
|
let emoji_catalog_last_coordinates = {
|
2017-07-19 00:28:56 +02:00
|
|
|
section: 0,
|
|
|
|
index: 0,
|
|
|
|
};
|
2019-11-02 00:06:25 +01:00
|
|
|
let current_section = 0;
|
|
|
|
let current_index = 0;
|
|
|
|
let search_is_active = false;
|
|
|
|
const search_results = [];
|
|
|
|
let section_head_offsets = [];
|
|
|
|
let edit_message_id = null;
|
2017-07-19 00:28:56 +02:00
|
|
|
|
2017-08-16 22:24:02 +02:00
|
|
|
function get_all_emoji_categories() {
|
2017-07-19 00:28:56 +02:00
|
|
|
return [
|
2020-02-05 05:45:02 +01:00
|
|
|
{ name: "Popular", icon: "fa-star-o" },
|
|
|
|
{ name: "Smileys & Emotion", icon: "fa-smile-o" },
|
|
|
|
{ name: "People & Body", icon: "fa-thumbs-o-up" },
|
2018-04-23 07:48:19 +02:00
|
|
|
{ name: "Animals & Nature", icon: "fa-leaf" },
|
|
|
|
{ name: "Food & Drink", icon: "fa-cutlery" },
|
|
|
|
{ name: "Activities", icon: "fa-soccer-ball-o" },
|
|
|
|
{ name: "Travel & Places", icon: "fa-car" },
|
2017-07-19 00:28:56 +02:00
|
|
|
{ name: "Objects", icon: "fa-lightbulb-o" },
|
|
|
|
{ name: "Symbols", icon: "fa-hashtag" },
|
|
|
|
{ name: "Custom", icon: "fa-cog" },
|
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
function get_total_sections() {
|
|
|
|
if (search_is_active) {
|
|
|
|
return 1;
|
|
|
|
}
|
2017-08-16 22:28:59 +02:00
|
|
|
return exports.complete_emoji_catalog.length;
|
2017-07-19 00:28:56 +02:00
|
|
|
}
|
2017-04-27 07:27:25 +02:00
|
|
|
|
2017-07-19 00:28:56 +02:00
|
|
|
function get_max_index(section) {
|
|
|
|
if (search_is_active) {
|
|
|
|
return search_results.length;
|
|
|
|
} else if (section >= 0 && section < get_total_sections()) {
|
2017-08-16 22:28:59 +02:00
|
|
|
return exports.complete_emoji_catalog[section].emojis.length;
|
2017-04-27 07:27:25 +02:00
|
|
|
}
|
2017-07-19 00:28:56 +02:00
|
|
|
}
|
2017-04-27 07:27:25 +02:00
|
|
|
|
2017-10-05 15:47:49 +02:00
|
|
|
function get_emoji_id(section, index) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let type = "emoji_picker_emoji";
|
2017-10-05 15:47:49 +02:00
|
|
|
if (search_is_active) {
|
|
|
|
type = "emoji_search_result";
|
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const emoji_id = [type, section, index].join(",");
|
2017-10-05 15:47:49 +02:00
|
|
|
return emoji_id;
|
|
|
|
}
|
|
|
|
|
2017-10-05 16:22:16 +02:00
|
|
|
function get_emoji_coordinates(emoji_id) {
|
2017-12-14 19:51:17 +01:00
|
|
|
// Emoji id is of the following form:
|
|
|
|
// <emoji_type>_<section_number>_<index>.
|
|
|
|
// See `get_emoji_id()`.
|
2019-11-02 00:06:25 +01:00
|
|
|
const emoji_info = emoji_id.split(",");
|
2017-10-05 16:22:16 +02:00
|
|
|
return {
|
2017-12-14 19:51:17 +01:00
|
|
|
section: parseInt(emoji_info[1], 10),
|
|
|
|
index: parseInt(emoji_info[2], 10),
|
2017-10-05 16:22:16 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-07-19 00:28:56 +02:00
|
|
|
function show_search_results() {
|
|
|
|
$(".emoji-popover-emoji-map").hide();
|
|
|
|
$(".emoji-popover-category-tabs").hide();
|
|
|
|
$(".emoji-search-results-container").show();
|
|
|
|
emoji_catalog_last_coordinates = {
|
|
|
|
section: current_section,
|
|
|
|
index: current_index,
|
|
|
|
};
|
|
|
|
current_section = 0;
|
|
|
|
current_index = 0;
|
|
|
|
search_is_active = true;
|
2017-04-27 07:27:25 +02:00
|
|
|
}
|
|
|
|
|
2017-07-19 00:28:56 +02:00
|
|
|
function show_emoji_catalog() {
|
|
|
|
$(".emoji-popover-emoji-map").show();
|
|
|
|
$(".emoji-popover-category-tabs").show();
|
|
|
|
$(".emoji-search-results-container").hide();
|
|
|
|
current_section = emoji_catalog_last_coordinates.section;
|
|
|
|
current_index = emoji_catalog_last_coordinates.index;
|
|
|
|
search_is_active = false;
|
|
|
|
}
|
2017-04-29 09:46:31 +02:00
|
|
|
|
2017-07-19 00:28:56 +02:00
|
|
|
exports.generate_emoji_picker_data = function (realm_emojis) {
|
2020-02-12 06:22:45 +01:00
|
|
|
const catalog = new Map();
|
|
|
|
catalog.set("Custom", Array.from(realm_emojis.keys(), realm_emoji_name =>
|
|
|
|
emoji.emojis_by_name.get(realm_emoji_name)
|
|
|
|
));
|
2017-05-01 00:04:51 +02:00
|
|
|
|
2020-02-06 02:16:33 +01:00
|
|
|
for (const [category, codepoints] of Object.entries(emoji_codes.emoji_catalog)) {
|
2020-02-12 06:22:45 +01:00
|
|
|
const emojis = [];
|
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 codepoint of codepoints) {
|
2017-07-19 00:28:56 +02:00
|
|
|
if (emoji_codes.codepoint_to_name.hasOwnProperty(codepoint)) {
|
2020-02-06 00:04:34 +01:00
|
|
|
const emoji_dict = emoji.emojis_by_name.get(
|
|
|
|
emoji_codes.codepoint_to_name[codepoint]
|
|
|
|
);
|
|
|
|
if (emoji_dict !== undefined && emoji_dict.is_realm_emoji !== true) {
|
2020-02-12 06:22:45 +01:00
|
|
|
emojis.push(emoji_dict);
|
2017-07-19 00:28:56 +02:00
|
|
|
}
|
2017-05-23 15:54:01 +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
|
|
|
}
|
2020-02-12 06:22:45 +01:00
|
|
|
catalog.set(category, emojis);
|
2020-02-06 02:16:33 +01:00
|
|
|
}
|
2017-04-29 09:46:31 +02:00
|
|
|
|
2020-02-12 06:22:45 +01:00
|
|
|
const popular = [];
|
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 codepoint of typeahead.popular_emojis) {
|
2017-07-19 00:28:56 +02:00
|
|
|
if (emoji_codes.codepoint_to_name.hasOwnProperty(codepoint)) {
|
2020-02-06 00:04:34 +01:00
|
|
|
const emoji_dict = emoji.emojis_by_name.get(emoji_codes.codepoint_to_name[codepoint]);
|
|
|
|
if (emoji_dict !== undefined) {
|
2020-02-12 06:22:45 +01:00
|
|
|
popular.push(emoji_dict);
|
2017-07-19 00:28:56 +02:00
|
|
|
}
|
2017-04-29 09:46:31 +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
|
|
|
}
|
2020-02-12 06:22:45 +01:00
|
|
|
catalog.set("Popular", popular);
|
|
|
|
|
|
|
|
const categories = get_all_emoji_categories().filter(category => catalog.has(category.name));
|
|
|
|
exports.complete_emoji_catalog = categories.map(category => ({
|
|
|
|
name: category.name,
|
|
|
|
icon: category.icon,
|
|
|
|
emojis: catalog.get(category.name),
|
|
|
|
}));
|
2017-07-19 00:28:56 +02:00
|
|
|
};
|
2017-04-29 09:46:31 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const generate_emoji_picker_content = function (id) {
|
|
|
|
let emojis_used = [];
|
2017-04-29 09:46:31 +02:00
|
|
|
|
2017-07-19 00:28:56 +02:00
|
|
|
if (id !== undefined) {
|
|
|
|
emojis_used = reactions.get_emojis_used_by_user_for_message_id(id);
|
2017-06-02 17:31:06 +02:00
|
|
|
}
|
2020-02-06 00:04:34 +01:00
|
|
|
for (const emoji_dict of emoji.emojis_by_name.values()) {
|
2020-02-08 04:08:04 +01:00
|
|
|
emoji_dict.has_reacted = emoji_dict.aliases.some(alias => emojis_used.includes(alias));
|
2020-02-06 00:04:34 +01:00
|
|
|
}
|
2017-06-02 17:31:06 +02:00
|
|
|
|
2019-07-09 21:24:00 +02:00
|
|
|
return render_emoji_popover_content({
|
2017-07-19 00:28:56 +02:00
|
|
|
message_id: id,
|
2017-08-16 22:28:59 +02:00
|
|
|
emoji_categories: exports.complete_emoji_catalog,
|
2017-07-19 00:28:56 +02:00
|
|
|
});
|
|
|
|
};
|
2017-04-29 09:46:31 +02:00
|
|
|
|
2017-08-20 12:24:00 +02:00
|
|
|
function refill_section_head_offsets(popover) {
|
|
|
|
section_head_offsets = [];
|
|
|
|
popover.find('.emoji-popover-subheading').each(function () {
|
|
|
|
section_head_offsets.push({
|
|
|
|
section: $(this).attr('data-section'),
|
|
|
|
position_y: $(this).position().top,
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2017-04-27 07:27:25 +02:00
|
|
|
exports.reactions_popped = function () {
|
2017-04-28 22:26:22 +02:00
|
|
|
return current_message_emoji_popover_elem !== undefined;
|
2017-04-27 07:27:25 +02:00
|
|
|
};
|
|
|
|
|
2017-04-28 22:26:22 +02:00
|
|
|
exports.hide_emoji_popover = function () {
|
|
|
|
$('.has_popover').removeClass('has_popover has_emoji_popover');
|
2017-04-27 07:27:25 +02:00
|
|
|
if (exports.reactions_popped()) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const orig_title = current_message_emoji_popover_elem.data("original-title");
|
2017-04-28 22:26:22 +02:00
|
|
|
current_message_emoji_popover_elem.popover("destroy");
|
2017-09-07 21:03:24 +02:00
|
|
|
current_message_emoji_popover_elem.prop("title", orig_title);
|
2017-09-07 18:03:17 +02:00
|
|
|
current_message_emoji_popover_elem.removeClass("reaction_button_visible");
|
2017-04-28 22:26:22 +02:00
|
|
|
current_message_emoji_popover_elem = undefined;
|
2017-04-27 07:27:25 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-05-30 00:50:45 +02:00
|
|
|
function get_selected_emoji() {
|
|
|
|
return $(".emoji-popover-emoji").filter(":focus")[0];
|
|
|
|
}
|
|
|
|
|
2017-07-19 00:28:56 +02:00
|
|
|
function get_rendered_emoji(section, index) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const emoji_id = get_emoji_id(section, index);
|
|
|
|
const emoji = $(".emoji-popover-emoji[data-emoji-id='" + emoji_id + "']");
|
2017-07-19 00:28:56 +02:00
|
|
|
if (emoji.length > 0) {
|
|
|
|
return emoji;
|
2017-06-02 17:31:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-30 00:50:45 +02:00
|
|
|
function filter_emojis() {
|
2019-11-02 00:06:25 +01:00
|
|
|
const elt = $(".emoji-popover-filter").expectOne();
|
|
|
|
const query = elt.val().trim().toLowerCase();
|
|
|
|
const message_id = $(".emoji-search-results-container").data("message-id");
|
|
|
|
const search_results_visible = $(".emoji-search-results-container").is(":visible");
|
2017-07-19 00:28:56 +02:00
|
|
|
if (query !== "") {
|
2019-11-02 00:06:25 +01:00
|
|
|
const categories = exports.complete_emoji_catalog;
|
|
|
|
const search_terms = query.split(" ");
|
2019-10-26 00:28:42 +02:00
|
|
|
search_results.length = 0;
|
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 category of categories) {
|
2017-07-19 00:28:56 +02:00
|
|
|
if (category.name === "Popular") {
|
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
|
|
|
continue;
|
2017-05-30 00:50:45 +02:00
|
|
|
}
|
2019-11-02 00:06:25 +01:00
|
|
|
const emojis = category.emojis;
|
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 emoji_dict of emojis) {
|
2020-02-08 05:13:43 +01:00
|
|
|
for (const alias of emoji_dict.aliases) {
|
2020-02-08 05:11:31 +01:00
|
|
|
const match = search_terms.every(search_term => alias.includes(search_term));
|
2017-08-24 23:04:24 +02:00
|
|
|
if (match) {
|
2020-02-09 04:15:38 +01:00
|
|
|
search_results.push({ ...emoji_dict, name: alias });
|
2020-02-08 05:13:43 +01:00
|
|
|
break; // We only need the first matching alias per emoji.
|
2017-08-24 23:04:24 +02:00
|
|
|
}
|
2020-02-08 05:13:43 +01: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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const rendered_search_results = render_emoji_popover_search_results({
|
2017-07-19 00:28:56 +02:00
|
|
|
search_results: search_results,
|
|
|
|
message_id: message_id,
|
|
|
|
});
|
2018-03-22 22:14:43 +01:00
|
|
|
$('.emoji-search-results').html(rendered_search_results);
|
2019-01-09 14:30:35 +01:00
|
|
|
ui.reset_scrollbar($(".emoji-search-results-container"));
|
2017-07-19 00:28:56 +02:00
|
|
|
if (!search_results_visible) {
|
|
|
|
show_search_results();
|
2017-06-02 17:31:06 +02:00
|
|
|
}
|
2017-05-30 00:50:45 +02:00
|
|
|
} else {
|
2017-07-19 00:28:56 +02:00
|
|
|
show_emoji_catalog();
|
2017-05-30 00:50:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-27 11:04:30 +02:00
|
|
|
function get_alias_to_be_used(message_id, emoji_name) {
|
|
|
|
// If the user has reacted to this message, then this function
|
|
|
|
// returns the alias of this emoji he used, otherwise, returns
|
|
|
|
// the passed name as it is.
|
2019-11-02 00:06:25 +01:00
|
|
|
const message = message_store.get(message_id);
|
|
|
|
let aliases = [emoji_name];
|
2020-02-06 00:17:30 +01:00
|
|
|
if (!emoji.active_realm_emojis.has(emoji_name)) {
|
2017-08-27 11:04:30 +02:00
|
|
|
if (emoji_codes.name_to_codepoint.hasOwnProperty(emoji_name)) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const codepoint = emoji_codes.name_to_codepoint[emoji_name];
|
2020-02-06 00:20:45 +01:00
|
|
|
aliases = emoji.default_emoji_aliases.get(codepoint);
|
2017-08-27 11:04:30 +02:00
|
|
|
} else {
|
2020-02-26 00:26:47 +01:00
|
|
|
blueslip.error("Invalid emoji name: " + emoji_name);
|
2017-08-27 11:04:30 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const user_id = page_params.user_id;
|
reactions: Rewrite code to use clean reactions.
Before this commit, the reactions code would
take the `message.reactions` structure from
the server and try to "collapse" all the reactions
for the same users into the same reactions,
but with each reaction having a list of user_ids.
It was a strangely denormalized structure that
was awkward to work with, and it made it really
hard to reason about whether the data was in
the original structure that the server sent or
the modified structure.
Now we use a cleaner, normalized Map to keep
each reaction (i.e. one per emoji), and we
write that to `message.clean_reactions`.
The `clean_reactions` structure is now the
authoritatize source for all reaction-related
operations. As soon as you try to do anything
with reactions, we build the `clean_reactions`
data on the fly from the server data.
In particular, when we process events, we just
directly manipulate the `clean_reactions` data,
which is much easier to work with, since it's
a Map and doesn't duplicate any data.
This rewrite should avoid some obscure bugs.
I use `r` as shorthand for the clean reaction
structures, so as not to confuse it with
data from the server's message.reactions.
It also avoids some confusion where we use
`reaction` as a var name for the reaction
elements.
2020-03-08 13:13:47 +01:00
|
|
|
|
|
|
|
const reaction_name = reactions.get_name_for_alias(message, user_id, aliases);
|
|
|
|
|
|
|
|
if (reaction_name) {
|
|
|
|
return reaction_name;
|
2017-08-27 11:04:30 +02:00
|
|
|
}
|
|
|
|
return emoji_name;
|
|
|
|
}
|
|
|
|
|
2017-08-27 11:45:40 +02:00
|
|
|
function toggle_reaction(emoji_name) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const message_id = current_msg_list.selected_id();
|
|
|
|
const message = message_store.get(message_id);
|
2017-08-27 11:45:40 +02:00
|
|
|
if (!message) {
|
|
|
|
blueslip.error('reactions: Bad message id: ' + message_id);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const alias = get_alias_to_be_used(message_id, emoji_name);
|
2017-08-27 11:45:40 +02:00
|
|
|
reactions.toggle_emoji_reaction(message_id, alias);
|
|
|
|
}
|
|
|
|
|
2017-05-30 00:50:45 +02:00
|
|
|
function maybe_select_emoji(e) {
|
|
|
|
if (e.keyCode === 13) { // enter key
|
|
|
|
e.preventDefault();
|
2019-11-02 00:06:25 +01:00
|
|
|
const first_emoji = get_rendered_emoji(0, 0);
|
2017-05-30 00:50:45 +02:00
|
|
|
if (first_emoji) {
|
2019-10-25 09:45:13 +02:00
|
|
|
if (exports.is_composition(first_emoji)) {
|
2017-05-30 00:50:45 +02:00
|
|
|
first_emoji.click();
|
|
|
|
} else {
|
2017-08-27 11:45:40 +02:00
|
|
|
toggle_reaction(first_emoji.data("emoji-name"));
|
2017-05-30 00:50:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
exports.toggle_selected_emoji = function () {
|
|
|
|
// Toggle the currently selected emoji.
|
2019-11-02 00:06:25 +01:00
|
|
|
const selected_emoji = get_selected_emoji();
|
2017-05-30 00:50:45 +02:00
|
|
|
|
|
|
|
if (selected_emoji === undefined) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const emoji_name = $(selected_emoji).data("emoji-name");
|
2017-05-30 00:50:45 +02:00
|
|
|
|
2017-08-27 11:45:40 +02:00
|
|
|
toggle_reaction(emoji_name);
|
2017-05-30 00:50:45 +02:00
|
|
|
};
|
|
|
|
|
2017-07-19 00:28:56 +02:00
|
|
|
function round_off_to_previous_multiple(number_to_round, multiple) {
|
2018-06-06 18:50:09 +02:00
|
|
|
return number_to_round - number_to_round % multiple;
|
2017-07-19 00:28:56 +02:00
|
|
|
}
|
|
|
|
|
2017-08-27 22:15:02 +02:00
|
|
|
function reset_emoji_showcase() {
|
|
|
|
$(".emoji-showcase-container").html("");
|
|
|
|
}
|
|
|
|
|
|
|
|
function update_emoji_showcase($focused_emoji) {
|
|
|
|
// Don't use jQuery's data() function here. It has the side-effect
|
|
|
|
// of converting emoji names like :100:, :1234: etc to number.
|
2019-11-02 00:06:25 +01:00
|
|
|
const focused_emoji_name = $focused_emoji.attr("data-emoji-name");
|
|
|
|
const canonical_name = emoji.get_canonical_name(focused_emoji_name);
|
2020-02-06 00:04:34 +01:00
|
|
|
const focused_emoji_dict = emoji.emojis_by_name.get(canonical_name);
|
2017-08-27 22:15:02 +02:00
|
|
|
|
2020-02-09 04:15:38 +01:00
|
|
|
const emoji_dict = {
|
|
|
|
...focused_emoji_dict,
|
2017-08-27 22:15:02 +02:00
|
|
|
name: focused_emoji_name.replace(/_/g, ' '),
|
2020-02-09 04:15:38 +01:00
|
|
|
};
|
2019-11-02 00:06:25 +01:00
|
|
|
const rendered_showcase = render_emoji_showcase({
|
2017-08-27 22:15:02 +02:00
|
|
|
emoji_dict: emoji_dict,
|
|
|
|
});
|
|
|
|
|
|
|
|
$(".emoji-showcase-container").html(rendered_showcase);
|
|
|
|
}
|
|
|
|
|
2017-10-05 21:20:24 +02:00
|
|
|
function may_be_change_focused_emoji(next_section, next_index, preserve_scroll) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const next_emoji = get_rendered_emoji(next_section, next_index);
|
2017-07-19 00:28:56 +02:00
|
|
|
if (next_emoji) {
|
|
|
|
current_section = next_section;
|
|
|
|
current_index = next_index;
|
2017-10-05 21:20:24 +02:00
|
|
|
if (!preserve_scroll) {
|
|
|
|
next_emoji.focus();
|
|
|
|
} else {
|
2019-11-02 00:06:25 +01:00
|
|
|
const $emoji_map = $(".emoji-popover-emoji-map");
|
|
|
|
const start = ui.get_scroll_element($emoji_map).scrollTop();
|
2017-10-05 21:20:24 +02:00
|
|
|
next_emoji.focus();
|
2019-03-01 01:40:05 +01:00
|
|
|
if (ui.get_scroll_element($emoji_map).scrollTop() !== start) {
|
|
|
|
ui.get_scroll_element($emoji_map).scrollTop(start);
|
2017-10-05 21:20:24 +02:00
|
|
|
}
|
|
|
|
}
|
2017-08-27 22:15:02 +02:00
|
|
|
update_emoji_showcase(next_emoji);
|
2017-07-19 00:28:56 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function may_be_change_active_section(next_section) {
|
|
|
|
if (next_section >= 0 && next_section < get_total_sections()) {
|
|
|
|
current_section = next_section;
|
|
|
|
current_index = 0;
|
2019-11-02 00:06:25 +01:00
|
|
|
const offset = section_head_offsets[current_section];
|
2017-07-19 00:28:56 +02:00
|
|
|
if (offset) {
|
2019-03-01 01:40:05 +01:00
|
|
|
ui.get_scroll_element($(".emoji-popover-emoji-map")).scrollTop(offset.position_y);
|
2017-07-19 00:28:56 +02:00
|
|
|
may_be_change_focused_emoji(current_section, current_index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function get_next_emoji_coordinates(move_by) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let next_section = current_section;
|
|
|
|
let next_index = current_index + move_by;
|
|
|
|
let max_len;
|
2017-07-19 00:28:56 +02:00
|
|
|
if (next_index < 0) {
|
|
|
|
next_section = next_section - 1;
|
|
|
|
if (next_section >= 0) {
|
|
|
|
next_index = get_max_index(next_section) - 1;
|
|
|
|
if (move_by === -6) {
|
|
|
|
max_len = get_max_index(next_section);
|
2019-11-02 00:06:25 +01:00
|
|
|
const prev_multiple = round_off_to_previous_multiple(max_len, 6);
|
2017-07-19 00:28:56 +02:00
|
|
|
next_index = prev_multiple + current_index;
|
|
|
|
next_index = next_index >= max_len
|
2018-06-06 18:19:09 +02:00
|
|
|
? prev_multiple + current_index - 6
|
2018-05-06 21:43:17 +02:00
|
|
|
: next_index;
|
2017-07-19 00:28:56 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (next_index >= get_max_index(next_section)) {
|
|
|
|
next_section = next_section + 1;
|
|
|
|
if (next_section < get_total_sections()) {
|
|
|
|
next_index = 0;
|
|
|
|
if (move_by === 6) {
|
|
|
|
max_len = get_max_index(next_index);
|
|
|
|
next_index = current_index % 6;
|
|
|
|
next_index = next_index >= max_len ? max_len - 1 : next_index;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
section: next_section,
|
|
|
|
index: next_index,
|
|
|
|
};
|
|
|
|
}
|
2017-05-30 00:50:45 +02:00
|
|
|
|
2017-08-17 14:25:40 +02:00
|
|
|
function change_focus_to_filter() {
|
|
|
|
$('.emoji-popover-filter').focus();
|
|
|
|
// If search is active reset current selected emoji to first emoji.
|
|
|
|
if (search_is_active) {
|
|
|
|
current_section = 0;
|
|
|
|
current_index = 0;
|
|
|
|
}
|
2017-08-27 22:15:02 +02:00
|
|
|
reset_emoji_showcase();
|
2017-08-17 14:25:40 +02:00
|
|
|
}
|
|
|
|
|
2017-07-19 00:28:56 +02:00
|
|
|
exports.navigate = function (event_name) {
|
2017-10-02 23:10:55 +02:00
|
|
|
if (event_name === 'toggle_reactions_popover' && exports.reactions_popped() &&
|
|
|
|
(search_is_active === false || search_results.length === 0)) {
|
|
|
|
exports.hide_emoji_popover();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2017-08-17 14:25:40 +02:00
|
|
|
// If search is active and results are empty then return immediately.
|
|
|
|
if (search_is_active === true && search_results.length === 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const selected_emoji = get_rendered_emoji(current_section, current_index);
|
|
|
|
const is_filter_focused = $('.emoji-popover-filter').is(':focus');
|
|
|
|
let next_section = 0;
|
2017-05-30 00:50:45 +02:00
|
|
|
// special cases
|
2017-08-25 16:32:28 +02:00
|
|
|
if (is_filter_focused) {
|
|
|
|
// Move down into emoji map.
|
2019-11-02 00:06:25 +01:00
|
|
|
const filter_text = $(".emoji-popover-filter").val();
|
|
|
|
const is_cursor_at_end = $(".emoji-popover-filter").caret() === filter_text.length;
|
2017-08-25 16:32:28 +02:00
|
|
|
if (event_name === "down_arrow" ||
|
2018-06-06 18:50:09 +02:00
|
|
|
is_cursor_at_end && event_name === "right_arrow") {
|
2017-08-25 16:32:28 +02:00
|
|
|
selected_emoji.focus();
|
|
|
|
if (current_section === 0 && current_index < 6) {
|
2019-03-01 01:40:05 +01:00
|
|
|
ui.get_scroll_element($(".emoji-popover-emoji-map")).scrollTop(0);
|
2017-08-25 16:32:28 +02:00
|
|
|
}
|
2017-08-27 22:15:02 +02:00
|
|
|
update_emoji_showcase(selected_emoji);
|
2017-08-25 16:32:28 +02:00
|
|
|
return true;
|
2017-05-30 00:50:45 +02:00
|
|
|
}
|
2017-08-25 16:32:28 +02:00
|
|
|
if (event_name === "tab") {
|
|
|
|
selected_emoji.focus();
|
2017-08-27 22:15:02 +02:00
|
|
|
update_emoji_showcase(selected_emoji);
|
2017-08-25 16:32:28 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
2018-06-06 18:50:09 +02:00
|
|
|
} else if (current_section === 0 && current_index < 6 && event_name === 'up_arrow' ||
|
|
|
|
current_section === 0 && current_index === 0 && event_name === 'left_arrow') {
|
2017-07-19 00:28:56 +02:00
|
|
|
if (selected_emoji) {
|
|
|
|
// In this case, we're move up into the reaction
|
|
|
|
// filter. Here, we override the default browser
|
|
|
|
// behavior, which in Firefox is good (preserving
|
|
|
|
// the cursor position) and in Chrome is bad (cursor
|
|
|
|
// goes to beginning) with something reasonable and
|
|
|
|
// consistent (cursor goes to the end of the filter
|
|
|
|
// string).
|
2017-05-30 00:50:45 +02:00
|
|
|
$('.emoji-popover-filter').focus().caret(Infinity);
|
2019-03-01 01:40:05 +01:00
|
|
|
ui.get_scroll_element($(".emoji-popover-emoji-map")).scrollTop(0);
|
|
|
|
ui.get_scroll_element($(".emoji-search-results-container")).scrollTop(0);
|
2017-07-19 00:28:56 +02:00
|
|
|
current_section = 0;
|
|
|
|
current_index = 0;
|
2017-08-27 22:15:02 +02:00
|
|
|
reset_emoji_showcase();
|
2017-05-30 00:50:45 +02:00
|
|
|
return true;
|
|
|
|
}
|
2017-07-19 00:28:56 +02:00
|
|
|
} else if (event_name === 'tab') {
|
2017-08-25 16:32:28 +02:00
|
|
|
change_focus_to_filter();
|
2017-07-19 00:28:56 +02:00
|
|
|
return true;
|
|
|
|
} else if (event_name === 'shift_tab') {
|
|
|
|
if (!is_filter_focused) {
|
2017-08-17 14:25:40 +02:00
|
|
|
change_focus_to_filter();
|
2017-07-19 00:28:56 +02:00
|
|
|
}
|
|
|
|
return true;
|
|
|
|
} else if (event_name === 'page_up') {
|
|
|
|
next_section = current_section - 1;
|
|
|
|
may_be_change_active_section(next_section);
|
|
|
|
return true;
|
|
|
|
} else if (event_name === 'page_down') {
|
|
|
|
next_section = current_section + 1;
|
|
|
|
may_be_change_active_section(next_section);
|
|
|
|
return true;
|
|
|
|
} else if (!is_filter_focused) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let next_coord = {};
|
2017-07-19 00:28:56 +02:00
|
|
|
switch (event_name) {
|
2018-05-07 01:38:14 +02:00
|
|
|
case 'down_arrow':
|
|
|
|
next_coord = get_next_emoji_coordinates(6);
|
|
|
|
break;
|
|
|
|
case 'up_arrow':
|
|
|
|
next_coord = get_next_emoji_coordinates(-6);
|
|
|
|
break;
|
|
|
|
case 'left_arrow':
|
|
|
|
next_coord = get_next_emoji_coordinates(-1);
|
|
|
|
break;
|
|
|
|
case 'right_arrow':
|
|
|
|
next_coord = get_next_emoji_coordinates(1);
|
|
|
|
break;
|
2017-07-19 00:28:56 +02:00
|
|
|
}
|
|
|
|
return may_be_change_focused_emoji(next_coord.section, next_coord.index);
|
2017-05-30 00:50:45 +02:00
|
|
|
}
|
2017-07-19 00:28:56 +02:00
|
|
|
return false;
|
|
|
|
};
|
2017-05-30 00:50:45 +02:00
|
|
|
|
2017-08-29 23:01:18 +02:00
|
|
|
function process_keypress(e) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const is_filter_focused = $('.emoji-popover-filter').is(':focus');
|
|
|
|
const pressed_key = e.which;
|
2017-10-02 23:10:55 +02:00
|
|
|
if (!is_filter_focused && pressed_key !== 58) {
|
|
|
|
// ':' => 58, is a hotkey for toggling reactions popover.
|
2017-08-29 23:01:18 +02:00
|
|
|
if (pressed_key >= 32 && pressed_key <= 126 || pressed_key === 8) {
|
|
|
|
// Handle only printable characters or backspace.
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const emoji_filter = $('.emoji-popover-filter');
|
|
|
|
const old_query = emoji_filter.val();
|
|
|
|
let new_query = "";
|
2017-08-29 23:01:18 +02:00
|
|
|
|
|
|
|
if (pressed_key === 8) { // Handles backspace.
|
|
|
|
new_query = old_query.slice(0, -1);
|
|
|
|
} else { // Handles any printable character.
|
2019-11-02 00:06:25 +01:00
|
|
|
const key_str = String.fromCharCode(e.which);
|
2017-08-29 23:01:18 +02:00
|
|
|
new_query = old_query + key_str;
|
|
|
|
}
|
|
|
|
|
|
|
|
emoji_filter.val(new_query);
|
|
|
|
change_focus_to_filter();
|
|
|
|
filter_emojis();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-19 00:28:56 +02:00
|
|
|
exports.emoji_select_tab = function (elt) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const scrolltop = elt.scrollTop();
|
|
|
|
const scrollheight = elt.prop('scrollHeight');
|
|
|
|
const elt_height = elt.height();
|
|
|
|
let currently_selected = "";
|
2017-07-19 00:28:56 +02:00
|
|
|
section_head_offsets.forEach(function (o) {
|
2018-06-04 21:13:07 +02:00
|
|
|
if (scrolltop + elt_height / 2 >= o.position_y) {
|
2017-07-19 00:28:56 +02:00
|
|
|
currently_selected = o.section;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
// Handles the corner case of the last category being
|
|
|
|
// smaller than half of the emoji picker height.
|
|
|
|
if (elt_height + scrolltop === scrollheight) {
|
|
|
|
currently_selected = section_head_offsets[section_head_offsets.length - 1].section;
|
2017-05-30 00:50:45 +02:00
|
|
|
}
|
2017-07-19 00:28:56 +02:00
|
|
|
// Handles the corner case of the scrolling back to top.
|
|
|
|
if (scrolltop === 0) {
|
|
|
|
currently_selected = section_head_offsets[0].section;
|
2017-05-30 00:50:45 +02:00
|
|
|
}
|
2017-07-19 00:28:56 +02:00
|
|
|
if (currently_selected) {
|
|
|
|
$('.emoji-popover-tab-item.active').removeClass('active');
|
2018-06-04 21:13:07 +02:00
|
|
|
$('.emoji-popover-tab-item[data-tab-name="' + currently_selected + '"]').addClass('active');
|
2017-05-30 00:50:45 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2017-08-29 22:59:25 +02:00
|
|
|
function register_popover_events(popover) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const $emoji_map = popover.find('.emoji-popover-emoji-map');
|
2017-08-29 22:59:25 +02:00
|
|
|
|
2019-03-01 01:40:05 +01:00
|
|
|
ui.get_scroll_element($emoji_map).on("scroll", function () {
|
2019-10-25 09:45:13 +02:00
|
|
|
exports.emoji_select_tab(ui.get_scroll_element($emoji_map));
|
2017-08-29 22:59:25 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
$('.emoji-popover-filter').on('input', filter_emojis);
|
|
|
|
$('.emoji-popover-filter').keydown(maybe_select_emoji);
|
2017-08-29 23:01:18 +02:00
|
|
|
$('.emoji-popover').keypress(process_keypress);
|
|
|
|
$('.emoji-popover').keydown(function (e) {
|
|
|
|
// Because of cross-browser issues we need to handle backspace
|
|
|
|
// key separately. Firefox fires `keypress` event for backspace
|
|
|
|
// key but chrome doesn't so we need to trigger the logic for
|
|
|
|
// handling backspace in `keydown` event which is fired by both.
|
|
|
|
if (e.which === 8) {
|
|
|
|
process_keypress(e);
|
|
|
|
}
|
|
|
|
});
|
2017-08-29 22:59:25 +02:00
|
|
|
}
|
|
|
|
|
2017-08-29 22:42:54 +02:00
|
|
|
exports.render_emoji_popover = function (elt, id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const template_args = {
|
2017-08-29 22:42:54 +02:00
|
|
|
class: "emoji-info-popover",
|
|
|
|
};
|
2019-11-02 00:06:25 +01:00
|
|
|
let placement = popovers.compute_placement(elt, APPROX_HEIGHT, APPROX_WIDTH, true);
|
2017-09-26 21:54:34 +02:00
|
|
|
|
2019-02-15 19:15:03 +01:00
|
|
|
if (placement === 'viewport_center') {
|
|
|
|
// For legacy reasons `compute_placement` actually can
|
|
|
|
// return `viewport_center`, but bootstrap doesn't actually
|
|
|
|
// support that.
|
|
|
|
placement = 'left';
|
|
|
|
}
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let template = render_emoji_popover(template_args);
|
2017-09-26 21:54:34 +02:00
|
|
|
|
|
|
|
// if the window is mobile sized, add the `.popover-flex` wrapper to the emoji
|
|
|
|
// popover so that it will be wrapped in flex and centered in the screen.
|
|
|
|
if (window.innerWidth <= 768) {
|
|
|
|
template = "<div class='popover-flex'>" + template + "</div>";
|
|
|
|
}
|
|
|
|
|
2017-08-29 22:42:54 +02:00
|
|
|
elt.popover({
|
|
|
|
// temporary patch for handling popover placement of `viewport_center`
|
2019-02-15 19:15:03 +01:00
|
|
|
placement: placement,
|
2019-02-15 19:15:20 +01:00
|
|
|
fix_positions: true,
|
2018-12-18 19:34:45 +01:00
|
|
|
template: template,
|
|
|
|
title: "",
|
|
|
|
content: generate_emoji_picker_content(id),
|
2019-09-17 23:54:52 +02:00
|
|
|
html: true,
|
2018-12-18 19:34:45 +01:00
|
|
|
trigger: "manual",
|
2017-08-29 22:42:54 +02:00
|
|
|
});
|
|
|
|
elt.popover("show");
|
2017-09-07 21:03:24 +02:00
|
|
|
elt.prop("title", i18n.t("Add emoji reaction (:)"));
|
2019-02-14 13:58:44 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const popover = elt.data('popover').$tip;
|
2019-02-14 13:58:44 +01:00
|
|
|
popover.find('.emoji-popover-filter').focus();
|
2017-08-29 22:42:54 +02:00
|
|
|
current_message_emoji_popover_elem = elt;
|
|
|
|
|
|
|
|
emoji_catalog_last_coordinates = {
|
|
|
|
section: 0,
|
|
|
|
index: 0,
|
|
|
|
};
|
|
|
|
show_emoji_catalog();
|
|
|
|
|
|
|
|
refill_section_head_offsets(popover);
|
2017-08-29 22:59:25 +02:00
|
|
|
register_popover_events(popover);
|
2017-08-29 22:42:54 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
exports.toggle_emoji_popover = function (element, id) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const last_popover_elem = current_message_emoji_popover_elem;
|
2017-08-29 22:42:54 +02:00
|
|
|
popovers.hide_all();
|
|
|
|
if (last_popover_elem !== undefined
|
|
|
|
&& last_popover_elem.get()[0] === element) {
|
|
|
|
// We want it to be the case that a user can dismiss a popover
|
|
|
|
// by clicking on the same element that caused the popover.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
$(element).closest('.message_row').toggleClass('has_popover has_emoji_popover');
|
2019-11-02 00:06:25 +01:00
|
|
|
const elt = $(element);
|
2017-08-29 22:42:54 +02:00
|
|
|
if (id !== undefined) {
|
|
|
|
current_msg_list.select_id(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (elt.data('popover') === undefined) {
|
2017-09-07 18:03:17 +02:00
|
|
|
// Keep the element over which the popover is based off visible.
|
|
|
|
elt.addClass("reaction_button_visible");
|
2019-10-25 09:45:13 +02:00
|
|
|
exports.render_emoji_popover(elt, id);
|
2017-08-29 22:42:54 +02:00
|
|
|
}
|
2017-08-27 22:15:02 +02:00
|
|
|
reset_emoji_showcase();
|
2017-08-29 22:42:54 +02:00
|
|
|
};
|
|
|
|
|
2017-04-27 07:27:25 +02:00
|
|
|
exports.register_click_handlers = function () {
|
2017-07-19 00:28:56 +02:00
|
|
|
|
2017-08-16 12:14:34 +02:00
|
|
|
$(document).on('click', '.emoji-popover-emoji.reaction', function () {
|
|
|
|
// When an emoji is clicked in the popover,
|
|
|
|
// if the user has reacted to this message with this emoji
|
|
|
|
// the reaction is removed
|
|
|
|
// otherwise, the reaction is added
|
2019-11-02 00:06:25 +01:00
|
|
|
const emoji_name = $(this).data("emoji-name");
|
2017-08-27 11:45:40 +02:00
|
|
|
toggle_reaction(emoji_name);
|
2017-08-16 12:14:34 +02:00
|
|
|
});
|
|
|
|
|
2017-04-28 22:26:22 +02:00
|
|
|
$(document).on('click', '.emoji-popover-emoji.composition', function (e) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const emoji_name = $(this).data("emoji-name");
|
|
|
|
const emoji_text = ':' + emoji_name + ':';
|
2019-01-15 09:16:59 +01:00
|
|
|
// The following check will return false if emoji was not selected in
|
|
|
|
// message edit form.
|
|
|
|
if (edit_message_id !== null) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const edit_message_textarea = $("#message_edit_content_" + edit_message_id);
|
2019-01-15 09:16:59 +01:00
|
|
|
// Assign null to edit_message_id so that the selection of emoji in new
|
|
|
|
// message composition form works correctly.
|
|
|
|
edit_message_id = null;
|
|
|
|
compose_ui.insert_syntax_and_focus(emoji_text, edit_message_textarea);
|
|
|
|
} else {
|
|
|
|
compose_ui.insert_syntax_and_focus(emoji_text);
|
|
|
|
}
|
2017-04-27 07:27:25 +02:00
|
|
|
e.stopPropagation();
|
2019-10-25 09:45:13 +02:00
|
|
|
exports.hide_emoji_popover();
|
2017-04-27 07:27:25 +02:00
|
|
|
});
|
|
|
|
|
2019-01-15 09:16:59 +01:00
|
|
|
$("body").on("click", "#emoji_map", function (e) {
|
2017-04-27 07:27:25 +02:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
2019-01-15 09:16:59 +01:00
|
|
|
// The data-message-id atribute is only present in the emoji icon present in
|
|
|
|
// the message edit form. So the following check will return false if this
|
|
|
|
// event was not fired from message edit form.
|
|
|
|
if ($(this).attr("data-message-id") !== undefined) {
|
|
|
|
// Store data-message-id value in global variable edit_message_id so that
|
|
|
|
// its value can be further used to correclty find the message textarea element.
|
|
|
|
edit_message_id = $(this).attr("data-message-id");
|
|
|
|
} else {
|
|
|
|
edit_message_id = null;
|
|
|
|
}
|
2019-10-25 09:45:13 +02:00
|
|
|
exports.toggle_emoji_popover(this);
|
2017-04-27 07:27:25 +02:00
|
|
|
});
|
|
|
|
|
2017-09-07 11:14:59 +02:00
|
|
|
$("#main_div").on("click", ".reaction_button", function (e) {
|
2017-04-27 07:27:25 +02:00
|
|
|
e.stopPropagation();
|
2017-07-19 14:01:06 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const message_id = rows.get_message_id(this);
|
2019-10-25 09:45:13 +02:00
|
|
|
exports.toggle_emoji_popover(this, message_id);
|
2017-04-27 07:27:25 +02:00
|
|
|
});
|
|
|
|
|
2019-04-04 14:32:56 +02:00
|
|
|
$("#main_div").on("mouseenter", ".reaction_button", function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const elem = $(e.currentTarget);
|
|
|
|
const title = i18n.t("Add emoji reaction");
|
2019-04-04 14:32:56 +02:00
|
|
|
elem.tooltip({
|
|
|
|
title: title + " (:)",
|
|
|
|
trigger: 'hover',
|
|
|
|
placement: 'bottom',
|
|
|
|
animation: false,
|
|
|
|
});
|
|
|
|
elem.tooltip('show');
|
|
|
|
$(".tooltip-arrow").remove();
|
|
|
|
});
|
|
|
|
|
|
|
|
$('#main_div').on('mouseleave', '.reaction_button', function (e) {
|
|
|
|
e.stopPropagation();
|
2019-04-04 14:56:54 +02:00
|
|
|
$(e.currentTarget).tooltip('hide');
|
2019-04-04 14:32:56 +02:00
|
|
|
});
|
|
|
|
|
2017-04-27 07:27:25 +02:00
|
|
|
$("body").on("click", ".actions_popover .reaction_button", function (e) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const message_id = $(e.currentTarget).data('message-id');
|
2017-04-27 07:27:25 +02:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
// HACK: Because we need the popover to be based off an
|
|
|
|
// element that definitely exists in the page even if the
|
|
|
|
// message wasn't sent by us and thus the .reaction_hover
|
|
|
|
// element is not present, we use the message's
|
2018-10-15 16:25:18 +02:00
|
|
|
// .fa-chevron-down element as the base for the popover.
|
2019-11-02 00:06:25 +01:00
|
|
|
const elem = $(".selected_message .actions_hover")[0];
|
2019-10-25 09:45:13 +02:00
|
|
|
exports.toggle_emoji_popover(elem, message_id);
|
2017-04-27 07:27:25 +02:00
|
|
|
});
|
2017-05-30 00:50:45 +02:00
|
|
|
|
2017-07-19 00:28:56 +02:00
|
|
|
$("body").on("click", ".emoji-popover-tab-item", function (e) {
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
2020-02-08 05:31:13 +01:00
|
|
|
const offset = section_head_offsets.find(function (o) {
|
2017-07-19 00:28:56 +02:00
|
|
|
return o.section === $(this).attr("data-tab-name");
|
|
|
|
}.bind(this));
|
|
|
|
|
|
|
|
if (offset) {
|
2019-03-01 01:40:05 +01:00
|
|
|
ui.get_scroll_element($(".emoji-popover-emoji-map")).scrollTop(offset.position_y);
|
2017-07-19 00:28:56 +02:00
|
|
|
}
|
|
|
|
});
|
2017-08-27 22:15:02 +02:00
|
|
|
|
|
|
|
$("body").on("click", ".emoji-popover-filter", function () {
|
|
|
|
reset_emoji_showcase();
|
|
|
|
});
|
2017-09-29 22:14:57 +02:00
|
|
|
|
2017-10-05 18:11:03 +02:00
|
|
|
$("body").on("mouseenter", ".emoji-popover-emoji", function () {
|
2019-11-02 00:06:25 +01:00
|
|
|
const emoji_id = $(this).data("emoji-id");
|
|
|
|
const emoji_coordinates = get_emoji_coordinates(emoji_id);
|
2017-10-05 16:38:01 +02:00
|
|
|
|
2017-10-05 21:20:24 +02:00
|
|
|
may_be_change_focused_emoji(emoji_coordinates.section, emoji_coordinates.index, true);
|
2017-09-29 22:14:57 +02:00
|
|
|
});
|
2017-04-27 07:27:25 +02:00
|
|
|
};
|
|
|
|
|
2017-05-10 19:46:20 +02:00
|
|
|
exports.is_composition = function (emoji) {
|
2017-07-19 00:28:56 +02:00
|
|
|
return $(emoji).hasClass('composition');
|
2017-05-10 19:46:20 +02:00
|
|
|
};
|
|
|
|
|
2017-08-16 23:54:26 +02:00
|
|
|
exports.initialize = function () {
|
2017-07-19 00:28:56 +02:00
|
|
|
exports.generate_emoji_picker_data(emoji.active_realm_emojis);
|
2017-08-16 23:54:26 +02:00
|
|
|
};
|
2017-07-19 00:28:56 +02:00
|
|
|
|
2019-10-25 09:45:13 +02:00
|
|
|
window.emoji_picker = exports;
|