@shwfed/config 2.9.4 → 2.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/dist/mcp.mjs +558 -102
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +1 -0
  4. package/dist/preview/assets/FieldGroup.vue_vue_type_script_setup_true_lang-BZCeJsQD.js +1 -0
  5. package/dist/preview/assets/badge-muf_N5bh.js +1 -0
  6. package/dist/preview/assets/config-BDWlpbYr.js +1 -0
  7. package/dist/preview/assets/config-BbrX8RJn.js +1 -0
  8. package/dist/preview/assets/config-BeW9pIO6.js +1 -0
  9. package/dist/preview/assets/config-BmmWdZ8T.js +1 -0
  10. package/dist/preview/assets/config-CX1fFOhS.js +1 -0
  11. package/dist/preview/assets/config-CZHJLTaX.js +1 -0
  12. package/dist/preview/assets/config-CoEYHQXh.js +1 -0
  13. package/dist/preview/assets/config-DkiDpKM2.js +1 -0
  14. package/dist/preview/assets/config-DygE8cbP.js +1 -0
  15. package/dist/preview/assets/config-qlTY-CeE.js +1 -0
  16. package/dist/preview/assets/config-r9XM4u1X.js +1 -0
  17. package/dist/preview/assets/definition.vue_vue_type_script_setup_true_lang-BFjgX5YL.js +1 -0
  18. package/dist/preview/assets/{index-CHkjJPVP.js → index-CHKIlDe0.js} +1 -1
  19. package/dist/preview/assets/{index-cyMSopN_.js → index-CJHF5AcW.js} +217 -186
  20. package/dist/preview/assets/index-OJPhCe6w.js +1 -0
  21. package/dist/preview/assets/index-vPcvbp7e.css +1 -0
  22. package/dist/preview/assets/item-9Thi-zcK.js +1 -0
  23. package/dist/preview/assets/runtime-B2Q3DRbZ.js +1 -0
  24. package/dist/preview/assets/{runtime-Dy1bI4VO.js → runtime-BnExhm-v.js} +1 -1
  25. package/dist/preview/assets/runtime-BrCQ7TVd.js +1 -0
  26. package/dist/preview/assets/runtime-BstyXj1z.js +1 -0
  27. package/dist/preview/assets/runtime-ByhubOtA.js +1 -0
  28. package/dist/preview/assets/runtime-CE6eAbTE.js +1 -0
  29. package/dist/preview/assets/runtime-CdDJ6rEl.js +1 -0
  30. package/dist/preview/assets/runtime-DEHzrByN.js +1 -0
  31. package/dist/preview/assets/runtime-DM9caqw1.js +1 -0
  32. package/dist/preview/assets/{runtime-lZLYBBgn.js → runtime-DkLPBvxW.js} +1 -1
  33. package/dist/preview/index.html +2 -2
  34. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json/config.d.vue.ts +20 -0
  35. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json/config.vue.d.ts +20 -0
  36. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json/schema.d.ts +22 -0
  37. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json/schema.js +33 -1
  38. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json.confirm/config.d.vue.ts +20 -0
  39. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json.confirm/config.vue.d.ts +20 -0
  40. package/dist/runtime/components/actions/buttons/2026-04-18/com.shwfed.actions.button.http.request.json.confirm/schema.d.ts +10 -0
  41. package/dist/runtime/components/actions/buttons/2026-05-15/com.shwfed.actions.button.event.dispatch/config.d.vue.ts +4 -0
  42. package/dist/runtime/components/actions/buttons/2026-05-15/com.shwfed.actions.button.event.dispatch/config.vue.d.ts +4 -0
  43. package/dist/runtime/components/actions/buttons/2026-05-15/com.shwfed.actions.button.event.dispatch/schema.d.ts +2 -0
  44. package/dist/runtime/components/actions/buttons/2026-05-21/com.shwfed.actions.button.http.download/config.d.vue.ts +16 -0
  45. package/dist/runtime/components/actions/buttons/2026-05-21/com.shwfed.actions.button.http.download/config.vue.d.ts +16 -0
  46. package/dist/runtime/components/actions/buttons/2026-05-21/com.shwfed.actions.button.http.download/schema.d.ts +8 -0
  47. package/dist/runtime/components/actions/buttons/2026-05-24/com.shwfed.actions.button.state.write/config.d.vue.ts +4 -0
  48. package/dist/runtime/components/actions/buttons/2026-05-24/com.shwfed.actions.button.state.write/config.vue.d.ts +4 -0
  49. package/dist/runtime/components/actions/buttons/2026-05-24/com.shwfed.actions.button.state.write/schema.d.ts +2 -0
  50. package/dist/runtime/components/actions/buttons/2026-06-08/com.shwfed.actions.button.http.request.json.batch/config.d.vue.ts +51 -0
  51. package/dist/runtime/components/actions/buttons/2026-06-08/com.shwfed.actions.button.http.request.json.batch/config.vue +201 -0
  52. package/dist/runtime/components/actions/buttons/2026-06-08/com.shwfed.actions.button.http.request.json.batch/config.vue.d.ts +51 -0
  53. package/dist/runtime/components/actions/buttons/2026-06-08/com.shwfed.actions.button.http.request.json.batch/runtime.d.vue.ts +8 -0
  54. package/dist/runtime/components/actions/buttons/2026-06-08/com.shwfed.actions.button.http.request.json.batch/runtime.vue +37 -0
  55. package/dist/runtime/components/actions/buttons/2026-06-08/com.shwfed.actions.button.http.request.json.batch/runtime.vue.d.ts +8 -0
  56. package/dist/runtime/components/actions/buttons/2026-06-08/com.shwfed.actions.button.http.request.json.batch/schema.d.ts +30 -0
  57. package/dist/runtime/components/actions/buttons/2026-06-08/com.shwfed.actions.button.http.request.json.batch/schema.js +87 -0
  58. package/dist/runtime/components/actions/components/triggers-field.d.vue.ts +4 -0
  59. package/dist/runtime/components/actions/components/triggers-field.vue +65 -7
  60. package/dist/runtime/components/actions/components/triggers-field.vue.d.ts +4 -0
  61. package/dist/runtime/components/actions/config.vue +68 -1
  62. package/dist/runtime/components/actions/utils/resolve.d.ts +85 -2
  63. package/dist/runtime/components/actions/utils/resolve.js +122 -21
  64. package/dist/runtime/components/app.vue +59 -4
  65. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.d.vue.ts +2 -2
  66. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/config.vue.d.ts +2 -2
  67. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.d.vue.ts +2 -2
  68. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/runtime.vue.d.ts +2 -2
  69. package/dist/runtime/components/config/blocks/2026-05-06/com.shwfed.block.table/schema.d.ts +1 -1
  70. package/dist/runtime/components/config/index.vue +9 -2
  71. package/dist/runtime/components/config/schema.d.ts +6 -10
  72. package/dist/runtime/components/config/schema.js +12 -2
  73. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.d.vue.ts +2 -2
  74. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.number/config.vue.d.ts +2 -2
  75. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.d.vue.ts +2 -2
  76. package/dist/runtime/components/form/fields/2026-04-28/com.shwfed.form.field.numberrange/config.vue.d.ts +2 -2
  77. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.d.vue.ts +2 -2
  78. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/config.vue.d.ts +2 -2
  79. package/dist/runtime/components/form/fields/2026-05-13/com.shwfed.form.field.list/schema.d.ts +1 -1
  80. package/dist/runtime/components/operations/2026-06-09/com.shwfed.operation.alert/config.d.vue.ts +31 -0
  81. package/dist/runtime/components/operations/2026-06-09/com.shwfed.operation.alert/config.vue +89 -0
  82. package/dist/runtime/components/operations/2026-06-09/com.shwfed.operation.alert/config.vue.d.ts +31 -0
  83. package/dist/runtime/components/operations/2026-06-09/com.shwfed.operation.alert/runtime.d.ts +2 -0
  84. package/dist/runtime/components/operations/2026-06-09/com.shwfed.operation.alert/runtime.js +14 -0
  85. package/dist/runtime/components/operations/2026-06-09/com.shwfed.operation.alert/schema.d.ts +21 -0
  86. package/dist/runtime/components/operations/2026-06-09/com.shwfed.operation.alert/schema.js +36 -0
  87. package/dist/runtime/components/operations/utils/resolve.d.ts +48 -0
  88. package/dist/runtime/components/operations/utils/resolve.js +73 -0
  89. package/dist/runtime/components/table/columns/2026-05-13/com.shwfed.table.column.switch/schema.d.ts +2 -0
  90. package/dist/runtime/components/table/columns/2026-05-13/com.shwfed.table.column.switch.remote/schema.d.ts +2 -0
  91. package/dist/runtime/components/table/columns/2026-05-24/com.shwfed.table.column.combobox-single.remote.options-remote/config.d.vue.ts +4 -0
  92. package/dist/runtime/components/table/columns/2026-05-24/com.shwfed.table.column.combobox-single.remote.options-remote/config.vue.d.ts +4 -0
  93. package/dist/runtime/components/table/columns/2026-05-24/com.shwfed.table.column.combobox-single.remote.options-remote/schema.d.ts +2 -0
  94. package/dist/runtime/components/table/columns/2026-05-24/com.shwfed.table.column.combobox-single.remote.options-static/config.d.vue.ts +4 -0
  95. package/dist/runtime/components/table/columns/2026-05-24/com.shwfed.table.column.combobox-single.remote.options-static/config.vue.d.ts +4 -0
  96. package/dist/runtime/components/table/columns/2026-05-24/com.shwfed.table.column.combobox-single.remote.options-static/schema.d.ts +2 -0
  97. package/dist/runtime/components/table/columns/2026-05-25/com.shwfed.table.column.combobox-multi.remote.options-remote/config.d.vue.ts +4 -0
  98. package/dist/runtime/components/table/columns/2026-05-25/com.shwfed.table.column.combobox-multi.remote.options-remote/config.vue.d.ts +4 -0
  99. package/dist/runtime/components/table/columns/2026-05-25/com.shwfed.table.column.combobox-multi.remote.options-remote/schema.d.ts +2 -0
  100. package/dist/runtime/components/table/columns/2026-05-25/com.shwfed.table.column.combobox-multi.remote.options-static/config.d.vue.ts +4 -0
  101. package/dist/runtime/components/table/columns/2026-05-25/com.shwfed.table.column.combobox-multi.remote.options-static/config.vue.d.ts +4 -0
  102. package/dist/runtime/components/table/columns/2026-05-25/com.shwfed.table.column.combobox-multi.remote.options-static/schema.d.ts +2 -0
  103. package/dist/runtime/components/table/columns/2026-05-26/com.shwfed.table.column.combobox-multi.remote/config.d.vue.ts +4 -0
  104. package/dist/runtime/components/table/columns/2026-05-26/com.shwfed.table.column.combobox-multi.remote/config.vue.d.ts +4 -0
  105. package/dist/runtime/components/table/columns/2026-05-26/com.shwfed.table.column.combobox-multi.remote/schema.d.ts +2 -0
  106. package/dist/runtime/components/table/columns/2026-05-26/com.shwfed.table.column.combobox-single.remote/config.d.vue.ts +4 -0
  107. package/dist/runtime/components/table/columns/2026-05-26/com.shwfed.table.column.combobox-single.remote/config.vue.d.ts +4 -0
  108. package/dist/runtime/components/table/columns/2026-05-26/com.shwfed.table.column.combobox-single.remote/schema.d.ts +2 -0
  109. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-multi/config.d.vue.ts +4 -0
  110. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-multi/config.vue.d.ts +4 -0
  111. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-multi/schema.d.ts +4 -0
  112. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-single/config.d.vue.ts +4 -0
  113. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-single/config.vue.d.ts +4 -0
  114. package/dist/runtime/components/table/columns/2026-05-28/com.shwfed.table.column.combobox-single/schema.d.ts +4 -0
  115. package/dist/runtime/components/table/schema.d.ts +2 -2
  116. package/dist/runtime/components/ui/progress/Progress.d.vue.ts +11 -0
  117. package/dist/runtime/components/ui/progress/Progress.vue +32 -0
  118. package/dist/runtime/components/ui/progress/Progress.vue.d.ts +11 -0
  119. package/dist/runtime/components/ui/progress/index.d.ts +1 -0
  120. package/dist/runtime/components/ui/progress/index.js +1 -0
  121. package/dist/runtime/composables/useOverlay.d.ts +35 -0
  122. package/dist/runtime/composables/useOverlay.js +52 -0
  123. package/dist/runtime/share/event-bus.d.ts +24 -2
  124. package/dist/runtime/share/event-bus.js +21 -3
  125. package/dist/runtime/share/interpolate.d.ts +12 -0
  126. package/dist/runtime/share/interpolate.js +18 -0
  127. package/dist/runtime/share/page-target.d.ts +16 -0
  128. package/dist/runtime/share/page-target.js +1 -0
  129. package/dist/runtime/share/schema-meta.d.ts +5 -0
  130. package/dist/runtime/share/schema-meta.js +48 -0
  131. package/dist/runtime/style.css +1 -1
  132. package/package.json +1 -1
  133. package/dist/preview/assets/badge-Nmf0QGvo.js +0 -1
  134. package/dist/preview/assets/config-BuBQPlju.js +0 -1
  135. package/dist/preview/assets/config-CBxDLjqQ.js +0 -1
  136. package/dist/preview/assets/config-CSU0Tuu4.js +0 -1
  137. package/dist/preview/assets/config-Cnu3ZCPF.js +0 -1
  138. package/dist/preview/assets/config-CzBP2y8L.js +0 -1
  139. package/dist/preview/assets/config-nczxdERQ.js +0 -1
  140. package/dist/preview/assets/config-qCPGn6mB.js +0 -1
  141. package/dist/preview/assets/config-vHYVnzyt.js +0 -1
  142. package/dist/preview/assets/config-w2mvI_4h.js +0 -1
  143. package/dist/preview/assets/definition.vue_vue_type_script_setup_true_lang-DqE0YNVF.js +0 -1
  144. package/dist/preview/assets/index-BgvMBKzp.js +0 -1
  145. package/dist/preview/assets/index-COSjWreD.css +0 -1
  146. package/dist/preview/assets/item-DAL4Ui_l.js +0 -1
  147. package/dist/preview/assets/runtime-BcsMOkA2.js +0 -1
  148. package/dist/preview/assets/runtime-BzwoxDCd.js +0 -1
  149. package/dist/preview/assets/runtime-Cu2DKoI0.js +0 -1
  150. package/dist/preview/assets/runtime-Dp0JEFtU.js +0 -1
  151. package/dist/preview/assets/runtime-YE1oXXQN.js +0 -1
  152. package/dist/preview/assets/runtime-YRSxexBQ.js +0 -1
  153. package/dist/preview/assets/runtime-uwt0BxpS.js +0 -1
