@hankchen/canvas-editor 0.4.0 → 0.5.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/dist/index.d.ts +258 -116
- package/dist/index.js +219 -67
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -602,7 +602,6 @@ function compileRefNode(raw, rawRef, declPath, ctx) {
|
|
|
602
602
|
}
|
|
603
603
|
function compileInlineNode(raw, declPath, ctx) {
|
|
604
604
|
const node = annotationsOf(raw);
|
|
605
|
-
if (raw["additionalProperties"] === true) node.open = true;
|
|
606
605
|
const rawProperties = raw["properties"];
|
|
607
606
|
if (isPlainObject(rawProperties)) {
|
|
608
607
|
const properties = /* @__PURE__ */ new Map();
|
|
@@ -616,6 +615,23 @@ function compileInlineNode(raw, declPath, ctx) {
|
|
|
616
615
|
if (isPlainObject(rawItems)) node.items = compileSchemaNode(rawItems, `${declPath}/items`, ctx);
|
|
617
616
|
return node;
|
|
618
617
|
}
|
|
618
|
+
var OPEN_MAP_DETAIL_SUFFIX = "\u2014\u2014\u5F00\u653E\u6620\u5C04\u5951\u7EA6\u5DF2\u5E9F\u9664\uFF0C\u52A8\u6001\u952E\u8BF7\u7531\u5BBF\u4E3B\u7EC4\u88C5\u6210\u5177\u4F53 properties \u540E\u6CE8\u5165";
|
|
619
|
+
function findOpenMapping(raw, path) {
|
|
620
|
+
if (Array.isArray(raw)) {
|
|
621
|
+
for (let i = 0; i < raw.length; i += 1) {
|
|
622
|
+
const hit = findOpenMapping(raw[i], `${path}/${i}`);
|
|
623
|
+
if (hit !== null) return hit;
|
|
624
|
+
}
|
|
625
|
+
return null;
|
|
626
|
+
}
|
|
627
|
+
if (!isPlainObject(raw)) return null;
|
|
628
|
+
if (raw["additionalProperties"] === true) return path;
|
|
629
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
630
|
+
const hit = findOpenMapping(value, `${path}/${pointerEscape(key)}`);
|
|
631
|
+
if (hit !== null) return hit;
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
619
635
|
function parseExpressionSchema(raw) {
|
|
620
636
|
if (!isPlainObject(raw)) {
|
|
621
637
|
return { ok: false, reason: "invalid_schema", detail: "schema \u6839\u5FC5\u987B\u662F\u5BF9\u8C61\u58F0\u660E" };
|
|
@@ -631,6 +647,10 @@ function parseExpressionSchema(raw) {
|
|
|
631
647
|
if (!isPlainObject(rawProperties)) {
|
|
632
648
|
return { ok: false, reason: "invalid_schema", detail: "\u6839\u58F0\u660E\u7F3A properties \u5C42\u7EA7\uFF08schema \u5373\u8F7D\u8377\u5F62\u72B6\uFF0C\u5FC5\u987B\u58F0\u660E\u9876\u5C42\u952E\uFF09" };
|
|
633
649
|
}
|
|
650
|
+
const openPath = findOpenMapping(raw, "#");
|
|
651
|
+
if (openPath !== null) {
|
|
652
|
+
return { ok: false, reason: "open_map_unsupported", detail: `"${openPath}" \u58F0\u660E\u4E86 additionalProperties: true${OPEN_MAP_DETAIL_SUFFIX}` };
|
|
653
|
+
}
|
|
634
654
|
const diagnostics = [];
|
|
635
655
|
const schema = compileSchemaNode(raw, "#", { root: raw, diagnostics, activePath: /* @__PURE__ */ new Set() });
|
|
636
656
|
return { ok: true, schema, diagnostics };
|
|
@@ -672,7 +692,7 @@ function schemaNodeAtPath(node, segments) {
|
|
|
672
692
|
function schemaChildEntries(node) {
|
|
673
693
|
if (node.type === "array" || node.items !== void 0) return { entries: [] };
|
|
674
694
|
const entries = node.properties !== void 0 ? [...node.properties].map(([key, child]) => ({ key, node: child })) : [];
|
|
675
|
-
return
|
|
695
|
+
return { entries };
|
|
676
696
|
}
|
|
677
697
|
function resolveRowSchema(root, rowsPath) {
|
|
678
698
|
const segments = rowsPath.split(".");
|
|
@@ -712,6 +732,9 @@ function skipWs(expr, pos) {
|
|
|
712
732
|
while (pos < expr.length && WS_CHARS.includes(expr[pos])) pos += 1;
|
|
713
733
|
return pos;
|
|
714
734
|
}
|
|
735
|
+
function fail(kind, start, end) {
|
|
736
|
+
return { ok: false, error: "expression_syntax_error", kind, at: { start, end } };
|
|
737
|
+
}
|
|
715
738
|
function isCallFragment(expr) {
|
|
716
739
|
let head = 0;
|
|
717
740
|
while (head < expr.length && !HEAD_STOP_CHARS.includes(expr[head])) head += 1;
|
|
@@ -723,26 +746,30 @@ function isCallFragment(expr) {
|
|
|
723
746
|
function parseExpressionCall(expr) {
|
|
724
747
|
let head = 0;
|
|
725
748
|
while (head < expr.length && !HEAD_STOP_CHARS.includes(expr[head])) head += 1;
|
|
726
|
-
if (head === 0) return
|
|
749
|
+
if (head === 0) return fail("invalid_path", 0, expr.length);
|
|
727
750
|
const name = expr.slice(0, head);
|
|
728
751
|
let pos = skipWs(expr, head);
|
|
729
|
-
if (pos >= expr.length || expr[pos] !== "(") return
|
|
752
|
+
if (pos >= expr.length || expr[pos] !== "(") return fail("missing_open_paren", head, expr.length);
|
|
753
|
+
const openParen = pos;
|
|
730
754
|
pos = skipWs(expr, pos + 1);
|
|
731
755
|
const args = [];
|
|
756
|
+
const spans = [];
|
|
732
757
|
if (pos < expr.length && expr[pos] === ")") {
|
|
733
758
|
pos += 1;
|
|
734
759
|
} else {
|
|
735
760
|
for (; ; ) {
|
|
736
|
-
if (pos >= expr.length) return
|
|
761
|
+
if (pos >= expr.length) return fail("unbalanced_paren", openParen, expr.length);
|
|
737
762
|
const arg = parseCallArg(expr, pos);
|
|
738
763
|
if (!arg.ok) return arg;
|
|
739
764
|
args.push(arg.value);
|
|
765
|
+
spans.push({ start: arg.start, end: arg.end });
|
|
740
766
|
pos = skipWs(expr, arg.end);
|
|
741
|
-
if (pos >= expr.length) return
|
|
767
|
+
if (pos >= expr.length) return fail("unbalanced_paren", openParen, expr.length);
|
|
742
768
|
if (expr[pos] === ",") {
|
|
769
|
+
const comma = pos;
|
|
743
770
|
pos = skipWs(expr, pos + 1);
|
|
744
771
|
if (pos < expr.length && expr[pos] === ")") {
|
|
745
|
-
return
|
|
772
|
+
return fail("empty_arg_slot", comma + 1, pos);
|
|
746
773
|
}
|
|
747
774
|
continue;
|
|
748
775
|
}
|
|
@@ -750,26 +777,35 @@ function parseExpressionCall(expr) {
|
|
|
750
777
|
pos += 1;
|
|
751
778
|
break;
|
|
752
779
|
}
|
|
753
|
-
return
|
|
780
|
+
return fail("bad_arg_syntax", pos, pos + 1);
|
|
754
781
|
}
|
|
755
782
|
}
|
|
756
|
-
|
|
757
|
-
if (
|
|
758
|
-
|
|
783
|
+
pos = skipWs(expr, pos);
|
|
784
|
+
if (pos !== expr.length) return fail("trailing_after_call", pos, expr.length);
|
|
785
|
+
if (args[0] !== void 0 && args[0].type !== "path") {
|
|
786
|
+
const span = spans[0];
|
|
787
|
+
return fail("first_arg_not_path", span.start, span.end);
|
|
788
|
+
}
|
|
789
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
790
|
+
const arg = args[i];
|
|
791
|
+
const span = spans[i];
|
|
759
792
|
if (arg.type === "path" && !parseExpressionPath(arg.value).ok) {
|
|
760
|
-
return
|
|
793
|
+
return fail("invalid_path_arg", span.start, span.end);
|
|
761
794
|
}
|
|
762
795
|
}
|
|
763
796
|
return { ok: true, name, args };
|
|
764
797
|
}
|
|
765
798
|
function parseCallArg(expr, pos) {
|
|
766
799
|
const len = expr.length;
|
|
800
|
+
const start = pos;
|
|
767
801
|
const ch = expr[pos];
|
|
768
802
|
if (ch === "'") {
|
|
769
803
|
let cursor = pos + 1;
|
|
770
804
|
let buf = "";
|
|
771
805
|
for (; ; ) {
|
|
772
|
-
if (cursor >= len)
|
|
806
|
+
if (cursor >= len) {
|
|
807
|
+
return { ok: false, error: "expression_syntax_error", kind: "unterminated_string", at: { start, end: len } };
|
|
808
|
+
}
|
|
773
809
|
if (expr[cursor] === "'") {
|
|
774
810
|
if (cursor + 1 < len && expr[cursor + 1] === "'") {
|
|
775
811
|
buf += "'";
|
|
@@ -782,10 +818,9 @@ function parseCallArg(expr, pos) {
|
|
|
782
818
|
buf += expr[cursor];
|
|
783
819
|
cursor += 1;
|
|
784
820
|
}
|
|
785
|
-
return { ok: true, value: { type: "string", value: buf }, end: cursor };
|
|
821
|
+
return { ok: true, value: { type: "string", value: buf }, start, end: cursor };
|
|
786
822
|
}
|
|
787
823
|
if (ch >= "0" && ch <= "9") {
|
|
788
|
-
const start2 = pos;
|
|
789
824
|
const isDigitAt = (index) => {
|
|
790
825
|
const c = expr[index];
|
|
791
826
|
return c !== void 0 && c >= "0" && c <= "9";
|
|
@@ -795,13 +830,14 @@ function parseCallArg(expr, pos) {
|
|
|
795
830
|
pos += 1;
|
|
796
831
|
while (isDigitAt(pos)) pos += 1;
|
|
797
832
|
}
|
|
798
|
-
return { ok: true, value: { type: "number", value: expr.slice(
|
|
833
|
+
return { ok: true, value: { type: "number", value: expr.slice(start, pos) }, start, end: pos };
|
|
799
834
|
}
|
|
800
|
-
const start = pos;
|
|
801
835
|
while (pos < len && expr[pos] !== "," && expr[pos] !== ")") pos += 1;
|
|
802
836
|
const raw = phpTrim(expr.slice(start, pos));
|
|
803
|
-
if (raw === "")
|
|
804
|
-
|
|
837
|
+
if (raw === "") {
|
|
838
|
+
return { ok: false, error: "expression_syntax_error", kind: "empty_arg_slot", at: { start, end: pos } };
|
|
839
|
+
}
|
|
840
|
+
return { ok: true, value: { type: "path", value: raw }, start, end: pos };
|
|
805
841
|
}
|
|
806
842
|
function expressionCallSlotAtEnd(expr) {
|
|
807
843
|
let head = 0;
|
|
@@ -854,12 +890,20 @@ function phpTrim2(value) {
|
|
|
854
890
|
while (end > start && PHP_TRIM_CHARS.includes(value[end - 1])) end -= 1;
|
|
855
891
|
return value.slice(start, end);
|
|
856
892
|
}
|
|
857
|
-
function
|
|
858
|
-
if (expr === "") return "
|
|
893
|
+
function fragmentDiagnostic(expr) {
|
|
894
|
+
if (expr === "") return { kind: "empty_fragment", at: { start: 0, end: 0 } };
|
|
859
895
|
if (isCallFragment(expr)) {
|
|
860
|
-
|
|
896
|
+
const parsed = parseExpressionCall(expr);
|
|
897
|
+
if (parsed.ok) return null;
|
|
898
|
+
return { kind: parsed.kind, at: parsed.at };
|
|
861
899
|
}
|
|
862
|
-
|
|
900
|
+
if (parseExpressionPath(expr).ok) return null;
|
|
901
|
+
return { kind: "invalid_path", at: { start: 0, end: expr.length } };
|
|
902
|
+
}
|
|
903
|
+
function leadingPhpTrim(value) {
|
|
904
|
+
let start = 0;
|
|
905
|
+
while (start < value.length && PHP_TRIM_CHARS.includes(value[start])) start += 1;
|
|
906
|
+
return start;
|
|
863
907
|
}
|
|
864
908
|
function scanExpressionFragments(template) {
|
|
865
909
|
const parts = [];
|
|
@@ -880,8 +924,26 @@ function scanExpressionFragments(template) {
|
|
|
880
924
|
}
|
|
881
925
|
const raw = template.slice(open + 2, close);
|
|
882
926
|
const expr = phpTrim2(raw);
|
|
883
|
-
|
|
884
|
-
|
|
927
|
+
let diagnostic = null;
|
|
928
|
+
const found = fragmentDiagnostic(expr);
|
|
929
|
+
if (found) {
|
|
930
|
+
const exprStart = open + 2 + leadingPhpTrim(raw);
|
|
931
|
+
diagnostic = {
|
|
932
|
+
kind: found.kind,
|
|
933
|
+
start: open,
|
|
934
|
+
end: close + 2,
|
|
935
|
+
at: { start: exprStart + found.at.start, end: exprStart + found.at.end }
|
|
936
|
+
};
|
|
937
|
+
}
|
|
938
|
+
parts.push({
|
|
939
|
+
kind: "fragment",
|
|
940
|
+
start: open,
|
|
941
|
+
end: close + 2,
|
|
942
|
+
raw,
|
|
943
|
+
expr,
|
|
944
|
+
error: diagnostic !== null ? "expression_syntax_error" : null,
|
|
945
|
+
diagnostic
|
|
946
|
+
});
|
|
885
947
|
pos = close + 2;
|
|
886
948
|
open = template.indexOf("{{", pos);
|
|
887
949
|
}
|
|
@@ -3084,11 +3146,10 @@ var EditorSession = class {
|
|
|
3084
3146
|
* 进入文本编辑:路径必须解析到 TextLayer。会话只进 ui 分支(不进历史),
|
|
3085
3147
|
* live 文本住在绑定层的 textarea,内容层随即跳绘该层文本(防重影)。
|
|
3086
3148
|
*
|
|
3087
|
-
*
|
|
3088
|
-
*
|
|
3089
|
-
*
|
|
3090
|
-
*
|
|
3091
|
-
* 不重锚);换层进入按新层现状重锚。
|
|
3149
|
+
* 内容模式活标志(ADR 0019)初值按进入瞬间 `layer.expression !== null` 取定
|
|
3150
|
+
* (与文档现态一致),此后不再冻结——pill 切换与内核 toggleDataMode 会话期
|
|
3151
|
+
* 都只翻标志,提交去向由退出时现态裁决(见 commitTextEdit)。同路径重复进入
|
|
3152
|
+
* 短路(会话不重建,接管序号延续);换层进入按新层现状取初值。
|
|
3092
3153
|
*/
|
|
3093
3154
|
beginTextEdit(path) {
|
|
3094
3155
|
const layer = this.textLayerAt(path);
|
|
@@ -3104,14 +3165,14 @@ var EditorSession = class {
|
|
|
3104
3165
|
* 提交与随后 blur 竞态),会话清空后后续调用幂等,保证一次退出恰一步历史。
|
|
3105
3166
|
*
|
|
3106
3167
|
* 提交矩阵(canvas-web-expression-editing 工单 01,spec 决策 3,空串判定先于
|
|
3107
|
-
*
|
|
3108
|
-
*
|
|
3109
|
-
*
|
|
3110
|
-
*
|
|
3168
|
+
* 片段判定;对比对象与去向裁决按会话标志**退出时现态**——活标志语义,ADR
|
|
3169
|
+
* 0019,取代旧「进入时快照」):
|
|
3170
|
+
* - 串与文档一致:不进历史(进出编辑零噪声),返回 false——对比对象随退出时
|
|
3171
|
+
* 会话标志取:表达式现态对比 `layer.expression`、静态现态对比 `layer.text`;
|
|
3111
3172
|
* - 空串:删除该图层(复用 deleteLayer 的 splice/重映射语义),任何模式同语义;
|
|
3112
|
-
* -
|
|
3173
|
+
* - 表达式现态且串含 ≥1 语法有效闭合片段(hasValidExpressionFragment):
|
|
3113
3174
|
* updateDataExpression 标记写(保持标记、text 自动重镜像);
|
|
3114
|
-
* -
|
|
3175
|
+
* - 其余(静态现态恒走此支;表达式现态无合法片段回落):updateData 字面写、
|
|
3115
3176
|
* 字面接管解标——含 `{{...}}` 按字面保留,杜绝无片段标记层。
|
|
3116
3177
|
*/
|
|
3117
3178
|
commitTextEdit(text) {
|
|
@@ -3135,8 +3196,10 @@ var EditorSession = class {
|
|
|
3135
3196
|
/**
|
|
3136
3197
|
* 编辑 overlay 的布局描述:与绘制/命中同一套布局策略求出的盒几何与排版字段
|
|
3137
3198
|
* (textarea 以盒左上对位、盒尺寸占位,字号/行高/内边距按场景像素交给 CSS
|
|
3138
|
-
* transform
|
|
3139
|
-
*
|
|
3199
|
+
* transform 缩放)。对当前文档现算(canvas-web-edit-overlay-toolbar 工单 01,
|
|
3200
|
+
* ADR 0019 布局现算):会话期样式/几何权威写实时反映到查询值;verticalAnchorY
|
|
3201
|
+
* 按当前文档文本计算——live 键入只住 textarea 草稿不落文档,键入不触发文档
|
|
3202
|
+
* 变更,锚点天然稳定不跳。非文本层/路径失效返回 null。
|
|
3140
3203
|
*/
|
|
3141
3204
|
textEditLayout(path) {
|
|
3142
3205
|
const layer = this.textLayerAt(path);
|
|
@@ -3213,7 +3276,8 @@ var EditorSession = class {
|
|
|
3213
3276
|
* 原地不动——事务后派生列表在被替换处缩一位,原下标即「下一处」(「替换后
|
|
3214
3277
|
* 自动跳下一处」靠派生天然成立,末处替换则钳位停在尾)。写值经 replaceHitsInDraft
|
|
3215
3278
|
* 与文本编辑提交同门(text 直写 + expression 保持 null)。无文档/空 query/
|
|
3216
|
-
* 无命中空转返回 false
|
|
3279
|
+
* 无命中空转返回 false。命中编辑层时草稿接管(会话内容独占,工单 02——
|
|
3280
|
+
* 替换结果出现在正在编辑的框里)。
|
|
3217
3281
|
*/
|
|
3218
3282
|
replaceOne() {
|
|
3219
3283
|
const doc = this.store.doc;
|
|
@@ -3222,7 +3286,7 @@ var EditorSession = class {
|
|
|
3222
3286
|
const matches = scanTextMatches(doc, query);
|
|
3223
3287
|
const hit = matches[clampFindCursor(this.store.ui.find.cursor, matches.length)];
|
|
3224
3288
|
if (!hit) return false;
|
|
3225
|
-
this.
|
|
3289
|
+
this.transactContent([hit.path], (draft) => {
|
|
3226
3290
|
replaceHitsInDraft(draft, [hit], this.store.ui.find.replacement);
|
|
3227
3291
|
});
|
|
3228
3292
|
return true;
|
|
@@ -3231,7 +3295,8 @@ var EditorSession = class {
|
|
|
3231
3295
|
* 全部替换(查找条「全部替换」按钮):命中(path + 偏移对)在事务前按原文
|
|
3232
3296
|
* 快照一次算定,单事务内按同字段偏移降序应用、替换产物不重扫(replacement
|
|
3233
3297
|
* 含查询串不死循环)——一次调用 = 一步历史,undo 一次全回。返回应用处数
|
|
3234
|
-
* (计数反馈「已替换 N 处」);无文档/空 query 空转返回 0
|
|
3298
|
+
* (计数反馈「已替换 N 处」);无文档/空 query 空转返回 0。命中编辑层时
|
|
3299
|
+
* 草稿接管(会话内容独占,工单 02)。
|
|
3235
3300
|
*/
|
|
3236
3301
|
replaceAll() {
|
|
3237
3302
|
const doc = this.store.doc;
|
|
@@ -3240,7 +3305,7 @@ var EditorSession = class {
|
|
|
3240
3305
|
const hits = scanTextMatches(doc, query);
|
|
3241
3306
|
if (hits.length === 0) return 0;
|
|
3242
3307
|
let applied = 0;
|
|
3243
|
-
this.
|
|
3308
|
+
this.transactContent(hits.map((hit) => hit.path), (draft) => {
|
|
3244
3309
|
applied = replaceHitsInDraft(draft, hits, this.store.ui.find.replacement);
|
|
3245
3310
|
});
|
|
3246
3311
|
return applied;
|
|
@@ -3294,9 +3359,13 @@ var EditorSession = class {
|
|
|
3294
3359
|
* null,值与标记的镜像关系随之解除——标记态文档经字面编辑退化为字面态,
|
|
3295
3360
|
* 不可自动恢复属预期(spec §3.7 兼容面)。
|
|
3296
3361
|
* 表/行/格无数据字段,空转。
|
|
3362
|
+
*
|
|
3363
|
+
* 会话内容独占(工单 02,ADR 0019 内核底线面):命中编辑层的写落文档的同时
|
|
3364
|
+
* 草稿接管(draftSeq 递增,绑定层全量替换 textarea 草稿)——旁门程序化写的
|
|
3365
|
+
* 结果进编辑框草稿而非被退出提交覆盖,任何方向不丢字。
|
|
3297
3366
|
*/
|
|
3298
3367
|
updateData(path, value, options = {}) {
|
|
3299
|
-
this.
|
|
3368
|
+
this.transactContent([path], (draft) => {
|
|
3300
3369
|
const layer = resolveLayer(draft, path);
|
|
3301
3370
|
if (!layer) return;
|
|
3302
3371
|
switch (layer.type) {
|
|
@@ -3327,9 +3396,11 @@ var EditorSession = class {
|
|
|
3327
3396
|
* 字面态空值归一交给 updateData/decode 分支(这里不清值,字面接管语义)。
|
|
3328
3397
|
* - 仅三内容层生效,表/行/格/行模板空转(与 updateData 同门)。
|
|
3329
3398
|
* - updateData 语义不变:字面写解除标记仍成立(spec §1 明示两者并排)。
|
|
3399
|
+
* - 命中编辑层的旁门打标同走草稿接管(draftSeq 递增;会话标志不随动——
|
|
3400
|
+
* 活标志只归 pill/toggleDataMode 翻,见 toggleDataMode)。
|
|
3330
3401
|
*/
|
|
3331
3402
|
updateDataExpression(path, expression, options = {}) {
|
|
3332
|
-
this.
|
|
3403
|
+
this.transactContent([path], (draft) => {
|
|
3333
3404
|
const layer = resolveLayer(draft, path);
|
|
3334
3405
|
if (!layer) return;
|
|
3335
3406
|
switch (layer.type) {
|
|
@@ -3350,6 +3421,63 @@ var EditorSession = class {
|
|
|
3350
3421
|
}
|
|
3351
3422
|
}, options);
|
|
3352
3423
|
}
|
|
3424
|
+
/**
|
|
3425
|
+
* 数据字段取值方式切换(静态 ⇆ 表达式,工单 02 自面板收编内核):表达式现态
|
|
3426
|
+
* → 字面接管(updateData,值保持现镜像);静态现态 → 打标(updateDataExpression,
|
|
3427
|
+
* 初值 = 值字段原文)。各为独立一步历史(无 mergeKey)。三内容层分派各取值
|
|
3428
|
+
* 字段,表/行/格/悬空路径空转。
|
|
3429
|
+
*
|
|
3430
|
+
* 活标志防御路由(ADR 0019):路径即编辑中的层时只翻会话活标志(零文档变更
|
|
3431
|
+
* 零历史步)——会话期内容模式切换的唯一写入口是会话标志,文档面锁定归编辑框
|
|
3432
|
+
* 草稿;面板锁定段/pill 显式调用与宿主旁路调用都安全收敛到同一语义。
|
|
3433
|
+
*/
|
|
3434
|
+
toggleDataMode(path) {
|
|
3435
|
+
const editing = this.store.ui.editing;
|
|
3436
|
+
if (editing && pathsEqual(editing.path, path)) {
|
|
3437
|
+
this.store.setEditing({ ...editing, expression: !editing.expression });
|
|
3438
|
+
return;
|
|
3439
|
+
}
|
|
3440
|
+
const layer = this.store.doc ? resolveLayer(this.store.doc, path) : null;
|
|
3441
|
+
if (!layer) return;
|
|
3442
|
+
switch (layer.type) {
|
|
3443
|
+
case "TextLayer":
|
|
3444
|
+
if (layer.expression !== null) this.updateData(path, layer.text);
|
|
3445
|
+
else this.updateDataExpression(path, layer.text);
|
|
3446
|
+
return;
|
|
3447
|
+
case "ImageLayer":
|
|
3448
|
+
if (layer.expression !== null) this.updateData(path, layer.src ?? "");
|
|
3449
|
+
else this.updateDataExpression(path, layer.src ?? "");
|
|
3450
|
+
return;
|
|
3451
|
+
case "QrCodeLayer":
|
|
3452
|
+
if (layer.expression !== null) this.updateData(path, layer.value);
|
|
3453
|
+
else this.updateDataExpression(path, layer.value);
|
|
3454
|
+
return;
|
|
3455
|
+
default:
|
|
3456
|
+
return;
|
|
3457
|
+
}
|
|
3458
|
+
}
|
|
3459
|
+
/**
|
|
3460
|
+
* 内容写事务的统一形(会话内容独占工单 02):transact 落库 + 命中编辑层的
|
|
3461
|
+
* 草稿接管收口——updateData/updateDataExpression/replaceOne/replaceAll 四个
|
|
3462
|
+
* 旁门写入口同门(接管判定见 takeoverDraftIfNeeded;零变更事务天然短路)。
|
|
3463
|
+
*/
|
|
3464
|
+
transactContent(paths, recipe, options = {}) {
|
|
3465
|
+
const docBefore = this.store.doc;
|
|
3466
|
+
this.store.transact(recipe, options);
|
|
3467
|
+
this.takeoverDraftIfNeeded(paths, docBefore);
|
|
3468
|
+
}
|
|
3469
|
+
/**
|
|
3470
|
+
* 草稿接管收口(工单 02):paths 中有编辑层且文档确实变更(引用比较——零变更
|
|
3471
|
+
* 事务 transact 空转不通知,接管序号也不动)时递增会话 draftSeq 并广播
|
|
3472
|
+
* ui.editing。绑定层据此全量替换 textarea 草稿为文档新值并刷新表达式诊断。
|
|
3473
|
+
* commitTextEdit 先清会话再落库,天然不复入接管。
|
|
3474
|
+
*/
|
|
3475
|
+
takeoverDraftIfNeeded(paths, docBefore) {
|
|
3476
|
+
const editing = this.store.ui.editing;
|
|
3477
|
+
if (!editing || this.store.doc === docBefore) return;
|
|
3478
|
+
if (!paths.some((path) => pathsEqual(path, editing.path))) return;
|
|
3479
|
+
this.store.setEditing({ ...editing, draftSeq: (editing.draftSeq ?? 0) + 1 });
|
|
3480
|
+
}
|
|
3353
3481
|
/** 更新画布级字段(未选中图层时面板的宽/高写入口);键由类型收窄为 width/height */
|
|
3354
3482
|
updateCanvasProp(key, value, options = {}) {
|
|
3355
3483
|
this.store.transact((draft) => {
|
|
@@ -3361,12 +3489,14 @@ var EditorSession = class {
|
|
|
3361
3489
|
* 注入数据源 schema 声明(载荷形态,D1 钉定:声明即 compile(canvas, dataset)
|
|
3362
3490
|
* 收到的 data 载荷形状,根上下文候选 = 载荷顶层键)。声明经
|
|
3363
3491
|
* normalizeExpressionSchemaSource 一次性编译收口——根级结构性问题(保留键
|
|
3364
|
-
* row/$ 前缀,前移填充期 reserved_root_key;根非对象/缺 properties
|
|
3365
|
-
*
|
|
3366
|
-
*
|
|
3367
|
-
*
|
|
3368
|
-
*
|
|
3369
|
-
*
|
|
3492
|
+
* row/$ 前缀,前移填充期 reserved_root_key;根非对象/缺 properties 层级)
|
|
3493
|
+
* 与开放映射契约废除(open_map_unsupported,ADR 0020:文档任意层
|
|
3494
|
+
* additionalProperties: true 显式拒绝,动态键归宿主组装成具体 properties 后
|
|
3495
|
+
* 注入)降级为无候选 + console 警告,局部故障($ref 断链/外部指针/环引用/
|
|
3496
|
+
* 目标形态不符)不拒绝:该节点降叶子 + 注入期汇总告警一次,树其余部分照常
|
|
3497
|
+
* 服务,不抛错不弹错(辅助声明不作权威);null/undefined = 清除声明。声明只
|
|
3498
|
+
* 住 store ui 分支:不进 graph、不落 localStorage、不进 wire;openDocument
|
|
3499
|
+
* 换文档不重置,重注入/清除走同一入口。签名与「注入即整体替换」语义不变(D6)。
|
|
3370
3500
|
*/
|
|
3371
3501
|
setDataSourceSchema(raw) {
|
|
3372
3502
|
this.store.setDataSourceSchema(
|
|
@@ -4241,6 +4371,29 @@ function classifyWheel(input) {
|
|
|
4241
4371
|
return "pan";
|
|
4242
4372
|
}
|
|
4243
4373
|
|
|
4374
|
+
// src/shared/expressionDiagnostics.ts
|
|
4375
|
+
function expressionDiagnostics(template) {
|
|
4376
|
+
const found = [];
|
|
4377
|
+
for (const part of scanExpressionFragments(template).parts) {
|
|
4378
|
+
if (part.kind === "fragment" && part.diagnostic !== null) found.push(part.diagnostic);
|
|
4379
|
+
}
|
|
4380
|
+
return found;
|
|
4381
|
+
}
|
|
4382
|
+
|
|
4383
|
+
// src/shared/resourcePath.ts
|
|
4384
|
+
function wholeTemplateDiagnostic(template, kind) {
|
|
4385
|
+
return { kind, start: 0, end: template.length, at: { start: 0, end: template.length } };
|
|
4386
|
+
}
|
|
4387
|
+
function resourcePathDiagnostics(template) {
|
|
4388
|
+
const anchored = template.endsWith("\n") ? template.slice(0, -1) : template;
|
|
4389
|
+
const matched = /^\{\{([^{}]*)\}\}$/.exec(anchored);
|
|
4390
|
+
if (matched === null) return [wholeTemplateDiagnostic(template, "resource_not_single_ref")];
|
|
4391
|
+
const path = phpTrim2(matched[1] ?? "");
|
|
4392
|
+
if (path === "") return [wholeTemplateDiagnostic(template, "empty_fragment")];
|
|
4393
|
+
if (path.includes("(")) return [wholeTemplateDiagnostic(template, "resource_function_call")];
|
|
4394
|
+
return [];
|
|
4395
|
+
}
|
|
4396
|
+
|
|
4244
4397
|
// src/shared/expressionFunctions.ts
|
|
4245
4398
|
var EXPRESSION_FUNCTIONS = [
|
|
4246
4399
|
{ name: "date", signature: "date(path[, format])", summary: "\u65E5\u671F\u683C\u5F0F\u5316\uFF08moment \u98CE\u683C token\uFF0C\u9884\u8BBE date/datetime\uFF09" },
|
|
@@ -4272,7 +4425,7 @@ function candidateMeta(node) {
|
|
|
4272
4425
|
}
|
|
4273
4426
|
function childCandidate(entry, prefix) {
|
|
4274
4427
|
const path = prefix === "" ? entry.key : `${prefix}.${entry.key}`;
|
|
4275
|
-
return { path, segment: entry.key, ...candidateMeta(entry.node) };
|
|
4428
|
+
return { path, segment: entry.key, kind: "path", ...candidateMeta(entry.node) };
|
|
4276
4429
|
}
|
|
4277
4430
|
function callCandidatesFor(entry, path, exprLen) {
|
|
4278
4431
|
const extras = [];
|
|
@@ -4283,6 +4436,7 @@ function callCandidatesFor(entry, path, exprLen) {
|
|
|
4283
4436
|
cursorBack: 2,
|
|
4284
4437
|
replaceLen: exprLen,
|
|
4285
4438
|
filterKey: entry.key,
|
|
4439
|
+
kind: "function",
|
|
4286
4440
|
description: "\u65E5\u671F\u683C\u5F0F\u5316",
|
|
4287
4441
|
type: "\u51FD\u6570"
|
|
4288
4442
|
});
|
|
@@ -4293,6 +4447,7 @@ function callCandidatesFor(entry, path, exprLen) {
|
|
|
4293
4447
|
segment: `${fn.name}(${path})`,
|
|
4294
4448
|
replaceLen: exprLen,
|
|
4295
4449
|
filterKey: entry.key,
|
|
4450
|
+
kind: "function",
|
|
4296
4451
|
description: fn.summary,
|
|
4297
4452
|
type: "\u51FD\u6570"
|
|
4298
4453
|
});
|
|
@@ -4310,6 +4465,7 @@ function functionCandidates() {
|
|
|
4310
4465
|
path: fn.signature,
|
|
4311
4466
|
segment: `${fn.name}()`,
|
|
4312
4467
|
cursorBack: 1,
|
|
4468
|
+
kind: "function",
|
|
4313
4469
|
description: fn.summary,
|
|
4314
4470
|
type: "\u51FD\u6570"
|
|
4315
4471
|
}));
|
|
@@ -4325,12 +4481,12 @@ function dateFormatCandidates() {
|
|
|
4325
4481
|
function headCandidates(schema, context, rowSchema, exprLen, withBuckets) {
|
|
4326
4482
|
const view = schemaChildEntries(schema);
|
|
4327
4483
|
const candidates = view.entries.flatMap((entry) => childCandidates(entry, "", exprLen, withBuckets));
|
|
4328
|
-
candidates.push({ path: "$root", segment: "$root" });
|
|
4484
|
+
candidates.push({ path: "$root", segment: "$root", kind: "path" });
|
|
4329
4485
|
if (context === "row") {
|
|
4330
|
-
candidates.push(rowSchema ? { path: "row", segment: "row", ...candidateMeta(rowSchema) } : { path: "row", segment: "row" });
|
|
4331
|
-
candidates.push({ path: "$index", segment: "$index" });
|
|
4486
|
+
candidates.push(rowSchema ? { path: "row", segment: "row", kind: "path", ...candidateMeta(rowSchema) } : { path: "row", segment: "row", kind: "path" });
|
|
4487
|
+
candidates.push({ path: "$index", segment: "$index", kind: "path" });
|
|
4332
4488
|
}
|
|
4333
|
-
return
|
|
4489
|
+
return candidates;
|
|
4334
4490
|
}
|
|
4335
4491
|
function callSlotResult(schema, query) {
|
|
4336
4492
|
const slot = expressionCallSlotAtEnd(query.expr);
|
|
@@ -4359,7 +4515,7 @@ function enumeratePathCandidates(schema, query, expr, allowCallSuggestions) {
|
|
|
4359
4515
|
ok: true,
|
|
4360
4516
|
prefix: "",
|
|
4361
4517
|
partial: "",
|
|
4362
|
-
|
|
4518
|
+
candidates: headCandidates(schema, query.context, query.rowSchema, expr.length, false)
|
|
4363
4519
|
};
|
|
4364
4520
|
}
|
|
4365
4521
|
return { ok: false, reason: "expression_syntax_error" };
|
|
@@ -4389,8 +4545,7 @@ function enumeratePathCandidates(schema, query, expr, allowCallSuggestions) {
|
|
|
4389
4545
|
ok: true,
|
|
4390
4546
|
prefix: "",
|
|
4391
4547
|
partial,
|
|
4392
|
-
candidates: [...view2
|
|
4393
|
-
...view2.open === true ? { open: true } : {}
|
|
4548
|
+
candidates: [...view2, ...fns]
|
|
4394
4549
|
};
|
|
4395
4550
|
}
|
|
4396
4551
|
let base;
|
|
@@ -4420,8 +4575,7 @@ function enumeratePathCandidates(schema, query, expr, allowCallSuggestions) {
|
|
|
4420
4575
|
ok: true,
|
|
4421
4576
|
prefix,
|
|
4422
4577
|
partial,
|
|
4423
|
-
candidates: view.entries.flatMap((entry) => childCandidates(entry, prefix, expr.length, withBuckets))
|
|
4424
|
-
...view.open === true ? { open: true } : {}
|
|
4578
|
+
candidates: view.entries.flatMap((entry) => childCandidates(entry, prefix, expr.length, withBuckets))
|
|
4425
4579
|
};
|
|
4426
4580
|
}
|
|
4427
4581
|
function enumerateExpressionCandidates(schema, query) {
|
|
@@ -4439,7 +4593,7 @@ function isArrayNode(node) {
|
|
|
4439
4593
|
return node.type === "array" || node.items !== void 0;
|
|
4440
4594
|
}
|
|
4441
4595
|
function isScalarNode(node) {
|
|
4442
|
-
return !isArrayNode(node) && node.properties === void 0
|
|
4596
|
+
return !isArrayNode(node) && node.properties === void 0;
|
|
4443
4597
|
}
|
|
4444
4598
|
function childCandidate2(key, node, prefix) {
|
|
4445
4599
|
const candidate = {
|
|
@@ -4459,7 +4613,7 @@ function levelCandidates(base, prefix) {
|
|
|
4459
4613
|
candidates.push(childCandidate2(key, child, prefix));
|
|
4460
4614
|
}
|
|
4461
4615
|
}
|
|
4462
|
-
return
|
|
4616
|
+
return candidates;
|
|
4463
4617
|
}
|
|
4464
4618
|
function enumerateRowsPathCandidates(startSchema, input) {
|
|
4465
4619
|
if (startSchema === null) return null;
|
|
@@ -4475,11 +4629,7 @@ function enumerateRowsPathCandidates(startSchema, input) {
|
|
|
4475
4629
|
}
|
|
4476
4630
|
if (isArrayNode(base) || isScalarNode(base)) return null;
|
|
4477
4631
|
const view = levelCandidates(base, completed.join("."));
|
|
4478
|
-
return {
|
|
4479
|
-
partial,
|
|
4480
|
-
candidates: view.candidates,
|
|
4481
|
-
...view.open === true ? { open: true } : {}
|
|
4482
|
-
};
|
|
4632
|
+
return { partial, candidates: view };
|
|
4483
4633
|
}
|
|
4484
4634
|
function resolveRowsPathStartSchema(doc, path, rootSchema) {
|
|
4485
4635
|
if (rootSchema === null) return null;
|
|
@@ -4632,6 +4782,7 @@ export {
|
|
|
4632
4782
|
enumerateExpressionCandidates,
|
|
4633
4783
|
enumerateRowsPathCandidates,
|
|
4634
4784
|
expressionCallSlotAtEnd,
|
|
4785
|
+
expressionDiagnostics,
|
|
4635
4786
|
expressionFragmentAtCursor,
|
|
4636
4787
|
fitRect,
|
|
4637
4788
|
fitViewport,
|
|
@@ -4683,6 +4834,7 @@ export {
|
|
|
4683
4834
|
resolveRowsPathStartSchema,
|
|
4684
4835
|
resolveSnap,
|
|
4685
4836
|
resolveSnapPoints,
|
|
4837
|
+
resourcePathDiagnostics,
|
|
4686
4838
|
rootLayerOf,
|
|
4687
4839
|
scanExpressionFragments,
|
|
4688
4840
|
scanTextMatches,
|