zulip/static/js/info_overlay.js

226 lines
6.5 KiB
JavaScript
Raw Normal View History

import $ from "jquery";
import render_markdown_help from "../templates/markdown_help.hbs";
import * as browser_history from "./browser_history";
import * as common from "./common";
import * as components from "./components";
import * as keydown_util from "./keydown_util";
import * as markdown from "./markdown";
import * as overlays from "./overlays";
import * as popovers from "./popovers";
import * as rendered_markdown from "./rendered_markdown";
import * as ui from "./ui";
import * as util from "./util";
// Make it explicit that our toggler is undefined until
// set_up_toggler is called.
export let toggler;
const markdown_help_rows = [
{
markdown: "*italic*",
usage_html: "(or <kbd>Ctrl</kbd>+<kbd>I</kbd>)",
},
{
markdown: "**bold**",
usage_html: "(or <kbd>Ctrl</kbd>+<kbd>B</kbd>)",
},
{
markdown: "~~strikethrough~~",
},
{
markdown: "[Zulip website](https://zulip.org)",
usage_html: "(or <kbd>Ctrl</kbd>+<kbd>Shift</kbd>+<kbd>L</kbd>)",
},
{
markdown: `\
* Milk
* Tea
* Green tea
* Black tea
* Oolong tea
* Coffee`,
},
{
markdown: `\
1. Milk
1. Tea
1. Coffee`,
},
{
markdown: ":heart:",
usage_html:
'(and <a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank" rel="noopener noreferrer">many others</a>, from the <a href="https://code.google.com/p/noto/" target="_blank" rel="noopener noreferrer">Noto Project</a>)',
},
{
markdown: "@**Joe Smith**",
usage_html: "(autocompletes from @joe)",
output_html: '<p><span class="user-mention">@Joe Smith</span></p>',
effect_html: "(notifies Joe Smith)",
},
{
markdown: "@_**Joe Smith**",
usage_html: "(autocompletes from @_joe)",
output_html: '<p><span class="user-mention">Joe Smith</span></p>',
effect_html: "(links to profile but doesn't notify Joe Smith)",
},
{
markdown: "@**all**",
effect_html: "(notifies all recipients)",
},
{
markdown: "#**streamName**",
output_html: "<p><a>#streamName</a></p>",
effect_html: "(links to a stream)",
},
{
markdown: "/me is busy working",
output_html: '<p><span class="sender_name-in-status">Iago</span> is busy working</p>',
usage_html: "(send a status message as user Iago)",
},
{
markdown: "Some inline `code`",
},
{
markdown: `\
\`\`\`
def zulip():
print "Zulip"
\`\`\``,
},
{
markdown: `\
\`\`\`python
def zulip():
print "Zulip"
\`\`\``,
output_html: `\
<div class="codehilite"><pre><span class="k">def</span> <span class="nf">zulip</span><span class="p">():</span>
<span class="k">print</span> <span class="s">"Zulip"</span></pre></div>`,
},
{
note_html: i18n.t(
'To add syntax highlighting to a multi-line code block, add the language\'s <b>first</b> <a target="_blank" rel="noopener noreferrer" href="https://pygments.org/docs/lexers/">Pygments short name</a> after the first set of back-ticks. You can also make a code block by indenting each line with 4 spaces.',
),
},
{
markdown: "> Quoted",
},
{
markdown: `\
\`\`\`quote
Quoted block
\`\`\``,
},
{
markdown: `\
\`\`\`spoiler Always visible heading
This text won't be visible until the user clicks.
\`\`\``,
},
{
markdown: "Some inline math $$ e^{i \\pi} + 1 = 0 $$",
},
{
markdown: `\
\`\`\`math
\\int_{0}^{1} f(x) dx
\`\`\``,
},
{
note_html: i18n.t(
'You can also make <a target="_blank" rel="noopener noreferrer" href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#wiki-tables">tables</a> with this <a target="_blank" rel="noopener noreferrer" href="https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#wiki-tables">Markdown-ish table syntax</a>.',
),
},
];
export function set_up_toggler() {
for (const row of markdown_help_rows) {
if (row.markdown && !row.output_html) {
const message = {raw_content: row.markdown};
markdown.apply_markdown(message);
row.output_html = util.clean_user_content_links(message.content);
}
}
const $markdown_help = $(render_markdown_help({markdown_help_rows}));
$markdown_help.find(".rendered_markdown").each(function () {
rendered_markdown.update_elements($(this));
});
$(".informational-overlays .overlay-body").append($markdown_help);
const opts = {
selected: 0,
child_wants_focus: true,
values: [
{label: i18n.t("Keyboard shortcuts"), key: "keyboard-shortcuts"},
{label: i18n.t("Message formatting"), key: "message-formatting"},
{label: i18n.t("Search operators"), key: "search-operators"},
],
callback(name, key) {
$(".overlay-modal").hide();
$(`#${CSS.escape(key)}`).show();
ui.get_scroll_element($(`#${CSS.escape(key)}`).find(".modal-body")).trigger("focus");
},
};
toggler = components.toggle(opts);
const elem = toggler.get();
elem.addClass("large allow-overflow");
const modals = opts.values.map((item) => {
const key = item.key; // e.g. message-formatting
const modal = $(`#${CSS.escape(key)}`).find(".modal-body");
return modal;
});
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 modal of modals) {
ui.get_scroll_element(modal).prop("tabindex", 0);
keydown_util.handle({
elem: modal,
handlers: {
left_arrow: toggler.maybe_go_left,
right_arrow: toggler.maybe_go_right,
},
});
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
}
$(".informational-overlays .overlay-tabs").append(elem);
common.adjust_mac_shortcuts(".hotkeys_table .hotkey kbd");
common.adjust_mac_shortcuts("#markdown-instructions kbd");
}
export function show(target) {
if (!toggler) {
set_up_toggler();
}
const overlay = $(".informational-overlays");
if (!overlay.hasClass("show")) {
overlays.open_overlay({
name: "informationalOverlays",
overlay,
on_close() {
browser_history.exit_overlay();
},
});
}
if (target) {
toggler.goto(target);
}
}
export function maybe_show_keyboard_shortcuts() {
if (overlays.is_active()) {
return;
}
if (popovers.any_active()) {
return;
}
show("keyboard-shortcuts");
}