mirror of
				https://github.com/zulip/zulip.git
				synced 2025-11-04 05:53:43 +00:00 
			
		
		
		
	ts: Migrate user_status module to TypeScript.
				
					
				
			Also removed an defensive if check from `emoji.ts` since it is not needed now that we have `emoji.ts` converted to TypeScript.
This commit is contained in:
		
				
					committed by
					
						
						Tim Abbott
					
				
			
			
				
	
			
			
			
						parent
						
							951e31a154
						
					
				
				
					commit
					dcf45da09c
				
			@@ -270,7 +270,7 @@ EXEMPT_FILES = make_set(
 | 
			
		||||
        "web/src/user_profile.js",
 | 
			
		||||
        "web/src/user_settings.ts",
 | 
			
		||||
        "web/src/user_sort.js",
 | 
			
		||||
        "web/src/user_status.js",
 | 
			
		||||
        "web/src/user_status.ts",
 | 
			
		||||
        "web/src/user_status_ui.js",
 | 
			
		||||
        "web/src/user_topic_popover.js",
 | 
			
		||||
        "web/src/user_topics.ts",
 | 
			
		||||
 
 | 
			
		||||
@@ -316,13 +316,9 @@ export function get_emoji_details_by_name(emoji_name: string): EmojiRenderingDet
 | 
			
		||||
 | 
			
		||||
export function get_emoji_details_for_rendering(opts: {
 | 
			
		||||
    emoji_name: string;
 | 
			
		||||
    emoji_code: string | number;
 | 
			
		||||
    emoji_code: string;
 | 
			
		||||
    reaction_type: string;
 | 
			
		||||
}): EmojiRenderingDetails {
 | 
			
		||||
    if (!opts.emoji_name || !opts.emoji_code || !opts.reaction_type) {
 | 
			
		||||
        throw new Error("Invalid params.");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (opts.reaction_type !== "unicode_emoji") {
 | 
			
		||||
        const realm_emoji = all_realm_emojis.get(opts.emoji_code);
 | 
			
		||||
        if (!realm_emoji) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,94 +0,0 @@
 | 
			
		||||
import * as channel from "./channel";
 | 
			
		||||
import * as emoji from "./emoji";
 | 
			
		||||
import {user_settings} from "./user_settings";
 | 
			
		||||
 | 
			
		||||
const user_info = new Map();
 | 
			
		||||
const user_status_emoji_info = new Map();
 | 
			
		||||
 | 
			
		||||
export function server_update_status(opts) {
 | 
			
		||||
    channel.post({
 | 
			
		||||
        url: "/json/users/me/status",
 | 
			
		||||
        data: {
 | 
			
		||||
            status_text: opts.status_text,
 | 
			
		||||
            emoji_name: opts.emoji_name,
 | 
			
		||||
            emoji_code: opts.emoji_code,
 | 
			
		||||
            reaction_type: opts.reaction_type,
 | 
			
		||||
        },
 | 
			
		||||
        success() {
 | 
			
		||||
            if (opts.success) {
 | 
			
		||||
                opts.success();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function server_invisible_mode_on() {
 | 
			
		||||
    channel.patch({
 | 
			
		||||
        url: "/json/settings",
 | 
			
		||||
        data: {
 | 
			
		||||
            presence_enabled: false,
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function server_invisible_mode_off() {
 | 
			
		||||
    channel.patch({
 | 
			
		||||
        url: "/json/settings",
 | 
			
		||||
        data: {
 | 
			
		||||
            presence_enabled: true,
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function get_status_text(user_id) {
 | 
			
		||||
    return user_info.get(user_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function set_status_text(opts) {
 | 
			
		||||
    if (!opts.status_text) {
 | 
			
		||||
        user_info.delete(opts.user_id);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    user_info.set(opts.user_id, opts.status_text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function get_status_emoji(user_id) {
 | 
			
		||||
    return user_status_emoji_info.get(user_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function set_status_emoji(opts) {
 | 
			
		||||
    if (!opts.emoji_name) {
 | 
			
		||||
        user_status_emoji_info.delete(opts.user_id);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    user_status_emoji_info.set(opts.user_id, {
 | 
			
		||||
        emoji_alt_code: user_settings.emojiset === "text",
 | 
			
		||||
        ...emoji.get_emoji_details_for_rendering({
 | 
			
		||||
            emoji_name: opts.emoji_name,
 | 
			
		||||
            emoji_code: opts.emoji_code,
 | 
			
		||||
            reaction_type: opts.reaction_type,
 | 
			
		||||
        }),
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function initialize(params) {
 | 
			
		||||
    user_info.clear();
 | 
			
		||||
 | 
			
		||||
    for (const [str_user_id, dct] of Object.entries(params.user_status)) {
 | 
			
		||||
        // JSON does not allow integer keys, so we
 | 
			
		||||
        // convert them here.
 | 
			
		||||
        const user_id = Number.parseInt(str_user_id, 10);
 | 
			
		||||
 | 
			
		||||
        if (dct.status_text) {
 | 
			
		||||
            user_info.set(user_id, dct.status_text);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (dct.emoji_name) {
 | 
			
		||||
            user_status_emoji_info.set(user_id, {
 | 
			
		||||
                ...emoji.get_emoji_details_for_rendering(dct),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								web/src/user_status.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								web/src/user_status.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
import {z} from "zod";
 | 
			
		||||
 | 
			
		||||
import * as channel from "./channel";
 | 
			
		||||
import * as emoji from "./emoji";
 | 
			
		||||
import type {EmojiRenderingDetails} from "./emoji";
 | 
			
		||||
import {user_settings} from "./user_settings";
 | 
			
		||||
 | 
			
		||||
export type UserStatus = z.infer<typeof user_status_schema>;
 | 
			
		||||
export type UserStatusEmojiInfo = EmojiRenderingDetails & {
 | 
			
		||||
    emoji_alt_code?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type UserStatusEvent = z.infer<typeof user_status_event_schema>;
 | 
			
		||||
 | 
			
		||||
const user_status_event_schema = z.object({
 | 
			
		||||
    id: z.number(),
 | 
			
		||||
    type: z.literal("user_status"),
 | 
			
		||||
    user_id: z.number(),
 | 
			
		||||
    away: z.boolean().optional(),
 | 
			
		||||
    status_text: z.string(),
 | 
			
		||||
    emoji_name: z.string(),
 | 
			
		||||
    emoji_code: z.string(),
 | 
			
		||||
    reaction_type: z.string(),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const user_status_schema = z.union([
 | 
			
		||||
    z.object({
 | 
			
		||||
        status_text: z.string().optional(),
 | 
			
		||||
        emoji_name: z.string(),
 | 
			
		||||
        emoji_code: z.string(),
 | 
			
		||||
        reaction_type: z.string(),
 | 
			
		||||
        away: z.boolean().optional(),
 | 
			
		||||
    }),
 | 
			
		||||
    z.object({
 | 
			
		||||
        emoji_name: z.undefined(),
 | 
			
		||||
        status_text: z.string().optional(),
 | 
			
		||||
        away: z.boolean().optional(),
 | 
			
		||||
    }),
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
const user_status_param_schema = z.record(z.coerce.number(), user_status_schema);
 | 
			
		||||
 | 
			
		||||
const user_info = new Map<number, string>();
 | 
			
		||||
const user_status_emoji_info = new Map<number, UserStatusEmojiInfo>();
 | 
			
		||||
 | 
			
		||||
export function server_update_status(opts: {
 | 
			
		||||
    status_text: string;
 | 
			
		||||
    emoji_name: string;
 | 
			
		||||
    emoji_code: string;
 | 
			
		||||
    reaction_type?: string;
 | 
			
		||||
    success?: () => void;
 | 
			
		||||
}): void {
 | 
			
		||||
    void channel.post({
 | 
			
		||||
        url: "/json/users/me/status",
 | 
			
		||||
        data: {
 | 
			
		||||
            status_text: opts.status_text,
 | 
			
		||||
            emoji_name: opts.emoji_name,
 | 
			
		||||
            emoji_code: opts.emoji_code,
 | 
			
		||||
            reaction_type: opts.reaction_type,
 | 
			
		||||
        },
 | 
			
		||||
        success() {
 | 
			
		||||
            if (opts.success) {
 | 
			
		||||
                opts.success();
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function server_invisible_mode_on(): void {
 | 
			
		||||
    void channel.patch({
 | 
			
		||||
        url: "/json/settings",
 | 
			
		||||
        data: {
 | 
			
		||||
            presence_enabled: false,
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function server_invisible_mode_off(): void {
 | 
			
		||||
    void channel.patch({
 | 
			
		||||
        url: "/json/settings",
 | 
			
		||||
        data: {
 | 
			
		||||
            presence_enabled: true,
 | 
			
		||||
        },
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function get_status_text(user_id: number): string | undefined {
 | 
			
		||||
    return user_info.get(user_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function set_status_text(opts: {user_id: number; status_text: string}): void {
 | 
			
		||||
    if (!opts.status_text) {
 | 
			
		||||
        user_info.delete(opts.user_id);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    user_info.set(opts.user_id, opts.status_text);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function get_status_emoji(user_id: number): UserStatusEmojiInfo | undefined {
 | 
			
		||||
    return user_status_emoji_info.get(user_id);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function set_status_emoji(event: UserStatusEvent): void {
 | 
			
		||||
    const opts = user_status_event_schema.parse(event);
 | 
			
		||||
 | 
			
		||||
    if (!opts.emoji_name) {
 | 
			
		||||
        user_status_emoji_info.delete(opts.user_id);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    user_status_emoji_info.set(opts.user_id, {
 | 
			
		||||
        emoji_alt_code: user_settings.emojiset === "text",
 | 
			
		||||
        ...emoji.get_emoji_details_for_rendering({
 | 
			
		||||
            emoji_name: opts.emoji_name,
 | 
			
		||||
            emoji_code: opts.emoji_code,
 | 
			
		||||
            reaction_type: opts.reaction_type,
 | 
			
		||||
        }),
 | 
			
		||||
    });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function initialize(params: {user_status: object}): void {
 | 
			
		||||
    user_info.clear();
 | 
			
		||||
 | 
			
		||||
    const user_status = user_status_param_schema.parse(params.user_status);
 | 
			
		||||
 | 
			
		||||
    for (const [str_user_id, dct] of Object.entries(user_status)) {
 | 
			
		||||
        // JSON does not allow integer keys, so we
 | 
			
		||||
        // convert them here.
 | 
			
		||||
        const user_id = Number.parseInt(str_user_id, 10);
 | 
			
		||||
 | 
			
		||||
        if (dct.status_text) {
 | 
			
		||||
            user_info.set(user_id, dct.status_text);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (dct.emoji_name) {
 | 
			
		||||
            user_status_emoji_info.set(user_id, {
 | 
			
		||||
                ...emoji.get_emoji_details_for_rendering(dct),
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -489,6 +489,7 @@ test("get_items_for_users", () => {
 | 
			
		||||
    set_presence(alice.user_id, "offline");
 | 
			
		||||
    user_settings.emojiset = "google";
 | 
			
		||||
    user_settings.user_list_style = 2;
 | 
			
		||||
 | 
			
		||||
    const status_emoji_info = {
 | 
			
		||||
        emoji_alt_code: false,
 | 
			
		||||
        emoji_name: "car",
 | 
			
		||||
@@ -496,9 +497,16 @@ test("get_items_for_users", () => {
 | 
			
		||||
        reaction_type: "unicode_emoji",
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const status_emoji_info_event = {
 | 
			
		||||
        id: 1,
 | 
			
		||||
        type: "user_status",
 | 
			
		||||
        status_text: "",
 | 
			
		||||
        ...status_emoji_info,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const user_ids = [me.user_id, alice.user_id, fred.user_id];
 | 
			
		||||
    for (const user_id of user_ids) {
 | 
			
		||||
        user_status.set_status_emoji({user_id, ...status_emoji_info});
 | 
			
		||||
        user_status.set_status_emoji({user_id, ...status_emoji_info_event});
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const user_list_style = {
 | 
			
		||||
 
 | 
			
		||||
@@ -1152,9 +1152,10 @@ run_test("user_status", ({override}) => {
 | 
			
		||||
    {
 | 
			
		||||
        const stub = make_stub();
 | 
			
		||||
        override(activity_ui, "redraw_user", stub.f);
 | 
			
		||||
        override(compose_pm_pill, "get_user_ids", () => [event.user_id]);
 | 
			
		||||
        override(pm_list, "update_private_messages", noop);
 | 
			
		||||
        dispatch(event);
 | 
			
		||||
        assert.equal(stub.num_calls, 1);
 | 
			
		||||
        assert.equal(stub.num_calls, 2);
 | 
			
		||||
        const args = stub.get_args("user_id");
 | 
			
		||||
        assert_same(args.user_id, test_user.user_id);
 | 
			
		||||
        const emoji_info = user_status.get_status_emoji(test_user.user_id);
 | 
			
		||||
 
 | 
			
		||||
@@ -1068,11 +1068,13 @@ exports.fixtures = {
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    user_status__set_status_emoji: {
 | 
			
		||||
        id: 1,
 | 
			
		||||
        type: "user_status",
 | 
			
		||||
        user_id: test_user.user_id,
 | 
			
		||||
        emoji_name: "smiley",
 | 
			
		||||
        emoji_code: "1f603",
 | 
			
		||||
        reaction_type: "unicode_emoji",
 | 
			
		||||
        status_text: "",
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    user_status__set_status_text: {
 | 
			
		||||
 
 | 
			
		||||
@@ -60,10 +60,13 @@ run_test("basics", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    user_status.set_status_emoji({
 | 
			
		||||
        id: 1,
 | 
			
		||||
        user_id: 5,
 | 
			
		||||
        type: "user_status",
 | 
			
		||||
        emoji_code: "991",
 | 
			
		||||
        emoji_name: "example_realm_emoji",
 | 
			
		||||
        reaction_type: "realm_emoji",
 | 
			
		||||
        status_text: "",
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    assert.deepEqual(user_status.get_status_emoji(5), {
 | 
			
		||||
@@ -78,8 +81,13 @@ run_test("basics", () => {
 | 
			
		||||
    assert.equal(user_status.get_status_text(1), "in a meeting");
 | 
			
		||||
 | 
			
		||||
    user_status.set_status_text({
 | 
			
		||||
        id: 2,
 | 
			
		||||
        user_id: 2,
 | 
			
		||||
        type: "user_status",
 | 
			
		||||
        status_text: "out to lunch",
 | 
			
		||||
        emoji_name: "",
 | 
			
		||||
        emoji_code: "",
 | 
			
		||||
        reaction_type: "",
 | 
			
		||||
    });
 | 
			
		||||
    assert.equal(user_status.get_status_text(2), "out to lunch");
 | 
			
		||||
 | 
			
		||||
@@ -90,10 +98,13 @@ run_test("basics", () => {
 | 
			
		||||
    assert.equal(user_status.get_status_text(2), undefined);
 | 
			
		||||
 | 
			
		||||
    user_status.set_status_emoji({
 | 
			
		||||
        id: 3,
 | 
			
		||||
        user_id: 2,
 | 
			
		||||
        type: "user_status",
 | 
			
		||||
        emoji_name: "smiley",
 | 
			
		||||
        emoji_code: "1f603",
 | 
			
		||||
        reaction_type: "unicode_emoji",
 | 
			
		||||
        status_text: "",
 | 
			
		||||
    });
 | 
			
		||||
    assert.deepEqual(user_status.get_status_emoji(2), {
 | 
			
		||||
        emoji_name: "smiley",
 | 
			
		||||
@@ -103,10 +114,13 @@ run_test("basics", () => {
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    user_status.set_status_emoji({
 | 
			
		||||
        id: 4,
 | 
			
		||||
        user_id: 2,
 | 
			
		||||
        type: "user_status",
 | 
			
		||||
        emoji_name: "",
 | 
			
		||||
        emoji_code: "",
 | 
			
		||||
        reaction_type: "",
 | 
			
		||||
        status_text: "",
 | 
			
		||||
    });
 | 
			
		||||
    assert.deepEqual(user_status.get_status_emoji(2), undefined);
 | 
			
		||||
});
 | 
			
		||||
@@ -142,23 +156,28 @@ run_test("defensive checks", () => {
 | 
			
		||||
    assert.throws(
 | 
			
		||||
        () =>
 | 
			
		||||
            user_status.set_status_emoji({
 | 
			
		||||
                id: 1,
 | 
			
		||||
                status_text: "",
 | 
			
		||||
                type: "user_status",
 | 
			
		||||
                user_id: 5,
 | 
			
		||||
                emoji_name: "emoji",
 | 
			
		||||
                // no status code or reaction type.
 | 
			
		||||
            }),
 | 
			
		||||
        {
 | 
			
		||||
            name: "Error",
 | 
			
		||||
            message: "Invalid params.",
 | 
			
		||||
            name: "ZodError",
 | 
			
		||||
        },
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    assert.throws(
 | 
			
		||||
        () =>
 | 
			
		||||
            user_status.set_status_emoji({
 | 
			
		||||
                id: 2,
 | 
			
		||||
                type: "user_status",
 | 
			
		||||
                user_id: 5,
 | 
			
		||||
                reaction_type: "realm_emoji",
 | 
			
		||||
                emoji_name: "does_not_exist",
 | 
			
		||||
                emoji_code: "fake_code",
 | 
			
		||||
                status_text: "",
 | 
			
		||||
            }),
 | 
			
		||||
        {
 | 
			
		||||
            name: "Error",
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user