@yaoxiu/marketing-dsl 1.5.2 → 2.1.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/README.md +120 -17
- package/dist/{chunk-FKZKLCVV.js → chunk-7TWWOK7Q.js} +147 -7
- package/dist/docs/index.cjs +227 -45
- package/dist/docs/index.d.cts +1 -1
- package/dist/docs/index.d.ts +1 -1
- package/dist/docs/index.js +200 -46
- package/dist/index.cjs +194 -20
- package/dist/index.d.cts +75 -4
- package/dist/index.d.ts +75 -4
- package/dist/index.js +46 -16
- package/dist/report/index.cjs +47 -30
- package/dist/report/index.d.cts +80 -31
- package/dist/report/index.d.ts +80 -31
- package/dist/report/index.js +46 -31
- package/dist/{types-BgUaJUAu.d.cts → types-Bj0xyWnx.d.cts} +54 -2
- package/dist/{types-BgUaJUAu.d.ts → types-Bj0xyWnx.d.ts} +54 -2
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -327,13 +327,13 @@ function safeImageUrl(input) {
|
|
|
327
327
|
// src/actions.ts
|
|
328
328
|
function createDispatcher(options) {
|
|
329
329
|
const { setState, emit, openView, closeTop, closeAll } = options;
|
|
330
|
-
function
|
|
330
|
+
function run(action, context, interaction) {
|
|
331
331
|
if (!action || !action.type) return;
|
|
332
332
|
const handlers = options.handlers || {};
|
|
333
333
|
const editMode = !!options.editMode;
|
|
334
334
|
switch (action.type) {
|
|
335
335
|
case "sequence":
|
|
336
|
-
(action.actions || []).forEach((item) =>
|
|
336
|
+
(action.actions || []).forEach((item) => run(item, context, interaction));
|
|
337
337
|
return;
|
|
338
338
|
case "navigate": {
|
|
339
339
|
const url = safeUrl(interpolate(action.url, context));
|
|
@@ -343,6 +343,7 @@ function createDispatcher(options) {
|
|
|
343
343
|
}
|
|
344
344
|
const target = action.target || "_blank";
|
|
345
345
|
emit("navigate", { url, target });
|
|
346
|
+
if (!interaction.navigate) interaction.navigate = { url, target };
|
|
346
347
|
if (editMode) return;
|
|
347
348
|
if (typeof window === "undefined") return;
|
|
348
349
|
if (target === "_self") window.location.href = url;
|
|
@@ -356,20 +357,25 @@ function createDispatcher(options) {
|
|
|
356
357
|
return;
|
|
357
358
|
// 关掉栈顶那层;只剩一层时就是整体关闭
|
|
358
359
|
case "close":
|
|
360
|
+
interaction.closes = true;
|
|
359
361
|
closeTop(action.reason);
|
|
360
362
|
return;
|
|
361
363
|
case "closeAll":
|
|
364
|
+
interaction.closes = true;
|
|
362
365
|
closeAll(action.reason);
|
|
363
366
|
return;
|
|
364
367
|
case "setState":
|
|
365
368
|
setState(action.key, interpolate(action.value, context));
|
|
366
369
|
return;
|
|
367
|
-
case "track":
|
|
368
|
-
|
|
370
|
+
case "track": {
|
|
371
|
+
const payload = {
|
|
369
372
|
event: String(interpolate(action.event, context)),
|
|
370
373
|
params: interpolateDeep(action.params || {}, context)
|
|
371
|
-
}
|
|
374
|
+
};
|
|
375
|
+
emit("track", payload);
|
|
376
|
+
if (!interaction.track) interaction.track = payload;
|
|
372
377
|
return;
|
|
378
|
+
}
|
|
373
379
|
case "call": {
|
|
374
380
|
const handler = handlers[action.name];
|
|
375
381
|
if (typeof handler !== "function") {
|
|
@@ -389,6 +395,13 @@ function createDispatcher(options) {
|
|
|
389
395
|
});
|
|
390
396
|
}
|
|
391
397
|
}
|
|
398
|
+
function dispatch(action, context, meta) {
|
|
399
|
+
if (!action || !action.type) return;
|
|
400
|
+
const interaction = { trigger: (meta == null ? void 0 : meta.trigger) || "action" };
|
|
401
|
+
if ((meta == null ? void 0 : meta.reason) !== void 0) interaction.reason = meta.reason;
|
|
402
|
+
run(action, context, interaction);
|
|
403
|
+
emit("interaction", interaction);
|
|
404
|
+
}
|
|
392
405
|
return dispatch;
|
|
393
406
|
}
|
|
394
407
|
|
|
@@ -619,9 +632,7 @@ function resolveNodeStyle(node, layout) {
|
|
|
619
632
|
function resolveTree(input) {
|
|
620
633
|
const { normalized, viewStack, ready } = input;
|
|
621
634
|
const layers = viewStack.map((name) => ({ name, view: normalized.views[name] })).filter((item) => !!item.view);
|
|
622
|
-
const hasPopup =
|
|
623
|
-
(name) => normalized.views[name].type === "popup"
|
|
624
|
-
);
|
|
635
|
+
const hasPopup = layers.some((item) => item.view.type === "popup");
|
|
625
636
|
const countdowns = { endTimes: [], precision: "s" };
|
|
626
637
|
const renderLayers = ready ? layers.map(
|
|
627
638
|
(item, index) => resolveLayer(
|
|
@@ -635,8 +646,10 @@ function resolveTree(input) {
|
|
|
635
646
|
return {
|
|
636
647
|
ready: ready && layers.length > 0,
|
|
637
648
|
hasPopup,
|
|
638
|
-
//
|
|
639
|
-
|
|
649
|
+
// 根容器恒为 relative:它要待在宿主给的位置上(banner / 公告坑位是流式的普通 div,
|
|
650
|
+
// 根容器一脱流坑位就塌成 0、内容按最近定位祖先飘走)。弹窗铺满视口由弹窗层自己
|
|
651
|
+
// `position: fixed` 完成,不再靠根容器撑开,见 layerStyle。
|
|
652
|
+
rootStyle: { position: "relative" },
|
|
640
653
|
layers: renderLayers,
|
|
641
654
|
countdownEndTimes: countdowns.endTimes,
|
|
642
655
|
countdownPrecision: countdowns.precision
|
|
@@ -682,7 +695,15 @@ function resolveLayer(name, view, isTop, input, countdowns) {
|
|
|
682
695
|
};
|
|
683
696
|
}
|
|
684
697
|
function layerStyle(isPopup, isTop) {
|
|
685
|
-
const style = isPopup ? {
|
|
698
|
+
const style = isPopup ? {
|
|
699
|
+
position: "fixed",
|
|
700
|
+
top: "0",
|
|
701
|
+
right: "0",
|
|
702
|
+
bottom: "0",
|
|
703
|
+
left: "0",
|
|
704
|
+
// 弹窗层可能挂在页面中段的坑位里,不提层会被后面的内容盖住
|
|
705
|
+
zIndex: "1000"
|
|
706
|
+
} : {};
|
|
686
707
|
if (!isTop) style.pointerEvents = "none";
|
|
687
708
|
return style;
|
|
688
709
|
}
|
|
@@ -710,7 +731,7 @@ function resolveStageStyle(stage, isPopup, context) {
|
|
|
710
731
|
base.margin = "auto";
|
|
711
732
|
base.flex = "none";
|
|
712
733
|
}
|
|
713
|
-
|
|
734
|
+
const style = Object.assign(
|
|
714
735
|
base,
|
|
715
736
|
{
|
|
716
737
|
// width / height 支持数字(px)、'100%' / 'auto' / calc(...) 等相对单位,也支持 {{ }}
|
|
@@ -724,6 +745,11 @@ function resolveStageStyle(stage, isPopup, context) {
|
|
|
724
745
|
// stage.style 也走一遍插值,这样切 tab 时弹窗本身的背景能跟着变
|
|
725
746
|
toCssStyle(interpolateDeep(stage.style, context))
|
|
726
747
|
);
|
|
748
|
+
if (!isPopup && style.maxWidth !== void 0 && style.margin === void 0) {
|
|
749
|
+
style.marginLeft = "auto";
|
|
750
|
+
style.marginRight = "auto";
|
|
751
|
+
}
|
|
752
|
+
return style;
|
|
727
753
|
}
|
|
728
754
|
function resolveCloseButton(config, layerName, closeTop) {
|
|
729
755
|
if (!config || config.show === false) return void 0;
|
|
@@ -1187,7 +1213,11 @@ function createRuntime(dsl, options = {}) {
|
|
|
1187
1213
|
const view = normalized.views[viewName];
|
|
1188
1214
|
const action = view && view.stage && view.stage[hook];
|
|
1189
1215
|
if (!action) return;
|
|
1190
|
-
|
|
1216
|
+
const reason = extra && extra.closeReason;
|
|
1217
|
+
dispatch(action, Object.assign({}, buildContext(), extra), {
|
|
1218
|
+
trigger: hook === "onShow" ? "show" : "close",
|
|
1219
|
+
reason: typeof reason === "string" ? reason : void 0
|
|
1220
|
+
});
|
|
1191
1221
|
}
|
|
1192
1222
|
function loadData() {
|
|
1193
1223
|
const data = dsl.data || {};
|
|
@@ -1218,8 +1248,8 @@ function createRuntime(dsl, options = {}) {
|
|
|
1218
1248
|
resolvedData = resolved;
|
|
1219
1249
|
loading = false;
|
|
1220
1250
|
notify();
|
|
1221
|
-
emit("ready", { keys: Object.keys(resolved) });
|
|
1222
1251
|
fireLifecycle(normalized.entry, "onShow");
|
|
1252
|
+
emit("ready", { keys: Object.keys(resolved) });
|
|
1223
1253
|
}).catch((error) => {
|
|
1224
1254
|
if (destroyed) return;
|
|
1225
1255
|
loading = false;
|
|
@@ -1266,6 +1296,120 @@ function createRuntime(dsl, options = {}) {
|
|
|
1266
1296
|
};
|
|
1267
1297
|
}
|
|
1268
1298
|
|
|
1299
|
+
// src/user-fields.ts
|
|
1300
|
+
var DEFAULT_USER_FIELDS = [
|
|
1301
|
+
"shopId",
|
|
1302
|
+
"shopName",
|
|
1303
|
+
"collectCount",
|
|
1304
|
+
"albbCollectCount",
|
|
1305
|
+
"isNewUser",
|
|
1306
|
+
"levelId",
|
|
1307
|
+
"levelName",
|
|
1308
|
+
"serviceExpireTime",
|
|
1309
|
+
"remainDays",
|
|
1310
|
+
"userTypeNew",
|
|
1311
|
+
"serviceInfo"
|
|
1312
|
+
];
|
|
1313
|
+
var USER_FIELD_LABELS = {
|
|
1314
|
+
shopId: "\u5E97\u94FA ID",
|
|
1315
|
+
shopName: "\u5E97\u94FA\u540D\u79F0",
|
|
1316
|
+
collectCount: "\u5269\u4F59\u91C7\u96C6\u6B21\u6570",
|
|
1317
|
+
albbCollectCount: "\u5269\u4F59 1688 \u91C7\u96C6\u6B21\u6570",
|
|
1318
|
+
isNewUser: "\u662F\u5426\u65B0\u7528\u6237\uFF080 / 1\uFF0C\u53EF\u76F4\u63A5\u7528\u4E8E visibleWhen\uFF09",
|
|
1319
|
+
levelId: "\u7248\u672C ID",
|
|
1320
|
+
levelName: "\u7248\u672C\u540D\u79F0\uFF0C\u5982\u300C\u4E13\u4E1A\u7248\u300D",
|
|
1321
|
+
serviceExpireTime: "\u670D\u52A1\u5230\u671F\u65F6\u95F4\uFF0C\u53EF\u76F4\u63A5\u5582\u7ED9 countdown \u7684 to",
|
|
1322
|
+
remainDays: "\u670D\u52A1\u5269\u4F59\u5929\u6570",
|
|
1323
|
+
userTypeNew: "\u65B0\u5546\u7528\u6237\u7C7B\u578B\uFF080 \u672A\u8BBE\u7F6E / 1 \u6709\u8D27\u6E90 / 2 \u65E0\u8D27\u6E90 / 3 \u4E24\u8005\u517C\u5907\uFF09",
|
|
1324
|
+
serviceInfo: "\u5BA2\u670D\u4E0E\u793E\u7FA4\u4FE1\u606F\uFF08\u5BF9\u8C61\uFF0C\u6309\u9700\u53D6\u5B50\u5B57\u6BB5\uFF09"
|
|
1325
|
+
};
|
|
1326
|
+
var USER_FIELD_RE = /\buser\s*\.\s*([A-Za-z_$][A-Za-z0-9_$]*)/g;
|
|
1327
|
+
var INTERPOLATION_RE2 = /\{\{([\s\S]*?)\}\}/g;
|
|
1328
|
+
var RAW_EXPRESSION_KEYS = ["visibleWhen"];
|
|
1329
|
+
function editDistance(a, b) {
|
|
1330
|
+
const prev = [];
|
|
1331
|
+
for (let j = 0; j <= b.length; j++) prev[j] = j;
|
|
1332
|
+
for (let i = 1; i <= a.length; i++) {
|
|
1333
|
+
let diagonal = prev[0];
|
|
1334
|
+
prev[0] = i;
|
|
1335
|
+
for (let j = 1; j <= b.length; j++) {
|
|
1336
|
+
const temp = prev[j];
|
|
1337
|
+
prev[j] = Math.min(
|
|
1338
|
+
prev[j] + 1,
|
|
1339
|
+
prev[j - 1] + 1,
|
|
1340
|
+
diagonal + (a[i - 1] === b[j - 1] ? 0 : 1)
|
|
1341
|
+
);
|
|
1342
|
+
diagonal = temp;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
return prev[b.length];
|
|
1346
|
+
}
|
|
1347
|
+
function closestField(name, allowed) {
|
|
1348
|
+
const lower = name.toLowerCase();
|
|
1349
|
+
let best = "";
|
|
1350
|
+
let bestScore = Infinity;
|
|
1351
|
+
allowed.forEach((field) => {
|
|
1352
|
+
const score = editDistance(lower, field.toLowerCase());
|
|
1353
|
+
if (score < bestScore) {
|
|
1354
|
+
bestScore = score;
|
|
1355
|
+
best = field;
|
|
1356
|
+
}
|
|
1357
|
+
});
|
|
1358
|
+
return bestScore <= Math.max(3, Math.ceil(name.length / 2)) ? best : "";
|
|
1359
|
+
}
|
|
1360
|
+
function collectFromExpression(source, into) {
|
|
1361
|
+
USER_FIELD_RE.lastIndex = 0;
|
|
1362
|
+
let matched = USER_FIELD_RE.exec(source);
|
|
1363
|
+
while (matched) {
|
|
1364
|
+
into.push(matched[1]);
|
|
1365
|
+
matched = USER_FIELD_RE.exec(source);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
function collectFromValue(value, key, into) {
|
|
1369
|
+
if (RAW_EXPRESSION_KEYS.indexOf(key) > -1) {
|
|
1370
|
+
collectFromExpression(value, into);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
INTERPOLATION_RE2.lastIndex = 0;
|
|
1374
|
+
let matched = INTERPOLATION_RE2.exec(value);
|
|
1375
|
+
while (matched) {
|
|
1376
|
+
collectFromExpression(matched[1], into);
|
|
1377
|
+
matched = INTERPOLATION_RE2.exec(value);
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
function validateUserFields(dsl, allowed) {
|
|
1381
|
+
const issues = [];
|
|
1382
|
+
const seen = [];
|
|
1383
|
+
const walk = (node, path, key) => {
|
|
1384
|
+
if (typeof node === "string") {
|
|
1385
|
+
const names = [];
|
|
1386
|
+
collectFromValue(node, key, names);
|
|
1387
|
+
names.forEach((name) => {
|
|
1388
|
+
if (allowed.indexOf(name) > -1) return;
|
|
1389
|
+
const guess = closestField(name, allowed);
|
|
1390
|
+
issues.push({
|
|
1391
|
+
path,
|
|
1392
|
+
message: `\u7528\u6237\u4FE1\u606F\u91CC\u6CA1\u6709 "user.${name}" \u8FD9\u4E2A\u5B57\u6BB5` + (guess ? `\uFF0C\u662F\u4E0D\u662F\u60F3\u5199 "user.${guess}"\uFF1F` : "") + `\u3002\u53EF\u7528\u5B57\u6BB5\uFF1A${allowed.join(" / ")}`
|
|
1393
|
+
});
|
|
1394
|
+
});
|
|
1395
|
+
return;
|
|
1396
|
+
}
|
|
1397
|
+
if (!node || typeof node !== "object") return;
|
|
1398
|
+
if (seen.indexOf(node) > -1) return;
|
|
1399
|
+
seen.push(node);
|
|
1400
|
+
if (Array.isArray(node)) {
|
|
1401
|
+
node.forEach((item, index) => walk(item, `${path}[${index}]`, key));
|
|
1402
|
+
return;
|
|
1403
|
+
}
|
|
1404
|
+
Object.keys(node).forEach((childKey) => {
|
|
1405
|
+
const childPath = path ? `${path}.${childKey}` : childKey;
|
|
1406
|
+
walk(node[childKey], childPath, childKey);
|
|
1407
|
+
});
|
|
1408
|
+
};
|
|
1409
|
+
walk(dsl, "", "");
|
|
1410
|
+
return issues;
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1269
1413
|
// src/validate.ts
|
|
1270
1414
|
var LENGTH_STYLE_KEYS = [
|
|
1271
1415
|
"width",
|
|
@@ -1317,7 +1461,7 @@ var CLOSE_POSITIONS = [
|
|
|
1317
1461
|
"bottom-center"
|
|
1318
1462
|
];
|
|
1319
1463
|
var CONTAINER_TYPES = ["box", "flex"];
|
|
1320
|
-
function validate(dsl) {
|
|
1464
|
+
function validate(dsl, options) {
|
|
1321
1465
|
const errors = [];
|
|
1322
1466
|
const warnings = [];
|
|
1323
1467
|
const add = (path, message) => errors.push({ path, message });
|
|
@@ -1347,6 +1491,11 @@ function validate(dsl) {
|
|
|
1347
1491
|
viewNames.forEach((name) => {
|
|
1348
1492
|
validateView(views[name], isMulti ? `views.${name}` : "", ctx);
|
|
1349
1493
|
});
|
|
1494
|
+
if (options && options.userFields) {
|
|
1495
|
+
validateUserFields(doc, options.userFields).forEach(
|
|
1496
|
+
(issue) => add(issue.path, issue.message)
|
|
1497
|
+
);
|
|
1498
|
+
}
|
|
1350
1499
|
return { valid: errors.length === 0, errors, warnings };
|
|
1351
1500
|
}
|
|
1352
1501
|
function validateMultiView(dsl, add) {
|
|
@@ -1379,6 +1528,7 @@ function validateView(view, prefix, ctx) {
|
|
|
1379
1528
|
if (["popup", "banner", "notice"].indexOf(view.type) === -1) {
|
|
1380
1529
|
add(at("type"), "type \u5FC5\u987B\u662F popup / banner / notice \u4E4B\u4E00");
|
|
1381
1530
|
}
|
|
1531
|
+
ctx.viewType = view.type;
|
|
1382
1532
|
validateStage(
|
|
1383
1533
|
view.stage,
|
|
1384
1534
|
(path, message) => add(prefix ? `${prefix}.${path}` : path, message),
|
|
@@ -1402,6 +1552,16 @@ function checkLength(value) {
|
|
|
1402
1552
|
if (isLength(value)) return true;
|
|
1403
1553
|
return value.trim().split(/\s+/).every(isLength);
|
|
1404
1554
|
}
|
|
1555
|
+
var BANNER_MIN_ASPECT_RATIO = 12;
|
|
1556
|
+
function parseAspectRatio(value) {
|
|
1557
|
+
if (typeof value === "number") return isFinite(value) && value > 0 ? value : null;
|
|
1558
|
+
if (typeof value !== "string" || value.indexOf("{{") > -1) return null;
|
|
1559
|
+
const parts = value.split("/");
|
|
1560
|
+
const width = Number(parts[0]);
|
|
1561
|
+
const height = parts.length > 1 ? Number(parts[1]) : 1;
|
|
1562
|
+
if (!isFinite(width) || !isFinite(height) || width <= 0 || height <= 0) return null;
|
|
1563
|
+
return width / height;
|
|
1564
|
+
}
|
|
1405
1565
|
function checkAspectRatio(value) {
|
|
1406
1566
|
if (typeof value === "string" && value.indexOf("{{") > -1) return true;
|
|
1407
1567
|
if (typeof value === "number") return isFinite(value) && value > 0;
|
|
@@ -1569,11 +1729,21 @@ function validateNode(node, path, layout, ctx) {
|
|
|
1569
1729
|
`"${node.style[key]}" \u4E0D\u662F\u5408\u6CD5\u957F\u5EA6\uFF0C\u652F\u6301\u6570\u5B57(px) / '100%' / 'auto' \u7B49`
|
|
1570
1730
|
);
|
|
1571
1731
|
}
|
|
1572
|
-
if (key === "aspectRatio"
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1732
|
+
if (key === "aspectRatio") {
|
|
1733
|
+
if (!checkAspectRatio(node.style[key])) {
|
|
1734
|
+
add(
|
|
1735
|
+
`${path}.style.aspectRatio`,
|
|
1736
|
+
"aspectRatio \u8981\u5199\u6210 '750 / 200' \u6216 1.5 \u8FD9\u6837\u7684\u5BBD\u9AD8\u6BD4"
|
|
1737
|
+
);
|
|
1738
|
+
} else if (ctx.viewType === "banner") {
|
|
1739
|
+
const ratio = parseAspectRatio(node.style[key]);
|
|
1740
|
+
if (ratio !== null && ratio < BANNER_MIN_ASPECT_RATIO) {
|
|
1741
|
+
add(
|
|
1742
|
+
`${path}.style.aspectRatio`,
|
|
1743
|
+
`banner \u7684\u5BBD\u9AD8\u6BD4\u4E0D\u80FD\u5C0F\u4E8E ${BANNER_MIN_ASPECT_RATIO}:1\uFF08\u5F53\u524D\u7EA6 ${ratio.toFixed(1)}:1\uFF09\u3002banner \u5751\u4F4D\u5E38\u9A7B\u9875\u9762\u9876\u90E8\uFF0C\u6BD4\u4F8B\u8D8A\u65B9\u5728\u5BBD\u5C4F\u4E0A\u8D8A\u9AD8 \u2014\u2014 1800px \u5BBD\u7684\u5751\u4F4D\u91CC\uFF0C${ratio.toFixed(1)}:1 \u4F1A\u7B97\u51FA ${Math.round(1800 / ratio)}px \u9AD8\uFF0C\u5403\u6389\u9996\u5C4F\u4E00\u5927\u5757`
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1577
1747
|
}
|
|
1578
1748
|
});
|
|
1579
1749
|
}
|
|
@@ -1706,11 +1876,14 @@ function formatIssues(issues) {
|
|
|
1706
1876
|
|
|
1707
1877
|
exports.ACTION_TYPES = ACTION_TYPES;
|
|
1708
1878
|
exports.ALLOWED_STYLE_KEYS = ALLOWED_STYLE_KEYS;
|
|
1879
|
+
exports.BANNER_MIN_ASPECT_RATIO = BANNER_MIN_ASPECT_RATIO;
|
|
1709
1880
|
exports.CLOSE_POSITIONS = CLOSE_POSITIONS;
|
|
1881
|
+
exports.DEFAULT_USER_FIELDS = DEFAULT_USER_FIELDS;
|
|
1710
1882
|
exports.DSL_VERSION = DSL_VERSION;
|
|
1711
1883
|
exports.NODE_TYPES = NODE_TYPES;
|
|
1712
1884
|
exports.SINGLE_VIEW_NAME = SINGLE_VIEW_NAME;
|
|
1713
1885
|
exports.TRACK_EVENT_PATTERN = TRACK_EVENT_PATTERN;
|
|
1886
|
+
exports.USER_FIELD_LABELS = USER_FIELD_LABELS;
|
|
1714
1887
|
exports.check = check;
|
|
1715
1888
|
exports.computeParts = computeParts;
|
|
1716
1889
|
exports.createRuntime = createRuntime;
|
|
@@ -1728,3 +1901,4 @@ exports.safeUrl = safeUrl;
|
|
|
1728
1901
|
exports.toCssStyle = toCssStyle;
|
|
1729
1902
|
exports.toLength = toLength;
|
|
1730
1903
|
exports.validate = validate;
|
|
1904
|
+
exports.validateUserFields = validateUserFields;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-
|
|
2
|
-
export { q as DslAction, h as DslActionBase, o as DslCallAction, j as DslCloseAction, k as DslCloseAllAction, t as DslCloseButton, s as DslClosePosition, w as DslDerived, A as DslHandler, f as DslLength, i as DslNavigateAction, r as DslNode, e as DslNodeType, l as DslOpenAction, g as DslRect, p as DslSequenceAction, m as DslSetStateAction, z as DslSource, v as DslSourceRef, u as DslStage, n as DslTrackAction, d as DslViewType, x as RenderElement, y as RenderLayer,
|
|
1
|
+
import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-Bj0xyWnx.cjs';
|
|
2
|
+
export { q as DslAction, h as DslActionBase, o as DslCallAction, j as DslCloseAction, k as DslCloseAllAction, t as DslCloseButton, s as DslClosePosition, w as DslDerived, A as DslHandler, E as DslInteractionEvent, B as DslInteractionTrigger, f as DslLength, i as DslNavigateAction, r as DslNode, e as DslNodeType, l as DslOpenAction, g as DslRect, p as DslSequenceAction, m as DslSetStateAction, z as DslSource, v as DslSourceRef, u as DslStage, n as DslTrackAction, d as DslViewType, x as RenderElement, y as RenderLayer, H as RuntimeEmit, G as RuntimeEventName, F as RuntimeEvents } from './types-Bj0xyWnx.cjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* 运行时。
|
|
@@ -46,6 +46,17 @@ interface Issue {
|
|
|
46
46
|
path: string;
|
|
47
47
|
message: string;
|
|
48
48
|
}
|
|
49
|
+
/** 校验选项,全部可选;不传时行为与旧版完全一致 */
|
|
50
|
+
interface ValidateOptions {
|
|
51
|
+
/**
|
|
52
|
+
* 允许出现在 `{{ user.xxx }}` 里的字段白名单。
|
|
53
|
+
*
|
|
54
|
+
* **不传就不校验**(宿主注入什么字段由宿主决定,核心包不该替它们写死)。
|
|
55
|
+
* 传了则所有插值位置里引用的 `user` 字段都必须在表内,否则报 error。
|
|
56
|
+
* 默认可用的一套见 `DEFAULT_USER_FIELDS`。
|
|
57
|
+
*/
|
|
58
|
+
userFields?: string[];
|
|
59
|
+
}
|
|
49
60
|
interface ValidateResult {
|
|
50
61
|
valid: boolean;
|
|
51
62
|
errors: Issue[];
|
|
@@ -62,10 +73,70 @@ declare const ACTION_TYPES: string[];
|
|
|
62
73
|
*/
|
|
63
74
|
declare const TRACK_EVENT_PATTERN: RegExp;
|
|
64
75
|
declare const CLOSE_POSITIONS: string[];
|
|
65
|
-
|
|
76
|
+
/**
|
|
77
|
+
* 校验一份 DSL。
|
|
78
|
+
*
|
|
79
|
+
* @param dsl 待校验的配置
|
|
80
|
+
* @param options 可选项;不传时与旧版行为完全一致
|
|
81
|
+
*/
|
|
82
|
+
declare function validate(dsl: Dsl | unknown, options?: ValidateOptions): ValidateResult;
|
|
83
|
+
/** 宽高比:'750 / 200'、'16/9' 或纯数字 */
|
|
84
|
+
/**
|
|
85
|
+
* banner 允许的最小宽高比(宽 ÷ 高)。
|
|
86
|
+
*
|
|
87
|
+
* banner 坑位常驻页面顶部、不跟随内容滚动,比例越方在宽屏上越高:
|
|
88
|
+
* 同样 1800px 宽的坑位,17:1 算出来是 105px,4:1(1200×300 那种大图)直接 451px,
|
|
89
|
+
* 首屏一眼看过去全是广告。所以比例必须够扁——**这是坑位的设计尺寸,不是配置的自由度**。
|
|
90
|
+
*
|
|
91
|
+
* 约束比例而不是约束固定高度,是因为整图 banner 必须靠 aspectRatio 维持等比:
|
|
92
|
+
* 写死高度的话坑位一宽,图就被横向拉变形了。
|
|
93
|
+
*/
|
|
94
|
+
declare const BANNER_MIN_ASPECT_RATIO = 12;
|
|
66
95
|
/** 把校验结果拼成可以直接展示的文本 */
|
|
67
96
|
declare function formatIssues(issues: Issue[]): string;
|
|
68
97
|
|
|
98
|
+
/**
|
|
99
|
+
* `{{ user.xxx }}` 字段白名单校验
|
|
100
|
+
*
|
|
101
|
+
* `user` 是宿主注入的登录用户信息对象,字段集由宿主决定、前端 DSL 只能读。
|
|
102
|
+
* 运营写错字段名(`user.expireDays` 而不是 `user.remainDays`)时,
|
|
103
|
+
* 运行时只会求值为空——页面上就是一处空白 / 倒计时归零,
|
|
104
|
+
* 投到线上才发现。所以在保存前就按白名单拦一道。
|
|
105
|
+
*
|
|
106
|
+
* 白名单本身是**可选**的:`validate(dsl)` 不传就完全不查(向后兼容),
|
|
107
|
+
* 调用方(后台调试台 / 后端保存)自己决定用哪套字段。
|
|
108
|
+
*/
|
|
109
|
+
/** 一条校验问题,结构与 `validate.ts` 的 `Issue` 一致(避免两个文件循环依赖类型) */
|
|
110
|
+
interface FieldIssue {
|
|
111
|
+
/** 出问题的位置,如 `views.main.nodes[0].content` */
|
|
112
|
+
path: string;
|
|
113
|
+
/** 给运营看的中文说明 */
|
|
114
|
+
message: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 默认可用的 `user` 字段白名单。
|
|
118
|
+
*
|
|
119
|
+
* 取自各平台统一的登录用户信息模型(`UserInfo`),这套字段名是全平台通用契约。
|
|
120
|
+
* 刻意排除两个字段:
|
|
121
|
+
* - `sourceData`:后端原始响应整包,结构随平台漂移,不对运营开放
|
|
122
|
+
* - `mcToken`:令牌,不该出现在运营配置里,更不该渲染到页面上
|
|
123
|
+
*/
|
|
124
|
+
declare const DEFAULT_USER_FIELDS: string[];
|
|
125
|
+
/** 白名单字段的中文含义,文档和编辑器提示共用一份,避免说明各写各的 */
|
|
126
|
+
declare const USER_FIELD_LABELS: Record<string, string>;
|
|
127
|
+
/**
|
|
128
|
+
* 深度遍历整份 DSL,校验所有 `{{ user.xxx }}` 引用的字段是否在白名单内。
|
|
129
|
+
*
|
|
130
|
+
* 遍历的是**全部字符串值**而不是某几个已知字段(content / to / src …),
|
|
131
|
+
* 因为能写插值的位置太多(样式值、动作 params、stage 尺寸…),
|
|
132
|
+
* 正向列举必漏;反过来「凡是字符串就查一遍 `{{ }}`」不会漏。
|
|
133
|
+
*
|
|
134
|
+
* @param dsl 待校验的 DSL(任意结构,坏数据交给主校验流程报错,这里只负责不崩)
|
|
135
|
+
* @param allowed 允许的字段名列表
|
|
136
|
+
* @returns 每个非法引用一条 error 级问题
|
|
137
|
+
*/
|
|
138
|
+
declare function validateUserFields(dsl: unknown, allowed: string[]): FieldIssue[];
|
|
139
|
+
|
|
69
140
|
/**
|
|
70
141
|
* 视图归一化。
|
|
71
142
|
*
|
|
@@ -177,4 +248,4 @@ declare function computeParts(endTime: number, now?: number): CountdownParts;
|
|
|
177
248
|
/** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
|
|
178
249
|
declare function formatParts(parts: CountdownParts, format?: string): string;
|
|
179
250
|
|
|
180
|
-
export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, CssStyle, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, type Issue, NODE_TYPES, type NormalizedViews, RenderTree, RuntimeOptions, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate };
|
|
251
|
+
export { ACTION_TYPES, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, CssStyle, DEFAULT_USER_FIELDS, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, type Issue, NODE_TYPES, type NormalizedViews, RenderTree, RuntimeOptions, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, type ValidateOptions, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate, validateUserFields };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-
|
|
2
|
-
export { q as DslAction, h as DslActionBase, o as DslCallAction, j as DslCloseAction, k as DslCloseAllAction, t as DslCloseButton, s as DslClosePosition, w as DslDerived, A as DslHandler, f as DslLength, i as DslNavigateAction, r as DslNode, e as DslNodeType, l as DslOpenAction, g as DslRect, p as DslSequenceAction, m as DslSetStateAction, z as DslSource, v as DslSourceRef, u as DslStage, n as DslTrackAction, d as DslViewType, x as RenderElement, y as RenderLayer,
|
|
1
|
+
import { D as Dsl, R as RuntimeOptions, a as RenderTree, b as DslView, c as DslStyle, C as CssStyle } from './types-Bj0xyWnx.js';
|
|
2
|
+
export { q as DslAction, h as DslActionBase, o as DslCallAction, j as DslCloseAction, k as DslCloseAllAction, t as DslCloseButton, s as DslClosePosition, w as DslDerived, A as DslHandler, E as DslInteractionEvent, B as DslInteractionTrigger, f as DslLength, i as DslNavigateAction, r as DslNode, e as DslNodeType, l as DslOpenAction, g as DslRect, p as DslSequenceAction, m as DslSetStateAction, z as DslSource, v as DslSourceRef, u as DslStage, n as DslTrackAction, d as DslViewType, x as RenderElement, y as RenderLayer, H as RuntimeEmit, G as RuntimeEventName, F as RuntimeEvents } from './types-Bj0xyWnx.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* 运行时。
|
|
@@ -46,6 +46,17 @@ interface Issue {
|
|
|
46
46
|
path: string;
|
|
47
47
|
message: string;
|
|
48
48
|
}
|
|
49
|
+
/** 校验选项,全部可选;不传时行为与旧版完全一致 */
|
|
50
|
+
interface ValidateOptions {
|
|
51
|
+
/**
|
|
52
|
+
* 允许出现在 `{{ user.xxx }}` 里的字段白名单。
|
|
53
|
+
*
|
|
54
|
+
* **不传就不校验**(宿主注入什么字段由宿主决定,核心包不该替它们写死)。
|
|
55
|
+
* 传了则所有插值位置里引用的 `user` 字段都必须在表内,否则报 error。
|
|
56
|
+
* 默认可用的一套见 `DEFAULT_USER_FIELDS`。
|
|
57
|
+
*/
|
|
58
|
+
userFields?: string[];
|
|
59
|
+
}
|
|
49
60
|
interface ValidateResult {
|
|
50
61
|
valid: boolean;
|
|
51
62
|
errors: Issue[];
|
|
@@ -62,10 +73,70 @@ declare const ACTION_TYPES: string[];
|
|
|
62
73
|
*/
|
|
63
74
|
declare const TRACK_EVENT_PATTERN: RegExp;
|
|
64
75
|
declare const CLOSE_POSITIONS: string[];
|
|
65
|
-
|
|
76
|
+
/**
|
|
77
|
+
* 校验一份 DSL。
|
|
78
|
+
*
|
|
79
|
+
* @param dsl 待校验的配置
|
|
80
|
+
* @param options 可选项;不传时与旧版行为完全一致
|
|
81
|
+
*/
|
|
82
|
+
declare function validate(dsl: Dsl | unknown, options?: ValidateOptions): ValidateResult;
|
|
83
|
+
/** 宽高比:'750 / 200'、'16/9' 或纯数字 */
|
|
84
|
+
/**
|
|
85
|
+
* banner 允许的最小宽高比(宽 ÷ 高)。
|
|
86
|
+
*
|
|
87
|
+
* banner 坑位常驻页面顶部、不跟随内容滚动,比例越方在宽屏上越高:
|
|
88
|
+
* 同样 1800px 宽的坑位,17:1 算出来是 105px,4:1(1200×300 那种大图)直接 451px,
|
|
89
|
+
* 首屏一眼看过去全是广告。所以比例必须够扁——**这是坑位的设计尺寸,不是配置的自由度**。
|
|
90
|
+
*
|
|
91
|
+
* 约束比例而不是约束固定高度,是因为整图 banner 必须靠 aspectRatio 维持等比:
|
|
92
|
+
* 写死高度的话坑位一宽,图就被横向拉变形了。
|
|
93
|
+
*/
|
|
94
|
+
declare const BANNER_MIN_ASPECT_RATIO = 12;
|
|
66
95
|
/** 把校验结果拼成可以直接展示的文本 */
|
|
67
96
|
declare function formatIssues(issues: Issue[]): string;
|
|
68
97
|
|
|
98
|
+
/**
|
|
99
|
+
* `{{ user.xxx }}` 字段白名单校验
|
|
100
|
+
*
|
|
101
|
+
* `user` 是宿主注入的登录用户信息对象,字段集由宿主决定、前端 DSL 只能读。
|
|
102
|
+
* 运营写错字段名(`user.expireDays` 而不是 `user.remainDays`)时,
|
|
103
|
+
* 运行时只会求值为空——页面上就是一处空白 / 倒计时归零,
|
|
104
|
+
* 投到线上才发现。所以在保存前就按白名单拦一道。
|
|
105
|
+
*
|
|
106
|
+
* 白名单本身是**可选**的:`validate(dsl)` 不传就完全不查(向后兼容),
|
|
107
|
+
* 调用方(后台调试台 / 后端保存)自己决定用哪套字段。
|
|
108
|
+
*/
|
|
109
|
+
/** 一条校验问题,结构与 `validate.ts` 的 `Issue` 一致(避免两个文件循环依赖类型) */
|
|
110
|
+
interface FieldIssue {
|
|
111
|
+
/** 出问题的位置,如 `views.main.nodes[0].content` */
|
|
112
|
+
path: string;
|
|
113
|
+
/** 给运营看的中文说明 */
|
|
114
|
+
message: string;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* 默认可用的 `user` 字段白名单。
|
|
118
|
+
*
|
|
119
|
+
* 取自各平台统一的登录用户信息模型(`UserInfo`),这套字段名是全平台通用契约。
|
|
120
|
+
* 刻意排除两个字段:
|
|
121
|
+
* - `sourceData`:后端原始响应整包,结构随平台漂移,不对运营开放
|
|
122
|
+
* - `mcToken`:令牌,不该出现在运营配置里,更不该渲染到页面上
|
|
123
|
+
*/
|
|
124
|
+
declare const DEFAULT_USER_FIELDS: string[];
|
|
125
|
+
/** 白名单字段的中文含义,文档和编辑器提示共用一份,避免说明各写各的 */
|
|
126
|
+
declare const USER_FIELD_LABELS: Record<string, string>;
|
|
127
|
+
/**
|
|
128
|
+
* 深度遍历整份 DSL,校验所有 `{{ user.xxx }}` 引用的字段是否在白名单内。
|
|
129
|
+
*
|
|
130
|
+
* 遍历的是**全部字符串值**而不是某几个已知字段(content / to / src …),
|
|
131
|
+
* 因为能写插值的位置太多(样式值、动作 params、stage 尺寸…),
|
|
132
|
+
* 正向列举必漏;反过来「凡是字符串就查一遍 `{{ }}`」不会漏。
|
|
133
|
+
*
|
|
134
|
+
* @param dsl 待校验的 DSL(任意结构,坏数据交给主校验流程报错,这里只负责不崩)
|
|
135
|
+
* @param allowed 允许的字段名列表
|
|
136
|
+
* @returns 每个非法引用一条 error 级问题
|
|
137
|
+
*/
|
|
138
|
+
declare function validateUserFields(dsl: unknown, allowed: string[]): FieldIssue[];
|
|
139
|
+
|
|
69
140
|
/**
|
|
70
141
|
* 视图归一化。
|
|
71
142
|
*
|
|
@@ -177,4 +248,4 @@ declare function computeParts(endTime: number, now?: number): CountdownParts;
|
|
|
177
248
|
/** 不给 children 时退化成一行文本,占位符 {d} {h} {hAll} {m} {s} {cs} */
|
|
178
249
|
declare function formatParts(parts: CountdownParts, format?: string): string;
|
|
179
250
|
|
|
180
|
-
export { ACTION_TYPES, ALLOWED_STYLE_KEYS, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, CssStyle, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, type Issue, NODE_TYPES, type NormalizedViews, RenderTree, RuntimeOptions, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate };
|
|
251
|
+
export { ACTION_TYPES, ALLOWED_STYLE_KEYS, BANNER_MIN_ASPECT_RATIO, CLOSE_POSITIONS, type CountdownParts, type CountdownPrecision, CssStyle, DEFAULT_USER_FIELDS, DSL_VERSION, Dsl, type DslRuntime, DslStyle, DslView, type Issue, NODE_TYPES, type NormalizedViews, RenderTree, RuntimeOptions, SINGLE_VIEW_NAME, TRACK_EVENT_PATTERN, USER_FIELD_LABELS, type ValidateOptions, type ValidateResult, check, computeParts, createRuntime, evaluate, formatIssues, formatParts, interpolate, interpolateDeep, isLength, isValidEndTime, normalizeViews, parseEndTime, safeImageUrl, safeUrl, toCssStyle, toLength, validate, validateUserFields };
|