@lingfan-engine/editor 0.1.4 → 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 +2 -2
- package/src/contracts/texts.ts +4 -4
- package/src/i18n/surfaces.ts +3 -2
- package/src/index.ts +1 -0
- package/src/schema/catalog.ts +3 -0
- package/src/schema/opSchemas/commands.ts +4 -0
- package/src/script/expr/audit/index.ts +1 -1
- package/src/script/expr/audit/parser.ts +1 -1
- package/src/script/index.ts +12 -1
- package/src/script/vocabulary-gen.ts +1 -0
- package/src/script/vocabulary.g.ts +1 -0
- package/src/script/words/narrative.ts +44 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lingfan-engine/editor",
|
|
3
|
-
"version": "0.
|
|
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.
|
|
16
|
+
"@lingfan-engine/core": "^0.2.0",
|
|
17
17
|
"zod": "^4.6.5"
|
|
18
18
|
},
|
|
19
19
|
"license": "Apache-2.0",
|
package/src/contracts/texts.ts
CHANGED
|
@@ -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
|
|
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 归组不合法。 */
|
package/src/i18n/surfaces.ts
CHANGED
|
@@ -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
|
-
|
|
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
package/src/schema/catalog.ts
CHANGED
|
@@ -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
|
|
package/src/script/index.ts
CHANGED
|
@@ -52,9 +52,20 @@ export {
|
|
|
52
52
|
} from "./words/index.ts";
|
|
53
53
|
|
|
54
54
|
// —— 叙事 ——
|
|
55
|
-
export {
|
|
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(
|
|
163
|
-
|
|
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)——作者侧补全优于校验面放宽 */
|