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/poll_widget.js`
- `web/src/widgetize.ts`
- `web/src/zform.js`
- `web/src/zform.ts`
- `web/templates/widgets/`
- `zerver/lib/widget.py`
- `zerver/views/submessage.py`
@@ -308,7 +308,7 @@ widgets.todo = todo_widget;
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:
```js

View File

@@ -307,7 +307,7 @@ EXEMPT_FILES = make_set(
"web/src/views_util.ts",
"web/src/widget_modal.ts",
"web/src/zcommand.ts",
"web/src/zform.js",
"web/src/zform.ts",
"web/src/zulip_test.ts",
# Test library code isn't always fully used.
"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 const zform_widget_extra_data_schema = z.nullable(
z.object({
choices: z.array(
z.object({
type: z.string(),
long_name: z.string(),
reply: z.string(),
short_name: z.string(),
}),
),
heading: z.string(),
type: z.literal("choices"),
}),
);
export const zform_widget_extra_data_schema = z.object({
choices: z.array(
z.object({
type: z.string(),
long_name: z.string(),
reply: z.string(),
short_name: z.string(),
}),
),
heading: z.string(),
type: z.literal("choices"),
});
const poll_widget_extra_data_schema = z.nullable(
z.object({
@@ -38,7 +36,10 @@ const widget_data_event_schema = z.object({
sender_id: z.number(),
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("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({
widget_type: z.literal("todo"),
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 {Event, PollWidgetExtraData, PollWidgetOutboundData} from "./poll_widget.ts";
import type {TodoWidgetExtraData, TodoWidgetOutboundData} from "./todo_widget.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}[];
};
import type {ZFormExtraData} from "./zform.ts";
type WidgetExtraData = PollWidgetExtraData | TodoWidgetExtraData | ZFormExtraData | null;

View File

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

View File

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