@lingfan-engine/editor 0.1.5 → 0.2.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lingfan-engine/editor",
3
- "version": "0.1.5",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "灵泛叙事作者层:op schema 单一事实源 + 词函数子语言 + 编辑期诊断 + 故事树纯映射器——只读写故事 JSON,不碰运行时与平台",
6
6
  "exports": {
@@ -13,7 +13,7 @@
13
13
  ],
14
14
  "sideEffects": false,
15
15
  "dependencies": {
16
- "@lingfan-engine/core": "^0.1.5",
16
+ "@lingfan-engine/core": "^0.2.0",
17
17
  "zod": "^4.6.5"
18
18
  },
19
19
  "license": "Apache-2.0",
@@ -106,13 +106,13 @@ export interface EditorTexts {
106
106
  invalidVarHandlePath(prop: string, prefix: string): string;
107
107
  /** script/expr:未注册的变量。 */
108
108
  unregisteredVariable(key: string): string;
109
- /** script/expr 审计:操作数类型不匹配。 */
109
+ /** script/expr 检查:操作数类型不匹配。 */
110
110
  operandKindMismatch(op: string, side: string, want: string, described: string, note: string): string;
111
- /** script/expr 审计:三元条件必须为 boolean。 */
111
+ /** script/expr 检查:三元条件必须为 boolean。 */
112
112
  ternaryCondNotBool(described: string): string;
113
- /** script/expr 审计:不支持跨类型比较。 */
113
+ /** script/expr 检查:不支持跨类型比较。 */
114
114
  crossTypeComparison(op: string, left: string, right: string): string;
115
- /** script/expr 审计:未知类型的操作数标签。 */
115
+ /** script/expr 检查:未知类型的操作数标签。 */
116
116
  unknownOperandKind(): string;
117
117
 
118
118
  /** schema/surface:扩展 op 归组不合法。 */
@@ -9,11 +9,12 @@
9
9
  * 路径语法:`a.b` 逐段取值;`a[].b` 的 `[]` = 对数组逐项迭代(`options[].text`)。
10
10
  */
11
11
 
12
- /** 可翻译原文面(运行期 Translate 挂接:say 文本+说话人 / menu prompt+选项 / input prompt / notify 文本。character.screen 见下——仅入编辑器比对面,运行期不翻) */
12
+ /** 可翻译原文面(运行期 Translate 挂接:say 文本+说话人 / menu prompt+选项 / input prompt+候选文本 / notify 文本。character.screen 见下——仅入编辑器比对面,运行期不翻) */
13
13
  export const TRANSLATE_SURFACES: Readonly<Record<string, readonly string[]>> = {
14
14
  say: ["text", "speaker"],
15
15
  menu: ["prompt", "options[].text"],
16
- input: ["prompt"],
16
+ // 候选只翻显示文本;`value` 是要写进变量的原样值,**不进翻译面**(否则多语言下故事逻辑随语言漂移)
17
+ input: ["prompt", "options[].text"],
17
18
  notify: ["text"],
18
19
  // character.screen 仅入编辑器比对面(原文收集/键抽取),运行期**不翻**:引擎恰好 7 个
19
20
  // translate 调用点(runtime/i18n/translate.ts:31、ops/dialog.ts:92/107/171/175、
package/src/index.ts CHANGED
@@ -291,6 +291,7 @@ export {
291
291
  hide,
292
292
  imageElement,
293
293
  input,
294
+ inputCandidate,
294
295
  interaction,
295
296
  jump,
296
297
  letVar,
@@ -136,6 +136,9 @@ export const FIELD_META: Readonly<Record<string, FieldMeta>> = {
136
136
  "menu.z": { label: "层级(实例)", kind: "number" },
137
137
  "input.prompt": { label: "提示语", kind: "text" },
138
138
  "input.store": { label: "存入变量", kind: "identifier" },
139
+ "input.options": { label: "候选值", kind: "array" },
140
+ "input.options[].text": { label: "候选文本", kind: "text" },
141
+ "input.options[].value": { label: "填入值", kind: "string" },
139
142
  "input.z": { label: "层级(实例)", kind: "number" },
140
143
  "notify.text": { label: "文本", kind: "text" },
141
144
  "notify.type": { label: "类型", kind: "string" },
@@ -56,6 +56,10 @@ const menuSchema = z.strictObject({
56
56
  const inputSchema = z.strictObject({
57
57
  prompt: NonEmpty,
58
58
  store: NonEmpty,
59
+ // 候选快捷值:显示文本 + 要填进 store 的值(与 menu 的 {text,target} 刻意不同名)
60
+ options: z
61
+ .array(z.strictObject({ text: NonEmpty, value: NonEmpty }))
62
+ .optional(),
59
63
  z: InstanceZ, // choices 层实例 z
60
64
  });
61
65
 
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 审计子模块出口:判类器与它使用的词法词汇。
2
+ * 检查子模块出口:判类器与它使用的词法词汇。
3
3
  *
4
4
  * 装配层只从这里取,不引子模块内部文件。
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 表达式审计:把装配时产出的 token 流按引擎的类型规则判类,违规记入警告池。
2
+ * 表达式检查:把装配时产出的 token 流按引擎的类型规则判类,违规记入警告池。
3
3
  *
4
4
  * 判类器是一个整体(方法共享游标 `pos` 与警告池),所以实现文件刻意只留一条导入。
5
5
  */
@@ -52,9 +52,20 @@ export {
52
52
  } from "./words/index.ts";
53
53
 
54
54
  // —— 叙事 ——
55
- export { say, option, menu, input, notify, nvl, character, uncharacter } from "./words/index.ts";
55
+ export {
56
+ say,
57
+ option,
58
+ menu,
59
+ input,
60
+ inputCandidate,
61
+ notify,
62
+ nvl,
63
+ character,
64
+ uncharacter,
65
+ } from "./words/index.ts";
56
66
  export type {
57
67
  MenuOptionSpec,
68
+ InputCandidateSpec,
58
69
  SayOptions,
59
70
  NotifyOptions,
60
71
  NvlCommandMode,
@@ -39,6 +39,7 @@ export function selectWordSources(fileNames: readonly string[]): string[] {
39
39
  * 未登记的新导出词会让重建失败(见 renderVocabulary 的缺登记核对)。 */
40
40
  export const HELPER_WORDS: Readonly<Record<string, string>> = {
41
41
  option: "$menu.option",
42
+ inputCandidate: "$input.candidate",
42
43
  whenChain: "$if.chain",
43
44
  reward: "$minigame.reward",
44
45
  extOp: "$ext.op",
@@ -164,6 +164,7 @@ export const SCRIPT_COVERAGE: Readonly<Record<string, string>> = {
164
164
  videoSkipable: "video_skipable",
165
165
  // —— 辅助词(补充表登记,不产独立 op) ——
166
166
  option: "$menu.option",
167
+ inputCandidate: "$input.candidate",
167
168
  whenChain: "$if.chain",
168
169
  reward: "$minigame.reward",
169
170
  extOp: "$ext.op",
@@ -146,6 +146,31 @@ export function menu(
146
146
  };
147
147
  }
148
148
 
149
+ /** 输入候选:显示文本 + 要填进变量的值(用 {@link inputCandidate} 构造) */
150
+ export type InputCandidateSpec = NonNullable<CommandOf<"input">["options"]>[number];
151
+
152
+ /**
153
+ * 构造一个输入候选,配合 {@link input} 使用:玩家点中它,`value` 就直接当输入值提交,
154
+ * 同时仍然可以自己打字。
155
+ *
156
+ * 与菜单的 {@link option} 不同——候选不跳列,只替玩家填一个值。
157
+ *
158
+ * @param text 候选上显示的文字(支持翻译;翻译只影响显示)。
159
+ * @param value 选中后写进 `store` 的原样值(**不进翻译表**,多语言下不变)。
160
+ * @returns 输入候选数据。
161
+ * @example
162
+ * input("你的身份是?", "role", [
163
+ * inputCandidate("旅人", "traveler"),
164
+ * inputCandidate("商人", "merchant"),
165
+ * ]);
166
+ */
167
+ export function inputCandidate(
168
+ text: string,
169
+ value: string,
170
+ ): InputCandidateSpec {
171
+ return { text, value };
172
+ }
173
+
149
174
  /**
150
175
  * 向玩家要一段文字:弹出输入框显示 `prompt`,玩家提交后,输入内容写进
151
176
  * 状态键 `store`,故事从下一句继续。
@@ -154,13 +179,30 @@ export function menu(
154
179
  *
155
180
  * @param prompt 输入框上方显示的问题/提示语(必填非空)。
156
181
  * @param store 收纳玩家输入的状态变量名(写进全局状态,之后可用 `{store}` 插值显示)。
182
+ * @param options 候选快捷值(可选):给了就在输入框旁列出,玩家点一项即提交该项的 `value`,
183
+ * 也可以不理候选自己打字。用 {@link inputCandidate} 构造。
157
184
  * @returns `input` 故事命令。
158
185
  * @example
159
186
  * input("主角叫什么名字?", "playerName");
160
187
  * say("你好,{playerName}!");
188
+ * @example
189
+ * // 带候选:既能点,也能自己打
190
+ * input("你的身份是?", "role", [
191
+ * inputCandidate("旅人", "traveler"),
192
+ * inputCandidate("商人", "merchant"),
193
+ * ]);
161
194
  */
162
- export function input(prompt: string, store: string): CommandOf<"input"> {
163
- return { op: "input", prompt, store };
195
+ export function input(
196
+ prompt: string,
197
+ store: string,
198
+ options?: readonly InputCandidateSpec[],
199
+ ): CommandOf<"input"> {
200
+ return {
201
+ op: "input",
202
+ prompt,
203
+ store,
204
+ ...(options === undefined ? {} : { options: [...options] }),
205
+ };
164
206
  }
165
207
 
166
208
  /** type 保持手写的窄联合(schema 是 string)——作者侧补全优于校验面放宽 */