@@ -1,6 +1,8 @@
1
1
  <script setup>
2
2
  import { computed, inject, onBeforeUnmount, provide, ref, watch } from "vue";
3
3
  import { Icon } from "@iconify/vue";
4
+ import { Effect } from "effect";
5
+ import { toast } from "vue-sonner";
4
6
  import {
5
7
  ActionSchemaFields,
6
8
  ActionsConfig,
@@ -8,7 +10,8 @@ import {
8
10
  getStructFieldTitle
9
11
  } from "./schema";
10
12
  import { Environment } from "../../vendor/cel-js/lib/index";
11
- import { provideCELContext } from "../../utils/cel-context";
13
+ import { cel as rawCel } from "../../utils/cel";
14
+ import { celBindings, injectCELContext, provideCELContext } from "../../utils/cel-context";
12
15
  import { BUTTONS, findButton, ITEMS, findItem } from "./utils/resolve";
13
16
  import { SIDEBAR_TAKEOVER_KEY } from "../sidebar-takeover";
14
17
  import { BREADCRUMB_EXTENSION_KEY } from "../config/breadcrumb-extension";
@@ -18,6 +21,7 @@ import { Textarea } from "../ui/textarea";
18
21
  import { ExpressionEditor } from "../ui/expression-editor";
19
22
  import { Switch } from "../ui/switch";
20
23
  import { Separator } from "../ui/separator";
24
+ import { Button } from "../ui/button";
21
25
  import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
22
26
  import { Locale as LocaleField } from "../ui/locale";
23
27
  import { IconPicker } from "../ui/icon-picker";
@@ -303,6 +307,47 @@ function getButtonDeprecationNote(action) {
303
307
  if (!entry?.deprecated) return void 0;
304
308
  return entry.deprecationNote ?? "\u6B64\u6309\u94AE\u7C7B\u578B\u5DF2\u5F03\u7528\u3002";
305
309
  }
310
+ const inheritedCEL = injectCELContext();
311
+ function celForMigration(expression, context) {
312
+ return rawCel(expression, { ...celBindings(inheritedCEL), ...context });
313
+ }
314
+ const migrationContext = { $cel: celForMigration };
315
+ function migrationTargetFor(action) {
316
+ const entry = findButton(action.type, action.compatibilityDate);
317
+ if (!entry?.deprecated || !entry.supersededBy) return null;
318
+ return findButton(entry.supersededBy.type, entry.supersededBy.compatibilityDate) ?? null;
319
+ }
320
+ function canMigrateAction(action) {
321
+ return !!migrationTargetFor(action)?.migrate;
322
+ }
323
+ async function runActionMigration(action, apply) {
324
+ const target = migrationTargetFor(action);
325
+ if (!target?.migrate) return;
326
+ try {
327
+ const next = await Effect.runPromise(target.migrate(action, migrationContext));
328
+ apply(next);
329
+ toast.success(
330
+ target.type === action.type ? `\u5DF2\u8FC1\u79FB\u5230 ${target.compatibilityDate} \u7248\u672C` : `\u5DF2\u8FC1\u79FB\u5230 ${target.type}@${target.compatibilityDate}`
331
+ );
332
+ } catch (err) {
333
+ const message = err instanceof Error ? err.message : String(err);
334
+ toast.error("\u8FC1\u79FB\u5931\u8D25", { description: message });
335
+ }
336
+ }
337
+ function migrateSelectedActionItem() {
338
+ const it = selectedActionItem.value;
339
+ if (!it?.action) return;
340
+ void runActionMigration(it.action, (next) => patchSelectedActionItem((x) => {
341
+ x.action = next;
342
+ }));
343
+ }
344
+ function migrateSelectedSub() {
345
+ const sub = selectedSubRef.value;
346
+ if (!sub?.action) return;
347
+ void runActionMigration(sub.action, (next) => patchSelectedSub((s) => {
348
+ s.action = next;
349
+ }));
350
+ }
306
351
  function patchSelectedGroup(mutate) {
307
352
  const sel = selected.value;
308
353
  if (sel?.kind !== "group") return;
@@ -1581,6 +1626,17 @@ const newGroupZoneId = (insertIndex) => `new-group-${insertIndex}`;
1581
1626
  class="mt-px size-4 shrink-0"
1582
1627
  />
1583
1628
  <span class="flex-1">{{ getButtonDeprecationNote(selectedActionItem.action) }}</span>
1629
+ <Button
1630
+ v-if="canMigrateAction(selectedActionItem.action)"
1631
+ variant="ghost"
1632
+ size="sm"
1633
+ class="-my-1 text-amber-800 hover:bg-amber-100 hover:text-amber-900"
1634
+ data-slot="action-migrate"
1635
+ @click="migrateSelectedActionItem"
1636
+ >
1637
+ <Icon icon="fluent:arrow-sync-20-regular" />
1638
+ 迁移
1639
+ </Button>
1584
1640
  </div>
1585
1641
  </template>
1586
1642
 
@@ -1768,6 +1824,17 @@ const newGroupZoneId = (insertIndex) => `new-group-${insertIndex}`;
1768
1824
  class="mt-px size-4 shrink-0"
1769
1825
  />
1770
1826
  <span class="flex-1">{{ getButtonDeprecationNote(selectedSubRef.action) }}</span>
1827
+ <Button
1828
+ v-if="canMigrateAction(selectedSubRef.action)"
1829
+ variant="ghost"
1830
+ size="sm"
1831
+ class="-my-1 text-amber-800 hover:bg-amber-100 hover:text-amber-900"
1832
+ data-slot="action-migrate"
1833
+ @click="migrateSelectedSub"
1834
+ >
1835
+ <Icon icon="fluent:arrow-sync-20-regular" />
1836
+ 迁移
1837
+ </Button>
1771
1838
  </div>
1772
1839
  </template>
1773
1840
 
@@ -7,6 +7,33 @@ import type { EventChannel, OperationDescriptor, TriggerValue } from '../../../s
7
7
  type CelEvaluator = <T>(expression: string, context?: Record<string, unknown>) => Effect.Effect<T, any>;
8
8
  type AnySchema = Schema.Schema<any, any, never>;
9
9
  type SchemaFactory = (configure: (env: Environment) => void) => AnySchema;
10
+ /**
11
+ * Capabilities a button-migration function may need from the host. Mirrors the
12
+ * form / table hosts' `MigrationContext` so cross-host migrations share one
13
+ * signature; extend additively rather than introducing a parallel context.
14
+ */
15
+ export type MigrationContext = Readonly<{
16
+ $cel: <T>(expression: string, context?: Record<string, unknown>) => Effect.Effect<T, unknown>;
17
+ }>;
18
+ /**
19
+ * Migrates a value of a *source* button schema to this entry's schema. Lives on
20
+ * the *target* (newer / replacement) entry. The target is picked by one of two
21
+ * rules: (a) same `type` with a strictly newer `compatibilityDate`; (b) an
22
+ * explicit `migrateFrom` declaration on the target naming this source. Failures
23
+ * surface to the host as a toast — see `actions/config.vue`'s migrate handler.
24
+ */
25
+ export type MigrateFn = (prev: unknown, ctx: MigrationContext) => Effect.Effect<unknown, Error>;
26
+ /**
27
+ * Identifies a button entry by `(type, compatibilityDate)`. Used for both
28
+ * `migrateFrom` declarations and the cross-type form of `supersededBy`.
29
+ *
30
+ * Migration declarations only ever live on the *new* (target) code — old
31
+ * folders are never modified to announce that they have been superseded.
32
+ */
33
+ export type MigrateSource = Readonly<{
34
+ type: string;
35
+ compatibilityDate: string;
36
+ }>;
10
37
  export type ButtonMetadata = Readonly<{
11
38
  name: string;
12
39
  icon: string;
@@ -21,12 +48,34 @@ export type ButtonEntry = Readonly<{
21
48
  schema: SchemaFactory;
22
49
  config: Component;
23
50
  runtime: Component;
24
- /** True when this button type is deprecated — explicit flag or newer date. */
51
+ /** True when this button type is deprecated — explicit flag, newer date, or `migrateFrom`. */
25
52
  deprecated: boolean;
26
53
  /** Migration guidance shown in the inspector when `deprecated`. */
27
54
  deprecationNote?: string;
55
+ /**
56
+ * Identifies the entry that supersedes this one. Same `type` for the
57
+ * date-versioned rule; a different `type` for declared cross-type migrations.
58
+ * Absent for entries deprecated only via a free-text `metadata.deprecated`.
59
+ */
60
+ supersededBy?: MigrateSource;
61
+ /** Migration from a recognized source schema to this entry's schema. */
62
+ migrate?: MigrateFn;
63
+ /** Source schemas this entry accepts migration from. See module field. */
64
+ migrateFrom?: ReadonlyArray<MigrateSource>;
28
65
  }>;
29
66
  export declare const BUTTONS: ReadonlyArray<ButtonEntry>;
67
+ /**
68
+ * Finds the entry that declared `(type, compatibilityDate)` as a `migrateFrom`
69
+ * source. Use when a stored button's identity no longer matches any registry
70
+ * entry (the old folder was removed) — a present return means the value is
71
+ * still migratable via the target's `migrate`.
72
+ *
73
+ * For sources whose folder was kept (the usual case — see "never delete a
74
+ * superseded block folder"), the entry itself is in `BUTTONS`, tagged
75
+ * `deprecated`/`supersededBy`; reach the target via
76
+ * `findButton(supersededBy.type, …)` instead.
77
+ */
78
+ export declare function findMigrationTarget(type: string, compatibilityDate: string): ButtonEntry | undefined;
30
79
  export declare function allButtonSchemas(configure: (env: Environment) => void): ReadonlyArray<AnySchema>;
31
80
  export declare function findButton(type: string, compatibilityDate: string): ButtonEntry | undefined;
32
81
  export type ItemMetadata = Readonly<{
@@ -44,7 +93,7 @@ export type ItemEntry = Readonly<{
44
93
  export declare const ITEMS: ReadonlyArray<ItemEntry>;
45
94
  export declare function allItemSchemas(configure: (env: Environment) => void): ReadonlyArray<AnySchema>;
46
95
  export declare function findItem(type: string, compatibilityDate: string): ItemEntry | undefined;
47
- export declare function interpolateMarkdown(source: string, $cel: CelEvaluator): string;
96
+ export declare function interpolateMarkdown(source: string, $cel: CelEvaluator, context?: Record<string, unknown>): string;
48
97
  type ToastStyle = 'success' | 'error' | 'warning' | 'info';
49
98
  /**
50
99
  * Post-completion behaviour shared by the HTTP-request buttons. Once the
@@ -63,4 +112,38 @@ export type HttpRequestOutcome = {
63
112
  triggers?: Partial<Record<ToastStyle, ReadonlyArray<TriggerValue>>>;
64
113
  };
65
114
  export declare function executeHttpRequest(expression: string, $cel: CelEvaluator, outcome?: HttpRequestOutcome): Effect.Effect<unknown, any, Fetch.Fetch>;
115
+ /**
116
+ * Batch sibling of {@link executeHttpRequest}. The expression evaluates to a
117
+ * list of `HttpRequestBuilder`s, sent strictly one-by-one behind a live
118
+ * progress dialog. Each response is judged individually:
119
+ *
120
+ * - `resultExpression` (per response) is evaluated against that one response
121
+ * body `json`, returning a `bool` — `true` means the response succeeded.
122
+ * Absent ⇒ any non-erroring response counts as a success.
123
+ * - `continueOnError` — when false (default) the first failure (a transport
124
+ * error or a `resultExpression` that returned false) stops the batch. When
125
+ * true, the loop carries on past failures.
126
+ *
127
+ * The dialog always shows a Stop button; the loop polls `handle.isCancelled`
128
+ * between requests and stops the remaining ones (already-sent requests stand).
129
+ *
130
+ * The dialog never closes on its own — `handle.finish` flips it to a closeable
131
+ * state and the user dismisses it. The post-completion `messageExpression` (if
132
+ * any) is evaluated once against `jsons` — the list of **succeeded** response
133
+ * bodies — and shown as a toast, styled `success` when nothing failed and
134
+ * `error` otherwise.
135
+ *
136
+ * `markdown`, if given, is the dialog body: localized markdown re-interpolated
137
+ * after every request against the live counters `total` / `succeeded` /
138
+ * `failure`, so the message tracks progress.
139
+ */
140
+ export type HttpRequestBatchOutcome = {
141
+ /** Toast message expression, evaluated once against `jsons`. No toast when absent. */
142
+ messageExpression?: string;
143
+ /** Per-response success predicate, evaluated against `json`. Absent ⇒ always succeeds. */
144
+ resultExpression?: string;
145
+ continueOnError?: boolean;
146
+ markdown?: string;
147
+ };
148
+ export declare function executeHttpRequestBatch(expression: string, $cel: CelEvaluator, outcome?: HttpRequestBatchOutcome): Effect.Effect<unknown, any, Fetch.Fetch>;
66
149
  export {};
@@ -1,8 +1,10 @@
1
1
  import { defineAsyncComponent } from "vue";
2
- import { Effect } from "effect";
2
+ import { Effect, Either } from "effect";
3
3
  import { toast } from "vue-sonner";
4
+ import { useOverlay } from "../../../composables/useOverlay.js";
4
5
  import { defineRegistry } from "../../../share/define-registry.js";
5
6
  import { dispatchTriggers } from "../../../share/event-bus.js";
7
+ const sourceKey = (type, compatibilityDate) => `${type}@${compatibilityDate}`;
6
8
  const schemaModules = import.meta.glob(
7
9
  "../buttons/*/*/schema.{ts,js}",
8
10
  { eager: true, exhaustive: true }
@@ -33,23 +35,55 @@ const registry = defineRegistry({
33
35
  schemaModules,
34
36
  configModules,
35
37
  runtimeModules,
36
- // A button is deprecated when its schema declares `metadata.deprecated`
37
- // (an explicit, possibly cross-type replacement note), or when a newer
38
- // `compatibilityDate` of the same `type` exists (the date-versioned rule
39
- // shared with form). Either way the inspector shows the migration hint.
38
+ // A button is deprecated by one of three rules, in precedence order:
39
+ // (1) another entry declares this one in its `migrateFrom` — explicit
40
+ // cross-type supersession with a structured `supersededBy` target the
41
+ // inspector can offer a one-click 「迁移」 against;
42
+ // (2) its schema declares a free-text `metadata.deprecated` note — the
43
+ // escape hatch for a replacement with no structured migrate target;
44
+ // (3) a newer `compatibilityDate` of the same `type` exists — the
45
+ // date-versioned rule shared with form / table.
46
+ // `migrateFrom` wins over the date rule: a kept-around stub of an old type
47
+ // points at exactly which target it migrates to, so trust it over the
48
+ // implicit "any older same-type date is the same component upgraded" guess.
40
49
  deriveEntryExtras: (drafts) => {
41
50
  const latestByType = /* @__PURE__ */ new Map();
42
51
  for (const d of drafts) {
43
52
  const prev = latestByType.get(d.type);
44
53
  if (!prev || d.compatibilityDate > prev) latestByType.set(d.type, d.compatibilityDate);
45
54
  }
55
+ const migrationTargets = /* @__PURE__ */ new Map();
56
+ for (const d of drafts) {
57
+ const sources = d.module.migrateFrom;
58
+ if (!sources || sources.length === 0) continue;
59
+ for (const src of sources) {
60
+ const key = sourceKey(src.type, src.compatibilityDate);
61
+ const existing = migrationTargets.get(key);
62
+ if (existing) {
63
+ throw new Error(
64
+ `[shwfed-actions] migrateFrom collision: source "${key}" is claimed by both "${sourceKey(existing.type, existing.compatibilityDate)}" and "${sourceKey(d.type, d.compatibilityDate)}"`
65
+ );
66
+ }
67
+ migrationTargets.set(key, { type: d.type, compatibilityDate: d.compatibilityDate });
68
+ }
69
+ }
46
70
  return (draft) => {
71
+ const declared = migrationTargets.get(sourceKey(draft.type, draft.compatibilityDate));
72
+ if (declared) {
73
+ return {
74
+ deprecated: true,
75
+ supersededBy: declared,
76
+ deprecationNote: declared.type === draft.type ? `\u5DF2\u88AB\u66F4\u65B0\u7248\u672C ${declared.compatibilityDate} \u53D6\u4EE3\u3002` : `\u5DF2\u88AB ${declared.type}@${declared.compatibilityDate} \u53D6\u4EE3\u3002`
77
+ };
78
+ }
47
79
  const note = draft.module.metadata.deprecated;
48
80
  if (note) return { deprecated: true, deprecationNote: note };
49
- if (draft.compatibilityDate < latestByType.get(draft.type)) {
81
+ const latest = latestByType.get(draft.type);
82
+ if (draft.compatibilityDate < latest) {
50
83
  return {
51
84
  deprecated: true,
52
- deprecationNote: `\u5DF2\u88AB\u66F4\u65B0\u7248\u672C ${latestByType.get(draft.type)} \u53D6\u4EE3\u3002`
85
+ supersededBy: { type: draft.type, compatibilityDate: latest },
86
+ deprecationNote: `\u5DF2\u88AB\u66F4\u65B0\u7248\u672C ${latest} \u53D6\u4EE3\u3002`
53
87
  };
54
88
  }
55
89
  return { deprecated: false };
@@ -64,8 +98,32 @@ export const BUTTONS = registry.ENTRIES.map((e) => ({
64
98
  config: e.config,
65
99
  runtime: e.runtime,
66
100
  deprecated: e.deprecated,
67
- ...e.deprecationNote !== void 0 ? { deprecationNote: e.deprecationNote } : {}
101
+ ...e.deprecationNote !== void 0 ? { deprecationNote: e.deprecationNote } : {},
102
+ ...e.supersededBy !== void 0 ? { supersededBy: e.supersededBy } : {},
103
+ migrate: e.module.migrate,
104
+ ...e.module.migrateFrom !== void 0 ? { migrateFrom: e.module.migrateFrom } : {}
68
105
  }));
106
+ let _migrationTargets = null;
107
+ function getMigrationTargets() {
108
+ if (_migrationTargets) return _migrationTargets;
109
+ const map = /* @__PURE__ */ new Map();
110
+ for (const entry of BUTTONS) {
111
+ if (!entry.migrateFrom) continue;
112
+ for (const src of entry.migrateFrom) {
113
+ map.set(sourceKey(src.type, src.compatibilityDate), {
114
+ type: entry.type,
115
+ compatibilityDate: entry.compatibilityDate
116
+ });
117
+ }
118
+ }
119
+ _migrationTargets = map;
120
+ return map;
121
+ }
122
+ export function findMigrationTarget(type, compatibilityDate) {
123
+ const target = getMigrationTargets().get(sourceKey(type, compatibilityDate));
124
+ if (!target) return void 0;
125
+ return findButton(target.type, target.compatibilityDate);
126
+ }
69
127
  export function allButtonSchemas(configure) {
70
128
  return BUTTONS.map((entry) => entry.schema(configure).annotations({
71
129
  identifier: `${entry.type}@${entry.compatibilityDate}`
@@ -122,12 +180,12 @@ export function findItem(type, compatibilityDate) {
122
180
  return ITEMS.find((i) => i.type === type && i.compatibilityDate === compatibilityDate);
123
181
  }
124
182
  const INTERPOLATION_RE = /\{\{(.*?)\}\}/gs;
125
- export function interpolateMarkdown(source, $cel) {
183
+ export function interpolateMarkdown(source, $cel, context) {
126
184
  return source.replace(INTERPOLATION_RE, (_, expr) => {
127
185
  const trimmed = expr.trim();
128
186
  if (trimmed.length === 0) return "{{ }}";
129
187
  try {
130
- return stringifyCelResult(Effect.runSync($cel(trimmed)));
188
+ return stringifyCelResult(Effect.runSync($cel(trimmed, context)));
131
189
  } catch {
132
190
  return `{{${expr}}}`;
133
191
  }
@@ -139,21 +197,64 @@ function stringifyCelResult(value) {
139
197
  return String(value);
140
198
  }
141
199
  const TOAST_STYLES = /* @__PURE__ */ new Set(["success", "error", "warning", "info"]);
142
- export function executeHttpRequest(expression, $cel, outcome = {}) {
200
+ function classifyAndDispatch($cel, context, outcome) {
143
201
  const { messageExpression, resultExpression, channel, triggers } = outcome;
202
+ const result = resultExpression === void 0 ? Effect.succeed("success") : Effect.map($cel(resultExpression, context), (s) => TOAST_STYLES.has(s) ? s : "success");
203
+ return Effect.flatMap(result, (kind) => {
204
+ const toasted = messageExpression === void 0 ? Effect.void : Effect.flatMap(
205
+ $cel(messageExpression, context),
206
+ (message) => Effect.sync(() => toast[kind](message))
207
+ );
208
+ return Effect.andThen(toasted, dispatchTriggers(channel, triggers?.[kind]));
209
+ });
210
+ }
211
+ export function executeHttpRequest(expression, $cel, outcome = {}) {
144
212
  const request = Effect.flatMap(
145
213
  $cel(expression),
146
214
  (builder) => builder.json()
147
215
  );
148
- return Effect.flatMap(request, (body) => {
149
- const context = { json: body };
150
- const result = resultExpression === void 0 ? Effect.succeed("success") : Effect.map($cel(resultExpression, context), (s) => TOAST_STYLES.has(s) ? s : "success");
151
- return Effect.flatMap(result, (kind) => {
152
- const toasted = messageExpression === void 0 ? Effect.void : Effect.flatMap(
153
- $cel(messageExpression, context),
154
- (message) => Effect.sync(() => toast[kind](message))
155
- );
156
- return Effect.andThen(toasted, dispatchTriggers(channel, triggers?.[kind]));
157
- });
216
+ return Effect.flatMap(request, (body) => classifyAndDispatch($cel, { json: body }, outcome));
217
+ }
218
+ export function executeHttpRequestBatch(expression, $cel, outcome = {}) {
219
+ const { continueOnError = false, markdown, messageExpression, resultExpression } = outcome;
220
+ return Effect.gen(function* () {
221
+ const builders = yield* $cel(expression);
222
+ const total = builders.length;
223
+ let succeeded = 0;
224
+ let failure = 0;
225
+ const hasMarkdown = markdown !== void 0 && markdown.length > 0;
226
+ const render = () => hasMarkdown ? interpolateMarkdown(markdown, $cel, { total, succeeded, failure }) : "";
227
+ const handle = yield* useOverlay().openProgress({ total, cancellable: true, content: render() });
228
+ const jsons = [];
229
+ yield* Effect.ensuring(
230
+ Effect.gen(function* () {
231
+ for (const builder of builders) {
232
+ if (yield* handle.isCancelled) break;
233
+ const response = yield* Effect.either(builder.json());
234
+ let ok = false;
235
+ if (Either.isRight(response)) {
236
+ const json = response.right;
237
+ ok = resultExpression === void 0 ? true : yield* $cel(resultExpression, { json });
238
+ if (ok) {
239
+ jsons.push(json);
240
+ succeeded += 1;
241
+ } else {
242
+ failure += 1;
243
+ }
244
+ } else {
245
+ failure += 1;
246
+ }
247
+ if (!ok) yield* handle.markFailed;
248
+ yield* handle.advance;
249
+ if (hasMarkdown) yield* handle.setContent(render());
250
+ if (!ok && !continueOnError) break;
251
+ }
252
+ }),
253
+ handle.finish
254
+ );
255
+ if (messageExpression !== void 0) {
256
+ const message = yield* $cel(messageExpression, { jsons });
257
+ yield* Effect.sync(() => toast[failure === 0 ? "success" : "error"](message));
258
+ }
158
259
  });
159
260
  }
@@ -1,5 +1,6 @@
1
1
  <script setup>
2
2
  import { computed, defineComponent } from "vue";
3
+ import { useEventListener } from "@vueuse/core";
3
4
  import { Effect } from "effect";
4
5
  import { Icon } from "@iconify/vue";
5
6
  import { ConfigProvider, TooltipProvider } from "reka-ui";
@@ -16,19 +17,27 @@ import {
16
17
  AlertDialogHeader,
17
18
  AlertDialogTitle
18
19
  } from "./ui/alert-dialog";
20
+ import { Button } from "./ui/button";
19
21
  import { Markdown } from "./ui/markdown";
22
+ import { Progress } from "./ui/progress";
20
23
  import { useOverlay } from "../composables/useOverlay";
21
24
  import { useHead } from "#app";
22
25
  defineOptions({
23
26
  name: "ShwfedApp"
24
27
  });
25
28
  const overlay = useOverlay();
29
+ useEventListener("beforeunload", (event) => {
30
+ if (overlay.progress.some((p) => !p.finished)) {
31
+ event.preventDefault();
32
+ event.returnValue = "";
33
+ }
34
+ });
26
35
  const { t } = useI18n({
27
36
  messages: {
28
- zh: { cancel: "\u53D6\u6D88", confirm: "\u786E\u8BA4" },
29
- en: { cancel: "Cancel", confirm: "Confirm" },
30
- ja: { cancel: "\u30AD\u30E3\u30F3\u30BB\u30EB", confirm: "\u78BA\u8A8D" },
31
- ko: { cancel: "\uCDE8\uC18C", confirm: "\uD655\uC778" }
37
+ zh: { cancel: "\u53D6\u6D88", confirm: "\u786E\u8BA4", stop: "\u505C\u6B62", close: "\u5173\u95ED", processing: "\u5904\u7406\u4E2D\u2026" },
38
+ en: { cancel: "Cancel", confirm: "Confirm", stop: "Stop", close: "Close", processing: "Processing\u2026" },
39
+ ja: { cancel: "\u30AD\u30E3\u30F3\u30BB\u30EB", confirm: "\u78BA\u8A8D", stop: "\u505C\u6B62", close: "\u9589\u3058\u308B", processing: "\u51E6\u7406\u4E2D\u2026" },
40
+ ko: { cancel: "\uCDE8\uC18C", confirm: "\uD655\uC778", stop: "\uC911\uC9C0", close: "\uB2EB\uAE30", processing: "\uCC98\uB9AC \uC911\u2026" }
32
41
  }
33
42
  });
34
43
  const OverlayBody = defineComponent({
@@ -196,6 +205,52 @@ useHead({
196
205
  </AlertDialogContent>
197
206
  </AlertDialog>
198
207
  </template>
208
+
209
+ <template
210
+ v-for="p in overlay.progress"
211
+ :key="p.progressId"
212
+ >
213
+ <AlertDialog :open="p.open">
214
+ <AlertDialogContent class="sm:px-12 sm:py-8 flex flex-col gap-6">
215
+ <AlertDialogHeader class="sr-only">
216
+ <AlertDialogTitle>{{ t("processing") }}</AlertDialogTitle>
217
+ <AlertDialogDescription>Progress dialog</AlertDialogDescription>
218
+ </AlertDialogHeader>
219
+ <Markdown
220
+ v-if="p.content"
221
+ :source="p.content"
222
+ block
223
+ class="prose prose-zinc text-sm text-center text-zinc-700"
224
+ />
225
+ <Progress
226
+ :model-value="p.total ? p.done / p.total * 100 : 0"
227
+ :indicator-class="p.failed ? 'bg-red-500' : void 0"
228
+ />
229
+ <AlertDialogFooter v-if="p.finished || p.cancellable">
230
+ <Button
231
+ v-if="p.finished"
232
+ variant="default"
233
+ @click="overlay.closeProgress(p.progressId)"
234
+ >
235
+ <Icon icon="fluent:dismiss-20-regular" />
236
+ {{ t("close") }}
237
+ </Button>
238
+ <Button
239
+ v-else
240
+ variant="default"
241
+ :disabled="p.cancelled"
242
+ @click="overlay.cancelProgress(p.progressId)"
243
+ >
244
+ <Icon
245
+ :icon="p.cancelled ? 'fluent:spinner-ios-20-regular' : 'fluent:stop-20-regular'"
246
+ :class="p.cancelled ? 'animate-spin' : ''"
247
+ />
248
+ {{ t("stop") }}
249
+ </Button>
250
+ </AlertDialogFooter>
251
+ </AlertDialogContent>
252
+ </AlertDialog>
253
+ </template>
199
254
  </ClientOnly>
200
255
  </TooltipProvider>
201
256
  </ConfigProvider>
@@ -65,8 +65,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
65
65
  } | undefined;
66
66
  readonly dataSource?: {
67
67
  readonly data: string;
68
- readonly request?: string | undefined;
69
68
  readonly total?: string | undefined;
69
+ readonly request?: string | undefined;
70
70
  } | undefined;
71
71
  readonly actions?: {
72
72
  readonly size: "default" | "sm" | "xs";
@@ -246,8 +246,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
246
246
  } | undefined;
247
247
  readonly dataSource?: {
248
248
  readonly data: string;
249
- readonly request?: string | undefined;
250
249
  readonly total?: string | undefined;
250
+ readonly request?: string | undefined;
251
251
  } | undefined;
252
252
  readonly actions?: {
253
253
  readonly size: "default" | "sm" | "xs";
@@ -65,8 +65,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
65
65
  } | undefined;
66
66
  readonly dataSource?: {
67
67
  readonly data: string;
68
- readonly request?: string | undefined;
69
68
  readonly total?: string | undefined;
69
+ readonly request?: string | undefined;
70
70
  } | undefined;
71
71
  readonly actions?: {
72
72
  readonly size: "default" | "sm" | "xs";
@@ -246,8 +246,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {},
246
246
  } | undefined;
247
247
  readonly dataSource?: {
248
248
  readonly data: string;
249
- readonly request?: string | undefined;
250
249
  readonly total?: string | undefined;
250
+ readonly request?: string | undefined;
251
251
  } | undefined;
252
252
  readonly actions?: {
253
253
  readonly size: "default" | "sm" | "xs";
@@ -60,8 +60,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
60
60
  } | undefined;
61
61
  readonly dataSource?: {
62
62
  readonly data: string;
63
- readonly request?: string | undefined;
64
63
  readonly total?: string | undefined;
64
+ readonly request?: string | undefined;
65
65
  } | undefined;
66
66
  readonly actions?: {
67
67
  readonly size: "default" | "sm" | "xs";
@@ -241,8 +241,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
241
241
  } | undefined;
242
242
  readonly dataSource?: {
243
243
  readonly data: string;
244
- readonly request?: string | undefined;
245
244
  readonly total?: string | undefined;
245
+ readonly request?: string | undefined;
246
246
  } | undefined;
247
247
  readonly actions?: {
248
248
  readonly size: "default" | "sm" | "xs";
@@ -60,8 +60,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
60
60
  } | undefined;
61
61
  readonly dataSource?: {
62
62
  readonly data: string;
63
- readonly request?: string | undefined;
64
63
  readonly total?: string | undefined;
64
+ readonly request?: string | undefined;
65
65
  } | undefined;
66
66
  readonly actions?: {
67
67
  readonly size: "default" | "sm" | "xs";
@@ -241,8 +241,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {},
241
241
  } | undefined;
242
242
  readonly dataSource?: {
243
243
  readonly data: string;
244
- readonly request?: string | undefined;
245
244
  readonly total?: string | undefined;
245
+ readonly request?: string | undefined;
246
246
  } | undefined;
247
247
  readonly actions?: {
248
248
  readonly size: "default" | "sm" | "xs";
@@ -75,8 +75,8 @@ export declare function schema(configure: (env: Environment) => void, _blockRef:
75
75
  } | undefined;
76
76
  readonly dataSource?: {
77
77
  readonly data: string;
78
- readonly request?: string | undefined;
79
78
  readonly total?: string | undefined;
79
+ readonly request?: string | undefined;
80
80
  } | undefined;
81
81
  readonly actions?: {
82
82
  readonly size: "default" | "sm" | "xs";
@@ -13,6 +13,8 @@ import EditorBody from "./config.vue";
13
13
  import EditorBreadcrumb from "./breadcrumb.vue";
14
14
  import EditorFooter from "./footer.vue";
15
15
  import { provideEventTarget } from "../../share/event-bus";
16
+ import { operationHandlers } from "../operations/utils/resolve";
17
+ import * as alertOperation from "../operations/2026-06-09/com.shwfed.operation.alert/schema";
16
18
  import { PAGE_TARGET_ID, createPageConfig } from "./schema";
17
19
  import { useConfigEditor } from "./use-editor";
18
20
  import { findBlock } from "./utils/resolve";
@@ -42,11 +44,17 @@ const mergedConfigure = (env) => {
42
44
  class PageClosed {
43
45
  _tag = "PageClosed";
44
46
  }
47
+ const { locale } = useI18n({ useScope: "global", inheritLocale: true });
48
+ const $cel = (expression, context) => cel(expression, { ...celBindings(inheritedCEL), ...context });
45
49
  provideEventTarget(PAGE_TARGET_ID, {
46
50
  close: () => Effect.gen(function* () {
47
51
  window.close();
48
52
  return yield* Effect.die(new PageClosed());
49
- })
53
+ }),
54
+ ...operationHandlers(
55
+ [{ type: alertOperation.type, compatibilityDate: alertOperation.compatibilityDate }],
56
+ { cel: $cel, locale: () => locale.value }
57
+ )
50
58
  });
51
59
  const editorOpen = ref(false);
52
60
  const editorState = useConfigEditor(config, {
@@ -81,7 +89,6 @@ const pageStyle = computed(() => {
81
89
  return void 0;
82
90
  }
83
91
  });
84
- const { locale } = useI18n({ useScope: "global", inheritLocale: true });
85
92
  useHead(() => {
86
93
  const title = getLocalizedText(config.value.title, locale.value);
87
94
  return title ? { title } : {};