zform: Convert module to typescript.

This commit is contained in:
Evy Kassirer
2025-09-11 15:01:34 -07:00
committed by Tim Abbott
parent ec78f0551b
commit 635d99ba72
6 changed files with 43 additions and 43 deletions

View File

@@ -54,7 +54,7 @@ Some important code entities for the widget implementation are:
- `web/src/submessage.js` - `web/src/submessage.js`
- `web/src/poll_widget.js` - `web/src/poll_widget.js`
- `web/src/widgetize.ts` - `web/src/widgetize.ts`
- `web/src/zform.js` - `web/src/zform.ts`
- `web/templates/widgets/` - `web/templates/widgets/`
- `zerver/lib/widget.py` - `zerver/lib/widget.py`
- `zerver/views/submessage.py` - `zerver/views/submessage.py`
@@ -308,7 +308,7 @@ widgets.todo = todo_widget;
widgets.zform = zform; widgets.zform = zform;
``` ```
The code in `web/src/zform.js` renders the form (not The code in `web/src/zform.ts` renders the form (not
shown here) and then sets up a click handler like below: shown here) and then sets up a click handler like below:
```js ```js

View File

@@ -307,7 +307,7 @@ EXEMPT_FILES = make_set(
"web/src/views_util.ts", "web/src/views_util.ts",
"web/src/widget_modal.ts", "web/src/widget_modal.ts",
"web/src/zcommand.ts", "web/src/zcommand.ts",
"web/src/zform.js", "web/src/zform.ts",
"web/src/zulip_test.ts", "web/src/zulip_test.ts",
# Test library code isn't always fully used. # Test library code isn't always fully used.
"web/tests/lib/example_user.cjs", "web/tests/lib/example_user.cjs",

View File

@@ -12,20 +12,18 @@ import * as widgetize from "./widgetize.ts";
export type Submessage = z.infer<typeof message_store.submessage_schema>; export type Submessage = z.infer<typeof message_store.submessage_schema>;
export const zform_widget_extra_data_schema = z.nullable( export const zform_widget_extra_data_schema = z.object({
z.object({ choices: z.array(
choices: z.array( z.object({
z.object({ type: z.string(),
type: z.string(), long_name: z.string(),
long_name: z.string(), reply: z.string(),
reply: z.string(), short_name: z.string(),
short_name: z.string(), }),
}), ),
), heading: z.string(),
heading: z.string(), type: z.literal("choices"),
type: z.literal("choices"), });
}),
);
const poll_widget_extra_data_schema = z.nullable( const poll_widget_extra_data_schema = z.nullable(
z.object({ z.object({
@@ -38,7 +36,10 @@ const widget_data_event_schema = z.object({
sender_id: z.number(), sender_id: z.number(),
data: z.discriminatedUnion("widget_type", [ data: z.discriminatedUnion("widget_type", [
z.object({widget_type: z.literal("poll"), extra_data: poll_widget_extra_data_schema}), z.object({widget_type: z.literal("poll"), extra_data: poll_widget_extra_data_schema}),
z.object({widget_type: z.literal("zform"), extra_data: zform_widget_extra_data_schema}), z.object({
widget_type: z.literal("zform"),
extra_data: z.nullable(zform_widget_extra_data_schema),
}),
z.object({ z.object({
widget_type: z.literal("todo"), widget_type: z.literal("todo"),
extra_data: todo_widget_extra_data_schema, extra_data: todo_widget_extra_data_schema,

View File

@@ -5,13 +5,7 @@ import * as message_lists from "./message_lists.ts";
import type {Message} from "./message_store.ts"; import type {Message} from "./message_store.ts";
import type {Event, PollWidgetExtraData, PollWidgetOutboundData} from "./poll_widget.ts"; import type {Event, PollWidgetExtraData, PollWidgetOutboundData} from "./poll_widget.ts";
import type {TodoWidgetExtraData, TodoWidgetOutboundData} from "./todo_widget.ts"; import type {TodoWidgetExtraData, TodoWidgetOutboundData} from "./todo_widget.ts";
import type {ZFormExtraData} from "./zform.ts";
// TODO: This ZFormExtraData type should be moved to web/src/zform.js when it will be migrated
type ZFormExtraData = {
type: string;
heading: string;
choices: {type: string; reply: string; long_name: string; short_name: string}[];
};
type WidgetExtraData = PollWidgetExtraData | TodoWidgetExtraData | ZFormExtraData | null; type WidgetExtraData = PollWidgetExtraData | TodoWidgetExtraData | ZFormExtraData | null;

View File

@@ -1,7 +1,7 @@
import * as poll_widget from "./poll_widget.ts"; import * as poll_widget from "./poll_widget.ts";
import * as todo_widget from "./todo_widget.ts"; import * as todo_widget from "./todo_widget.ts";
import * as widgetize from "./widgetize.ts"; import * as widgetize from "./widgetize.ts";
import * as zform from "./zform.js"; import * as zform from "./zform.ts";
export function initialize() { export function initialize() {
widgetize.widgets.set("poll", poll_widget); widgetize.widgets.set("poll", poll_widget);

View File

@@ -1,43 +1,51 @@
import $ from "jquery"; import $ from "jquery";
import type * as z from "zod/mini";
import render_widgets_zform_choices from "../templates/widgets/zform_choices.hbs"; import render_widgets_zform_choices from "../templates/widgets/zform_choices.hbs";
import * as blueslip from "./blueslip.ts"; import * as blueslip from "./blueslip.ts";
import type {Message} from "./message_store.ts";
import type {Event} from "./poll_widget.ts";
import {zform_widget_extra_data_schema} from "./submessage.ts"; import {zform_widget_extra_data_schema} from "./submessage.ts";
import * as transmit from "./transmit.ts"; import * as transmit from "./transmit.ts";
export function activate(opts) { export type ZFormExtraData = z.infer<typeof zform_widget_extra_data_schema>;
const self = {};
export function activate(opts: {
$elem: JQuery;
extra_data: unknown;
message: Message;
}): {handle_events: (events: Event[]) => void} | undefined {
const $outer_elem = opts.$elem; const $outer_elem = opts.$elem;
const parse_result = zform_widget_extra_data_schema.safeParse(opts.extra_data); const parse_result = zform_widget_extra_data_schema.safeParse(opts.extra_data);
if (!parse_result.success) { if (!parse_result.success) {
blueslip.warn("invalid zform extra data", parse_result.error.issues); blueslip.warn("invalid zform extra data", {issues: parse_result.error.issues});
return undefined; return undefined;
} }
const {data} = parse_result; const {data} = parse_result;
function make_choices(data) { function make_choices(data: ZFormExtraData): JQuery {
// Assign idx values to each of our choices so that // Assign idx values to each of our choices so that
// our template can create data-idx values for our // our template can create data-idx values for our
// JS code to use later. // JS code to use later.
for (const [idx, choice] of data.choices.entries()) { const data_with_choices_with_idx = {
choice.idx = idx; ...data,
} choices: data.choices.map((choice, idx) => ({...choice, idx})),
};
const html = render_widgets_zform_choices(data); const html = render_widgets_zform_choices(data_with_choices_with_idx);
const $elem = $(html); const $elem = $(html);
$elem.find("button").on("click", (e) => { $elem.find("button").on("click", (e) => {
e.stopPropagation(); e.stopPropagation();
// Grab our index from the markup. // Grab our index from the markup.
const idx = $(e.target).attr("data-idx"); const idx = Number.parseInt($(e.target).attr("data-idx")!, 10);
// Use the index from the markup to dereference our // Use the index from the markup to dereference our
// data structure. // data structure.
const reply_content = data.choices[idx].reply; const reply_content = data.choices[idx]!.reply;
transmit.reply_message(opts.message, reply_content); transmit.reply_message(opts.message, reply_content);
}); });
@@ -45,16 +53,13 @@ export function activate(opts) {
return $elem; return $elem;
} }
function render() { function render(): void {
let rendered_widget;
if (data.type === "choices") { if (data.type === "choices") {
rendered_widget = make_choices(data); $outer_elem.html(make_choices(data).html());
$outer_elem.html(rendered_widget);
} }
} }
self.handle_events = function (events) { const handle_events = function (events: Event[]): void {
if (events) { if (events) {
blueslip.info("unexpected"); blueslip.info("unexpected");
} }
@@ -63,5 +68,5 @@ export function activate(opts) {
render(); render();
return self; return {handle_events};
} }