mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
These sigils will help make it easier to see that this is a special Zulip syntax feature, not just something the other user typed, which might help set the expectation that we're showing the time in the user's timezone. Tweaked by tabbott to improve variable/template naming.
240 lines
9.2 KiB
JavaScript
240 lines
9.2 KiB
JavaScript
import ClipboardJS from "clipboard";
|
|
import {parseISO, isValid} from "date-fns";
|
|
import $ from "jquery";
|
|
|
|
import copy_code_button from "../templates/copy_code_button.hbs";
|
|
import render_markdown_timestamp from "../templates/markdown_timestamp.hbs";
|
|
import view_code_in_playground from "../templates/view_code_in_playground.hbs";
|
|
|
|
import * as people from "./people";
|
|
import * as rtl from "./rtl";
|
|
import * as settings_config from "./settings_config";
|
|
import * as stream_data from "./stream_data";
|
|
import * as timerender from "./timerender";
|
|
import * as user_groups from "./user_groups";
|
|
|
|
/*
|
|
rendered_markdown
|
|
|
|
This module provides a single function 'update_elements' to
|
|
update any renamed users/streams/groups etc. and other
|
|
dynamic parts of our rendered messages.
|
|
|
|
Use this module wherever some Markdown rendered content
|
|
is being displayed.
|
|
*/
|
|
|
|
function get_user_id_for_mention_button(elem) {
|
|
const user_id_string = $(elem).attr("data-user-id");
|
|
// Handle legacy Markdown that was rendered before we cut
|
|
// over to using data-user-id.
|
|
const email = $(elem).attr("data-user-email");
|
|
|
|
if (user_id_string === "*" || email === "*") {
|
|
return "*";
|
|
}
|
|
|
|
if (user_id_string) {
|
|
return Number.parseInt(user_id_string, 10);
|
|
}
|
|
|
|
if (email) {
|
|
// Will return undefined if there's no match
|
|
const user = people.get_by_email(email);
|
|
if (user) {
|
|
return user.user_id;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function get_user_group_id_for_mention_button(elem) {
|
|
const user_group_id = $(elem).attr("data-user-group-id");
|
|
|
|
if (user_group_id) {
|
|
return Number.parseInt(user_group_id, 10);
|
|
}
|
|
|
|
return undefined;
|
|
}
|
|
|
|
// Helper function to update a mentioned user's name.
|
|
export function set_name_in_mention_element(element, name) {
|
|
if ($(element).hasClass("silent")) {
|
|
$(element).text(name);
|
|
} else {
|
|
$(element).text("@" + name);
|
|
}
|
|
}
|
|
|
|
export const update_elements = (content) => {
|
|
// Set the rtl class if the text has an rtl direction
|
|
if (rtl.get_direction(content.text()) === "rtl") {
|
|
content.addClass("rtl");
|
|
}
|
|
|
|
content.find(".user-mention").each(function () {
|
|
const user_id = get_user_id_for_mention_button(this);
|
|
// We give special highlights to the mention buttons
|
|
// that refer to the current user.
|
|
if (user_id === "*" || people.is_my_user_id(user_id)) {
|
|
// Either a wildcard mention or us, so mark it.
|
|
$(this).addClass("user-mention-me");
|
|
}
|
|
if (user_id && user_id !== "*" && !$(this).find(".highlight").length) {
|
|
// If it's a mention of a specific user, edit the
|
|
// mention text to show the user's current name,
|
|
// assuming that you're not searching for text
|
|
// inside the highlight.
|
|
const person = people.get_by_user_id(user_id, true);
|
|
if (person !== undefined) {
|
|
// Note that person might be undefined in some
|
|
// unpleasant corner cases involving data import.
|
|
set_name_in_mention_element(this, person.full_name);
|
|
}
|
|
}
|
|
});
|
|
|
|
content.find(".user-group-mention").each(function () {
|
|
const user_group_id = get_user_group_id_for_mention_button(this);
|
|
const user_group = user_groups.get_user_group_from_id(user_group_id, true);
|
|
if (user_group === undefined) {
|
|
// This is a user group the current user doesn't have
|
|
// data on. This can happen when user groups are
|
|
// deleted.
|
|
blueslip.info("Rendered unexpected user group " + user_group_id);
|
|
return;
|
|
}
|
|
|
|
const my_user_id = people.my_current_user_id();
|
|
// Mark user group you're a member of.
|
|
if (user_groups.is_member_of(user_group_id, my_user_id)) {
|
|
$(this).addClass("user-mention-me");
|
|
}
|
|
|
|
if (user_group_id && !$(this).find(".highlight").length) {
|
|
// Edit the mention to show the current name for the
|
|
// user group, if its not in search.
|
|
$(this).text("@" + user_group.name);
|
|
}
|
|
});
|
|
|
|
content.find("a.stream").each(function () {
|
|
const stream_id = Number.parseInt($(this).attr("data-stream-id"), 10);
|
|
if (stream_id && !$(this).find(".highlight").length) {
|
|
// Display the current name for stream if it is not
|
|
// being displayed in search highlight.
|
|
const stream_name = stream_data.maybe_get_stream_name(stream_id);
|
|
if (stream_name !== undefined) {
|
|
// If the stream has been deleted,
|
|
// stream_data.maybe_get_stream_name might return
|
|
// undefined. Otherwise, display the current stream name.
|
|
$(this).text("#" + stream_name);
|
|
}
|
|
}
|
|
});
|
|
|
|
content.find("a.stream-topic").each(function () {
|
|
const stream_id = Number.parseInt($(this).attr("data-stream-id"), 10);
|
|
if (stream_id && !$(this).find(".highlight").length) {
|
|
// Display the current name for stream if it is not
|
|
// being displayed in search highlight.
|
|
const text = $(this).text();
|
|
const topic = text.split(">", 2)[1];
|
|
const stream_name = stream_data.maybe_get_stream_name(stream_id);
|
|
if (stream_name !== undefined) {
|
|
// If the stream has been deleted,
|
|
// stream_data.maybe_get_stream_name might return
|
|
// undefined. Otherwise, display the current stream name.
|
|
$(this).text("#" + stream_name + " > " + topic);
|
|
}
|
|
}
|
|
});
|
|
|
|
content.find("time").each(function () {
|
|
// Populate each timestamp span with mentioned time
|
|
// in user's local timezone.
|
|
const time_str = $(this).attr("datetime");
|
|
if (time_str === undefined) {
|
|
return;
|
|
}
|
|
|
|
const timestamp = parseISO(time_str);
|
|
if (isValid(timestamp)) {
|
|
const text = $(this).text();
|
|
const rendered_time = timerender.render_markdown_timestamp(timestamp, text);
|
|
const rendered_timestamp = render_markdown_timestamp({
|
|
text: rendered_time.text,
|
|
});
|
|
$(this).html(rendered_timestamp);
|
|
$(this).attr("title", rendered_time.title);
|
|
} else {
|
|
// This shouldn't happen. If it does, we're very interested in debugging it.
|
|
blueslip.error(`Could not parse datetime supplied by backend: ${time_str}`);
|
|
}
|
|
});
|
|
|
|
content.find("span.timestamp-error").each(function () {
|
|
const time_str = $(this).text().replace("Invalid time format: ", "");
|
|
const text = i18n.t("Invalid time format: __timestamp__", {timestamp: time_str});
|
|
$(this).text(text);
|
|
});
|
|
|
|
content.find("div.spoiler-header").each(function () {
|
|
// If a spoiler block has no header content, it should have a default header.
|
|
// We do this client side to allow for i18n by the client.
|
|
if ($(this).html().trim().length === 0) {
|
|
$(this).append(`<p>${i18n.t("Spoiler")}</p>`);
|
|
}
|
|
|
|
// Add the expand/collapse button to spoiler blocks
|
|
const toggle_button_html =
|
|
'<span class="spoiler-button" aria-expanded="false"><span class="spoiler-arrow"></span></span>';
|
|
$(this).prepend(toggle_button_html);
|
|
});
|
|
|
|
// Display the view-code-in-playground and the copy-to-clipboard button inside the div.codehilite element.
|
|
content.find("div.codehilite").each(function () {
|
|
const $codehilite = $(this);
|
|
const $pre = $codehilite.find("pre");
|
|
const fenced_code_lang = $codehilite.data("code-language");
|
|
if (fenced_code_lang !== undefined) {
|
|
const playground_info = settings_config.get_playground_info_for_languages(
|
|
fenced_code_lang,
|
|
);
|
|
if (playground_info !== undefined) {
|
|
// If a playground is configured for this language,
|
|
// offer to view the code in that playground. When
|
|
// there are multiple playgrounds, we display a
|
|
// popover listing the options.
|
|
let title = i18n.t("View in playground");
|
|
const view_in_playground_button = $(view_code_in_playground());
|
|
$pre.prepend(view_in_playground_button);
|
|
if (playground_info.length === 1) {
|
|
title = i18n.t(`View in ${playground_info[0].name}`);
|
|
} else {
|
|
view_in_playground_button.attr("aria-haspopup", "true");
|
|
}
|
|
view_in_playground_button.attr("title", title);
|
|
view_in_playground_button.attr("aria-label", title);
|
|
}
|
|
}
|
|
const copy_button = $(copy_code_button());
|
|
$pre.prepend(copy_button);
|
|
new ClipboardJS(copy_button[0], {
|
|
text(copy_element) {
|
|
return $(copy_element).siblings("code").text();
|
|
},
|
|
});
|
|
});
|
|
|
|
// Display emoji (including realm emoji) as text if
|
|
// page_params.emojiset is 'text'.
|
|
if (page_params.emojiset === "text") {
|
|
content.find(".emoji").replaceWith(function () {
|
|
const text = $(this).attr("title");
|
|
return ":" + text + ":";
|
|
});
|
|
}
|
|
};
|