@nocobase/flow-engine 2.1.0-alpha.1 → 2.1.0-alpha.10
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/LICENSE +201 -661
- package/README.md +79 -10
- package/lib/BlockScopedFlowEngine.js +0 -1
- package/lib/FlowDefinition.d.ts +2 -0
- package/lib/JSRunner.d.ts +15 -0
- package/lib/JSRunner.js +82 -7
- package/lib/ViewScopedFlowEngine.js +8 -1
- package/lib/acl/Acl.js +13 -3
- package/lib/components/FlowContextSelector.js +155 -10
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +1 -0
- package/lib/components/dnd/gridDragPlanner.js +59 -3
- package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +21 -3
- package/lib/components/subModel/AddSubModelButton.js +16 -1
- package/lib/components/subModel/utils.js +2 -2
- package/lib/components/variables/VariableInput.js +9 -4
- package/lib/components/variables/VariableTag.js +46 -39
- package/lib/components/variables/utils.d.ts +7 -0
- package/lib/components/variables/utils.js +42 -2
- package/lib/data-source/index.d.ts +7 -27
- package/lib/data-source/index.js +84 -51
- package/lib/executor/FlowExecutor.d.ts +2 -1
- package/lib/executor/FlowExecutor.js +190 -26
- package/lib/flowContext.d.ts +230 -7
- package/lib/flowContext.js +2270 -148
- package/lib/flowEngine.d.ts +160 -1
- package/lib/flowEngine.js +383 -26
- package/lib/flowI18n.js +6 -4
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +51 -17
- package/lib/index.d.ts +7 -1
- package/lib/index.js +21 -0
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +9 -2
- package/lib/locale/index.d.ts +14 -0
- package/lib/locale/zh-CN.json +8 -1
- package/lib/models/CollectionFieldModel.d.ts +1 -0
- package/lib/models/CollectionFieldModel.js +3 -2
- package/lib/models/flowModel.d.ts +7 -0
- package/lib/models/flowModel.js +83 -8
- package/lib/provider.js +7 -6
- package/lib/resources/baseRecordResource.d.ts +5 -0
- package/lib/resources/baseRecordResource.js +24 -0
- package/lib/resources/multiRecordResource.d.ts +1 -0
- package/lib/resources/multiRecordResource.js +11 -4
- package/lib/resources/singleRecordResource.js +2 -0
- package/lib/resources/sqlResource.d.ts +4 -3
- package/lib/resources/sqlResource.js +8 -3
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/base.js +706 -41
- package/lib/runjs-context/contributions.d.ts +33 -0
- package/lib/runjs-context/contributions.js +88 -0
- package/lib/runjs-context/helpers.js +12 -1
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +22 -9
- package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
- package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
- package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
- package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
- package/lib/runjs-context/snippets/index.d.ts +11 -1
- package/lib/runjs-context/snippets/index.js +61 -40
- package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
- package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
- package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
- package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
- package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
- package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
- package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
- package/lib/runjsLibs.d.ts +28 -0
- package/lib/runjsLibs.js +532 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +7 -1
- package/lib/scheduler/ModelOperationScheduler.js +28 -23
- package/lib/types.d.ts +63 -1
- package/lib/utils/associationObjectVariable.d.ts +2 -2
- package/lib/utils/createCollectionContextMeta.js +1 -0
- package/lib/utils/createEphemeralContext.js +2 -2
- package/lib/utils/dateVariable.d.ts +16 -0
- package/lib/utils/dateVariable.js +380 -0
- package/lib/utils/exceptions.d.ts +7 -0
- package/lib/utils/exceptions.js +10 -0
- package/lib/utils/index.d.ts +8 -3
- package/lib/utils/index.js +49 -0
- package/lib/utils/params-resolvers.js +16 -9
- package/lib/utils/parsePathnameToViewParams.js +1 -1
- package/lib/utils/resolveModuleUrl.d.ts +58 -0
- package/lib/utils/resolveModuleUrl.js +65 -0
- package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
- package/lib/utils/resolveRunJSObjectValues.js +61 -0
- package/lib/utils/runjsModuleLoader.d.ts +58 -0
- package/lib/utils/runjsModuleLoader.js +422 -0
- package/lib/utils/runjsTemplateCompat.d.ts +35 -0
- package/lib/utils/runjsTemplateCompat.js +743 -0
- package/lib/utils/runjsValue.d.ts +29 -0
- package/lib/utils/runjsValue.js +275 -0
- package/lib/utils/safeGlobals.d.ts +18 -8
- package/lib/utils/safeGlobals.js +164 -17
- package/lib/utils/schema-utils.d.ts +17 -1
- package/lib/utils/schema-utils.js +80 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/createViewMeta.d.ts +0 -7
- package/lib/views/createViewMeta.js +19 -70
- package/lib/views/index.d.ts +1 -2
- package/lib/views/index.js +4 -3
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +28 -6
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +27 -5
- package/lib/views/usePage.d.ts +6 -1
- package/lib/views/usePage.js +53 -9
- package/lib/views/usePopover.js +4 -1
- package/lib/views/viewEvents.d.ts +17 -0
- package/lib/views/viewEvents.js +90 -0
- package/package.json +5 -5
- package/src/BlockScopedFlowEngine.ts +2 -5
- package/src/JSRunner.ts +111 -5
- package/src/ViewScopedFlowEngine.ts +8 -0
- package/src/__tests__/JSRunner.test.ts +91 -1
- package/src/__tests__/createViewMeta.popup.test.ts +62 -1
- package/src/__tests__/flowContext.test.ts +693 -1
- package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
- package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
- package/src/__tests__/flowRuntimeContext.test.ts +2 -1
- package/src/__tests__/flowSettings.open.test.tsx +123 -19
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/provider.test.tsx +0 -5
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +23 -7
- package/src/__tests__/runjsContextImplementations.test.ts +34 -3
- package/src/__tests__/runjsContextRuntime.test.ts +3 -3
- package/src/__tests__/runjsContributions.test.ts +89 -0
- package/src/__tests__/runjsExternalLibs.test.ts +242 -0
- package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
- package/src/__tests__/runjsLocales.test.ts +4 -1
- package/src/__tests__/runjsPreprocessDefault.test.ts +72 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
- package/src/__tests__/runjsSnippets.test.ts +40 -3
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/acl/Acl.tsx +3 -3
- package/src/components/FlowContextSelector.tsx +208 -12
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +3 -3
- package/src/components/__tests__/gridDragPlanner.test.ts +229 -1
- package/src/components/dnd/gridDragPlanner.ts +68 -2
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
- package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +31 -4
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
- package/src/components/subModel/AddSubModelButton.tsx +17 -1
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +142 -32
- package/src/components/subModel/utils.ts +1 -1
- package/src/components/variables/VariableInput.tsx +12 -4
- package/src/components/variables/VariableTag.tsx +54 -45
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
- package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
- package/src/components/variables/__tests__/utils.test.ts +81 -3
- package/src/components/variables/utils.ts +67 -6
- package/src/data-source/index.ts +88 -110
- package/src/executor/FlowExecutor.ts +230 -28
- package/src/executor/__tests__/flowExecutor.test.ts +123 -0
- package/src/flowContext.ts +2989 -212
- package/src/flowEngine.ts +427 -22
- package/src/flowI18n.ts +7 -5
- package/src/flowSettings.ts +58 -18
- package/src/index.ts +14 -1
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +9 -2
- package/src/locale/zh-CN.json +8 -1
- package/src/models/CollectionFieldModel.tsx +3 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +768 -0
- package/src/models/__tests__/flowModel.clone.test.ts +416 -0
- package/src/models/__tests__/flowModel.test.ts +20 -4
- package/src/models/flowModel.tsx +112 -7
- package/src/provider.tsx +9 -7
- package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
- package/src/resources/__tests__/sqlResource.test.ts +60 -0
- package/src/resources/baseRecordResource.ts +31 -0
- package/src/resources/multiRecordResource.ts +11 -4
- package/src/resources/singleRecordResource.ts +3 -0
- package/src/resources/sqlResource.ts +11 -6
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/base.ts +715 -44
- package/src/runjs-context/contributions.ts +88 -0
- package/src/runjs-context/helpers.ts +11 -1
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +24 -9
- package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
- package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
- package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
- package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
- package/src/runjs-context/snippets/index.ts +75 -41
- package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
- package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
- package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
- package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
- package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
- package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
- package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
- package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
- package/src/runjsLibs.ts +622 -0
- package/src/scheduler/ModelOperationScheduler.ts +41 -24
- package/src/types.ts +86 -1
- package/src/utils/__tests__/dateVariable.test.ts +101 -0
- package/src/utils/__tests__/params-resolvers.test.ts +40 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
- package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
- package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
- package/src/utils/__tests__/runjsValue.test.ts +44 -0
- package/src/utils/__tests__/safeGlobals.test.ts +57 -2
- package/src/utils/__tests__/utils.test.ts +157 -0
- package/src/utils/associationObjectVariable.ts +2 -2
- package/src/utils/createCollectionContextMeta.ts +1 -0
- package/src/utils/createEphemeralContext.ts +5 -4
- package/src/utils/dateVariable.ts +397 -0
- package/src/utils/exceptions.ts +11 -0
- package/src/utils/index.ts +38 -3
- package/src/utils/params-resolvers.ts +23 -9
- package/src/utils/parsePathnameToViewParams.ts +2 -2
- package/src/utils/resolveModuleUrl.ts +91 -0
- package/src/utils/resolveRunJSObjectValues.ts +46 -0
- package/src/utils/runjsModuleLoader.ts +553 -0
- package/src/utils/runjsTemplateCompat.ts +828 -0
- package/src/utils/runjsValue.ts +287 -0
- package/src/utils/safeGlobals.ts +188 -17
- package/src/utils/schema-utils.ts +109 -1
- package/src/views/FlowView.tsx +11 -1
- package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +44 -16
- package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
- package/src/views/createViewMeta.ts +22 -75
- package/src/views/index.tsx +1 -2
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +34 -5
- package/src/views/useDrawer.tsx +33 -4
- package/src/views/usePage.tsx +63 -8
- package/src/views/usePopover.tsx +4 -1
- package/src/views/viewEvents.ts +55 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import dayjs from 'dayjs';
|
|
11
|
+
|
|
12
|
+
const CTX_DATE_REGEX = /^\{\{\s*ctx\.date(?:\.(.+?))?\s*\}\}$/;
|
|
13
|
+
|
|
14
|
+
const PRESET_KEYS = new Set([
|
|
15
|
+
'today',
|
|
16
|
+
'now',
|
|
17
|
+
'yesterday',
|
|
18
|
+
'tomorrow',
|
|
19
|
+
'thisWeek',
|
|
20
|
+
'lastWeek',
|
|
21
|
+
'nextWeek',
|
|
22
|
+
'thisMonth',
|
|
23
|
+
'lastMonth',
|
|
24
|
+
'nextMonth',
|
|
25
|
+
'thisQuarter',
|
|
26
|
+
'lastQuarter',
|
|
27
|
+
'nextQuarter',
|
|
28
|
+
'thisYear',
|
|
29
|
+
'lastYear',
|
|
30
|
+
'nextYear',
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const RELATIVE_DIRECTIONS = new Set(['next', 'past']);
|
|
34
|
+
const RELATIVE_UNITS = new Set(['day', 'week', 'month', 'year']);
|
|
35
|
+
|
|
36
|
+
function parseCtxDateSegments(value: string): string[] | null {
|
|
37
|
+
if (typeof value !== 'string') return null;
|
|
38
|
+
const trimmed = value.trim();
|
|
39
|
+
const match = trimmed.match(CTX_DATE_REGEX);
|
|
40
|
+
if (!match) return null;
|
|
41
|
+
const rawPath = String(match[1] || '');
|
|
42
|
+
if (!rawPath) return [];
|
|
43
|
+
return rawPath
|
|
44
|
+
.split('.')
|
|
45
|
+
.map((seg) => seg.trim())
|
|
46
|
+
.filter(Boolean);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function isCtxDatePathPrefix(pathSegments: string[]): boolean {
|
|
50
|
+
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
51
|
+
if (segments[0] !== 'date') return false;
|
|
52
|
+
if (segments.length === 1) return true;
|
|
53
|
+
|
|
54
|
+
if (segments[1] === 'preset') {
|
|
55
|
+
if (segments.length === 2) return true;
|
|
56
|
+
return segments.length === 3 && PRESET_KEYS.has(segments[2]);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (segments[1] === 'relative') {
|
|
60
|
+
if (segments.length === 2) return true;
|
|
61
|
+
if (segments.length === 3) return RELATIVE_DIRECTIONS.has(segments[2]);
|
|
62
|
+
if (segments.length === 4) {
|
|
63
|
+
return RELATIVE_DIRECTIONS.has(segments[2]) && RELATIVE_UNITS.has(segments[3]);
|
|
64
|
+
}
|
|
65
|
+
if (segments.length === 5) {
|
|
66
|
+
return (
|
|
67
|
+
RELATIVE_DIRECTIONS.has(segments[2]) &&
|
|
68
|
+
RELATIVE_UNITS.has(segments[3]) &&
|
|
69
|
+
typeof parseNumberToken(segments[4]) === 'number'
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (segments[1] === 'exact') {
|
|
76
|
+
if (segments.length === 2) return true;
|
|
77
|
+
|
|
78
|
+
if (segments[2] === 'single') {
|
|
79
|
+
if (segments.length === 3) return true;
|
|
80
|
+
if (segments.length === 4) return segments[3] === 'date';
|
|
81
|
+
if (segments.length === 5) return segments[3] === 'date' && /^v.+/.test(segments[4]);
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (segments[2] === 'range') {
|
|
86
|
+
if (segments.length === 3) return true;
|
|
87
|
+
if (segments.length === 4) return segments[3] === 'date';
|
|
88
|
+
if (segments.length === 5) return segments[3] === 'date' && /^v.+/.test(segments[4]);
|
|
89
|
+
if (segments.length === 6) return segments[3] === 'date' && /^v.+/.test(segments[4]) && /^v.+/.test(segments[5]);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function withDatePrefix(pathSegments: string[]): string[] {
|
|
100
|
+
if (pathSegments[0] === 'date') {
|
|
101
|
+
return pathSegments;
|
|
102
|
+
}
|
|
103
|
+
return ['date', ...pathSegments];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function toCtxDateExpression(pathSegments: string[]): string {
|
|
107
|
+
const segs = withDatePrefix(pathSegments);
|
|
108
|
+
return `{{ ctx.${segs.join('.')} }}`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function utf8ToBase64(input: string): string {
|
|
112
|
+
const globalBuffer = (globalThis as any)?.Buffer;
|
|
113
|
+
if (globalBuffer && typeof globalBuffer.from === 'function') {
|
|
114
|
+
return globalBuffer.from(input, 'utf8').toString('base64');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (typeof btoa === 'function') {
|
|
118
|
+
const encoded = encodeURIComponent(input).replace(/%([0-9A-F]{2})/g, (_m, p1) =>
|
|
119
|
+
String.fromCharCode(parseInt(p1, 16)),
|
|
120
|
+
);
|
|
121
|
+
return btoa(encoded);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
throw new Error('No base64 encoder available');
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function base64ToUtf8(input: string): string {
|
|
128
|
+
const globalBuffer = (globalThis as any)?.Buffer;
|
|
129
|
+
if (globalBuffer && typeof globalBuffer.from === 'function') {
|
|
130
|
+
return globalBuffer.from(input, 'base64').toString('utf8');
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (typeof atob === 'function') {
|
|
134
|
+
const binary = atob(input);
|
|
135
|
+
const encoded = Array.from(binary)
|
|
136
|
+
.map((char) => `%${char.charCodeAt(0).toString(16).padStart(2, '0')}`)
|
|
137
|
+
.join('');
|
|
138
|
+
return decodeURIComponent(encoded);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
throw new Error('No base64 decoder available');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function normalizeToString(value: any): string | undefined {
|
|
145
|
+
if (value == null) return undefined;
|
|
146
|
+
if (typeof value === 'string') {
|
|
147
|
+
const trimmed = value.trim();
|
|
148
|
+
return trimmed.length ? trimmed : undefined;
|
|
149
|
+
}
|
|
150
|
+
if (dayjs.isDayjs(value)) {
|
|
151
|
+
return value.toISOString();
|
|
152
|
+
}
|
|
153
|
+
if (value instanceof Date) {
|
|
154
|
+
return dayjs(value).toISOString();
|
|
155
|
+
}
|
|
156
|
+
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
157
|
+
return String(value);
|
|
158
|
+
}
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parseNumberToken(token: string): number | undefined {
|
|
163
|
+
const match = String(token || '').match(/^n(\d+)$/);
|
|
164
|
+
if (!match) return undefined;
|
|
165
|
+
const parsed = Number(match[1]);
|
|
166
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return undefined;
|
|
167
|
+
return parsed;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function startOfIsoWeek(date: dayjs.Dayjs): dayjs.Dayjs {
|
|
171
|
+
const day = date.day();
|
|
172
|
+
const offset = day === 0 ? -6 : 1 - day;
|
|
173
|
+
return date.add(offset, 'day').startOf('day');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function startOfQuarter(date: dayjs.Dayjs): dayjs.Dayjs {
|
|
177
|
+
const month = date.month();
|
|
178
|
+
const quarterStartMonth = Math.floor(month / 3) * 3;
|
|
179
|
+
return date.month(quarterStartMonth).startOf('month');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function encodeBase64Url(input: string): string {
|
|
183
|
+
const base64 = utf8ToBase64(String(input || ''));
|
|
184
|
+
return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function decodeBase64Url(input: string): string | undefined {
|
|
188
|
+
try {
|
|
189
|
+
const raw = String(input || '').replace(/=+$/g, '');
|
|
190
|
+
if (!raw) return undefined;
|
|
191
|
+
if (!/^[A-Za-z0-9_-]+$/.test(raw)) return undefined;
|
|
192
|
+
|
|
193
|
+
const normalized = raw.replace(/-/g, '+').replace(/_/g, '/');
|
|
194
|
+
const padLength = normalized.length % 4 === 0 ? 0 : 4 - (normalized.length % 4);
|
|
195
|
+
const padded = `${normalized}${'='.repeat(padLength)}`;
|
|
196
|
+
const decoded = base64ToUtf8(padded);
|
|
197
|
+
|
|
198
|
+
if (encodeBase64Url(decoded) !== raw) return undefined;
|
|
199
|
+
return decoded;
|
|
200
|
+
} catch (_error) {
|
|
201
|
+
return undefined;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function isCtxDateExpression(value: unknown): value is string {
|
|
206
|
+
if (typeof value !== 'string') return false;
|
|
207
|
+
return CTX_DATE_REGEX.test(value.trim());
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function isCompleteCtxDatePath(pathSegments: string[]): boolean {
|
|
211
|
+
if (!isCtxDatePathPrefix(pathSegments)) return false;
|
|
212
|
+
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
213
|
+
if (segments[0] !== 'date') return false;
|
|
214
|
+
|
|
215
|
+
if (segments[1] === 'preset') {
|
|
216
|
+
return segments.length === 3 && PRESET_KEYS.has(segments[2]);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (segments[1] === 'relative') {
|
|
220
|
+
if (segments.length !== 5) return false;
|
|
221
|
+
return (
|
|
222
|
+
RELATIVE_DIRECTIONS.has(segments[2]) &&
|
|
223
|
+
RELATIVE_UNITS.has(segments[3]) &&
|
|
224
|
+
typeof parseNumberToken(segments[4]) === 'number'
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (segments[1] === 'exact' && segments[2] === 'single' && segments[3] === 'date') {
|
|
229
|
+
return segments.length === 5 && /^v.+/.test(segments[4]);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (segments[1] === 'exact' && segments[2] === 'range' && segments[3] === 'date') {
|
|
233
|
+
return segments.length === 6 && /^v.+/.test(segments[4]) && /^v.+/.test(segments[5]);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function parseCtxDateExpression(value: unknown): any {
|
|
240
|
+
if (!isCtxDateExpression(value)) return undefined;
|
|
241
|
+
const segments = withDatePrefix(parseCtxDateSegments(value as string) || []);
|
|
242
|
+
|
|
243
|
+
if (segments[1] === 'preset' && segments.length === 3 && PRESET_KEYS.has(segments[2])) {
|
|
244
|
+
return { type: segments[2] };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (
|
|
248
|
+
segments[1] === 'relative' &&
|
|
249
|
+
segments.length === 5 &&
|
|
250
|
+
RELATIVE_DIRECTIONS.has(segments[2]) &&
|
|
251
|
+
RELATIVE_UNITS.has(segments[3])
|
|
252
|
+
) {
|
|
253
|
+
const amount = parseNumberToken(segments[4]);
|
|
254
|
+
if (typeof amount === 'number') {
|
|
255
|
+
return { type: segments[2], unit: segments[3], number: amount };
|
|
256
|
+
}
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (segments[1] === 'exact' && segments[2] === 'single' && segments[3] === 'date' && segments.length === 5) {
|
|
261
|
+
const raw = String(segments[4] || '');
|
|
262
|
+
if (!raw.startsWith('v')) return undefined;
|
|
263
|
+
return decodeBase64Url(raw.slice(1));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (segments[1] === 'exact' && segments[2] === 'range' && segments[3] === 'date' && segments.length === 6) {
|
|
267
|
+
const leftRaw = String(segments[4] || '');
|
|
268
|
+
const rightRaw = String(segments[5] || '');
|
|
269
|
+
if (!leftRaw.startsWith('v') || !rightRaw.startsWith('v')) return undefined;
|
|
270
|
+
const left = decodeBase64Url(leftRaw.slice(1));
|
|
271
|
+
const right = decodeBase64Url(rightRaw.slice(1));
|
|
272
|
+
if (typeof left === 'undefined' || typeof right === 'undefined') return undefined;
|
|
273
|
+
return [left, right];
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return undefined;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function serializeCtxDateValue(value: unknown): string | undefined {
|
|
280
|
+
if (isCtxDateExpression(value)) {
|
|
281
|
+
return String(value).trim();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (value == null || value === '') {
|
|
285
|
+
return undefined;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (Array.isArray(value)) {
|
|
289
|
+
const start = normalizeToString(value[0]);
|
|
290
|
+
const end = normalizeToString(value[1]);
|
|
291
|
+
if (start && end) {
|
|
292
|
+
return toCtxDateExpression([
|
|
293
|
+
'date',
|
|
294
|
+
'exact',
|
|
295
|
+
'range',
|
|
296
|
+
'date',
|
|
297
|
+
`v${encodeBase64Url(start)}`,
|
|
298
|
+
`v${encodeBase64Url(end)}`,
|
|
299
|
+
]);
|
|
300
|
+
}
|
|
301
|
+
if (start) {
|
|
302
|
+
return toCtxDateExpression(['date', 'exact', 'single', 'date', `v${encodeBase64Url(start)}`]);
|
|
303
|
+
}
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (typeof value === 'object' && value) {
|
|
308
|
+
const typed = value as { type?: unknown; unit?: unknown; number?: unknown };
|
|
309
|
+
const type = typeof typed.type === 'string' ? typed.type : '';
|
|
310
|
+
|
|
311
|
+
if (type === 'past' || type === 'next') {
|
|
312
|
+
const unit = typeof typed.unit === 'string' && RELATIVE_UNITS.has(typed.unit) ? typed.unit : 'day';
|
|
313
|
+
const rawNumber = Number(typed.number);
|
|
314
|
+
const number = Number.isFinite(rawNumber) && rawNumber > 0 ? Math.floor(rawNumber) : 1;
|
|
315
|
+
return toCtxDateExpression(['date', 'relative', type, unit, `n${number}`]);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
if (PRESET_KEYS.has(type)) {
|
|
319
|
+
return toCtxDateExpression(['date', 'preset', type]);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const single = normalizeToString(value);
|
|
324
|
+
if (single) {
|
|
325
|
+
return toCtxDateExpression(['date', 'exact', 'single', 'date', `v${encodeBase64Url(single)}`]);
|
|
326
|
+
}
|
|
327
|
+
return undefined;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function resolveCtxDatePath(pathSegments: string[]): any {
|
|
331
|
+
const segments = withDatePrefix((pathSegments || []).map((seg) => String(seg)));
|
|
332
|
+
if (segments[0] !== 'date') return undefined;
|
|
333
|
+
|
|
334
|
+
if (segments[1] === 'preset' && segments.length === 3) {
|
|
335
|
+
const key = segments[2];
|
|
336
|
+
if (key === 'now') {
|
|
337
|
+
return dayjs().toISOString();
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const now = dayjs();
|
|
341
|
+
|
|
342
|
+
if (key === 'today') return now.format('YYYY-MM-DD');
|
|
343
|
+
if (key === 'yesterday') return now.add(-1, 'day').format('YYYY-MM-DD');
|
|
344
|
+
if (key === 'tomorrow') return now.add(1, 'day').format('YYYY-MM-DD');
|
|
345
|
+
|
|
346
|
+
if (key === 'thisWeek') return startOfIsoWeek(now).format('YYYY-MM-DD');
|
|
347
|
+
if (key === 'lastWeek') return startOfIsoWeek(now.add(-1, 'week')).format('YYYY-MM-DD');
|
|
348
|
+
if (key === 'nextWeek') return startOfIsoWeek(now.add(1, 'week')).format('YYYY-MM-DD');
|
|
349
|
+
|
|
350
|
+
if (key === 'thisMonth') return now.startOf('month').format('YYYY-MM-DD');
|
|
351
|
+
if (key === 'lastMonth') return now.add(-1, 'month').startOf('month').format('YYYY-MM-DD');
|
|
352
|
+
if (key === 'nextMonth') return now.add(1, 'month').startOf('month').format('YYYY-MM-DD');
|
|
353
|
+
|
|
354
|
+
if (key === 'thisQuarter') return startOfQuarter(now).format('YYYY-MM-DD');
|
|
355
|
+
if (key === 'lastQuarter') return startOfQuarter(now.add(-3, 'month')).format('YYYY-MM-DD');
|
|
356
|
+
if (key === 'nextQuarter') return startOfQuarter(now.add(3, 'month')).format('YYYY-MM-DD');
|
|
357
|
+
|
|
358
|
+
if (key === 'thisYear') return now.startOf('year').format('YYYY-MM-DD');
|
|
359
|
+
if (key === 'lastYear') return now.add(-1, 'year').startOf('year').format('YYYY-MM-DD');
|
|
360
|
+
if (key === 'nextYear') return now.add(1, 'year').startOf('year').format('YYYY-MM-DD');
|
|
361
|
+
|
|
362
|
+
return undefined;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (
|
|
366
|
+
segments[1] === 'relative' &&
|
|
367
|
+
segments.length === 5 &&
|
|
368
|
+
RELATIVE_DIRECTIONS.has(segments[2]) &&
|
|
369
|
+
RELATIVE_UNITS.has(segments[3])
|
|
370
|
+
) {
|
|
371
|
+
const amount = parseNumberToken(segments[4]);
|
|
372
|
+
if (typeof amount !== 'number') return undefined;
|
|
373
|
+
const direction = segments[2] === 'past' ? -1 : 1;
|
|
374
|
+
const unit = segments[3] as dayjs.ManipulateType;
|
|
375
|
+
return dayjs()
|
|
376
|
+
.add(direction * amount, unit)
|
|
377
|
+
.format('YYYY-MM-DD');
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (segments[1] === 'exact' && segments[2] === 'single' && segments[3] === 'date' && segments.length === 5) {
|
|
381
|
+
const token = String(segments[4] || '');
|
|
382
|
+
if (!token.startsWith('v')) return undefined;
|
|
383
|
+
return decodeBase64Url(token.slice(1));
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (segments[1] === 'exact' && segments[2] === 'range' && segments[3] === 'date' && segments.length === 6) {
|
|
387
|
+
const leftToken = String(segments[4] || '');
|
|
388
|
+
const rightToken = String(segments[5] || '');
|
|
389
|
+
if (!leftToken.startsWith('v') || !rightToken.startsWith('v')) return undefined;
|
|
390
|
+
const left = decodeBase64Url(leftToken.slice(1));
|
|
391
|
+
const right = decodeBase64Url(rightToken.slice(1));
|
|
392
|
+
if (typeof left === 'undefined' || typeof right === 'undefined') return undefined;
|
|
393
|
+
return [left, right];
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return undefined;
|
|
397
|
+
}
|
package/src/utils/exceptions.ts
CHANGED
|
@@ -34,3 +34,14 @@ export class FlowExitAllException extends Error {
|
|
|
34
34
|
this.modelUid = modelUid;
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 取消当前保存但保持设置弹窗打开
|
|
40
|
+
* 用于“保存前确认”场景,用户取消时不应关闭弹窗也不应提示错误
|
|
41
|
+
*/
|
|
42
|
+
export class FlowCancelSaveException extends Error {
|
|
43
|
+
constructor(message = 'Flow settings save cancelled.') {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'FlowCancelSaveException';
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/utils/index.ts
CHANGED
|
@@ -22,7 +22,7 @@ export {
|
|
|
22
22
|
export { escapeT, getT, tExpr } from './translation';
|
|
23
23
|
|
|
24
24
|
// 异常类
|
|
25
|
-
export { FlowExitException } from './exceptions';
|
|
25
|
+
export { FlowCancelSaveException, FlowExitAllException, FlowExitException } from './exceptions';
|
|
26
26
|
|
|
27
27
|
// 流程定义相关
|
|
28
28
|
export { defineAction } from './flow-definitions';
|
|
@@ -34,7 +34,14 @@ export { isInheritedFrom } from './inheritance';
|
|
|
34
34
|
export { resolveCreateModelOptions, resolveDefaultParams, resolveExpressions } from './params-resolvers';
|
|
35
35
|
|
|
36
36
|
// Schema 工具
|
|
37
|
-
export {
|
|
37
|
+
export {
|
|
38
|
+
compileUiSchema,
|
|
39
|
+
resolveStepUiSchema,
|
|
40
|
+
resolveStepDisabledInSettings,
|
|
41
|
+
resolveUiMode,
|
|
42
|
+
shouldHideEventInSettings,
|
|
43
|
+
shouldHideStepInSettings,
|
|
44
|
+
} from './schema-utils';
|
|
38
45
|
|
|
39
46
|
// Runtime Context Steps 设置
|
|
40
47
|
export { setupRuntimeContextSteps } from './setupRuntimeContextSteps';
|
|
@@ -59,9 +66,34 @@ export { extractPropertyPath, formatPathToVariable, isVariableExpression } from
|
|
|
59
66
|
|
|
60
67
|
export { clearAutoFlowError, getAutoFlowError, setAutoFlowError, type AutoFlowError } from './autoFlowError';
|
|
61
68
|
export { parsePathnameToViewParams, type ViewParam } from './parsePathnameToViewParams';
|
|
69
|
+
export {
|
|
70
|
+
decodeBase64Url,
|
|
71
|
+
encodeBase64Url,
|
|
72
|
+
isCompleteCtxDatePath,
|
|
73
|
+
isCtxDatePathPrefix,
|
|
74
|
+
isCtxDateExpression,
|
|
75
|
+
parseCtxDateExpression,
|
|
76
|
+
resolveCtxDatePath,
|
|
77
|
+
serializeCtxDateValue,
|
|
78
|
+
} from './dateVariable';
|
|
62
79
|
|
|
63
80
|
// 安全全局对象(window/document)
|
|
64
|
-
export {
|
|
81
|
+
export {
|
|
82
|
+
createSafeDocument,
|
|
83
|
+
createSafeWindow,
|
|
84
|
+
createSafeNavigator,
|
|
85
|
+
createSafeRunJSGlobals,
|
|
86
|
+
runjsWithSafeGlobals,
|
|
87
|
+
} from './safeGlobals';
|
|
88
|
+
|
|
89
|
+
// RunJS value helpers
|
|
90
|
+
export { isRunJSValue, normalizeRunJSValue, extractUsedVariablePathsFromRunJS, type RunJSValue } from './runjsValue';
|
|
91
|
+
|
|
92
|
+
// RunJS helpers
|
|
93
|
+
export { resolveRunJSObjectValues } from './resolveRunJSObjectValues';
|
|
94
|
+
|
|
95
|
+
// RunJS 代码兼容预处理({{ }})与 JSX 编译
|
|
96
|
+
export { prepareRunJsCode, preprocessRunJsTemplates } from './runjsTemplateCompat';
|
|
65
97
|
|
|
66
98
|
// Ephemeral context helper(用于临时注入属性/方法,避免污染父级 ctx)
|
|
67
99
|
export { createEphemeralContext } from './createEphemeralContext';
|
|
@@ -69,3 +101,6 @@ export { createEphemeralContext } from './createEphemeralContext';
|
|
|
69
101
|
// Filter helpers
|
|
70
102
|
export { pruneFilter } from './pruneFilter';
|
|
71
103
|
export { isBeforeRenderFlow } from './flows';
|
|
104
|
+
|
|
105
|
+
// Module URL resolver
|
|
106
|
+
export { resolveModuleUrl, isCssFile } from './resolveModuleUrl';
|
|
@@ -409,8 +409,13 @@ export async function preprocessExpression(expression: string, ctx: FlowContext)
|
|
|
409
409
|
async function compileExpression<TModel extends FlowModel = FlowModel>(expression: string, ctx: FlowContext) {
|
|
410
410
|
// 仅点号路径匹配:ctx.a.b.c(不支持括号/函数/索引),用于数组聚合取值
|
|
411
411
|
const matchDotOnly = (expr: string): string | null => {
|
|
412
|
-
|
|
413
|
-
|
|
412
|
+
// 顶层变量名仍使用 JS 标识符规则(与 ctx.defineProperty 保持一致);
|
|
413
|
+
// 子路径允许包含 '-'(例如 formValues.oho-test.o2m-users)。
|
|
414
|
+
const m = expr
|
|
415
|
+
.trim()
|
|
416
|
+
.match(/^ctx\.([a-zA-Z_$][a-zA-Z0-9_$]*)(?:\.([a-zA-Z_$][a-zA-Z0-9_$-]*(?:\.[a-zA-Z_$][a-zA-Z0-9_$-]*)*))?$/);
|
|
417
|
+
if (!m) return null;
|
|
418
|
+
return m[2] ? `${m[1]}.${m[2]}` : m[1];
|
|
414
419
|
};
|
|
415
420
|
|
|
416
421
|
// 基于 getValuesByPath 的聚合取值:支持数组扁平化,仅支持 '.' 访问
|
|
@@ -423,6 +428,20 @@ async function compileExpression<TModel extends FlowModel = FlowModel>(expressio
|
|
|
423
428
|
return getValuesByPath(base as object, segs.join('.'));
|
|
424
429
|
};
|
|
425
430
|
|
|
431
|
+
const resolveInnerExpression = async (innerExpr: string): Promise<any> => {
|
|
432
|
+
const dotPath = matchDotOnly(innerExpr);
|
|
433
|
+
if (dotPath) {
|
|
434
|
+
const resolved = await resolveDotOnlyPath(dotPath);
|
|
435
|
+
// 当 dotPath 含 '-' 时可能与减号运算符存在歧义,例如:ctx.aa.bb-ctx.cc。
|
|
436
|
+
// 若按 path 解析未取到值,则回退到 JS 表达式解析,尽量保持兼容。
|
|
437
|
+
if (resolved === undefined && dotPath.includes('-')) {
|
|
438
|
+
return await processExpression(innerExpr, ctx);
|
|
439
|
+
}
|
|
440
|
+
return resolved;
|
|
441
|
+
}
|
|
442
|
+
return await processExpression(innerExpr, ctx);
|
|
443
|
+
};
|
|
444
|
+
|
|
426
445
|
/**
|
|
427
446
|
* 单个表达式模式匹配
|
|
428
447
|
*
|
|
@@ -443,11 +462,7 @@ async function compileExpression<TModel extends FlowModel = FlowModel>(expressio
|
|
|
443
462
|
const singleMatch = expression.match(/^\s*\{\{\s*([^{}]+?)\s*\}\}\s*$/);
|
|
444
463
|
if (singleMatch) {
|
|
445
464
|
const inner = singleMatch[1];
|
|
446
|
-
|
|
447
|
-
if (dotPath) {
|
|
448
|
-
return await resolveDotOnlyPath(dotPath);
|
|
449
|
-
}
|
|
450
|
-
return await processExpression(inner, ctx);
|
|
465
|
+
return await resolveInnerExpression(inner);
|
|
451
466
|
}
|
|
452
467
|
|
|
453
468
|
/**
|
|
@@ -470,8 +485,7 @@ async function compileExpression<TModel extends FlowModel = FlowModel>(expressio
|
|
|
470
485
|
let result = expression;
|
|
471
486
|
|
|
472
487
|
for (const [fullMatch, innerExpr] of matches) {
|
|
473
|
-
const
|
|
474
|
-
const value = dotPath ? await resolveDotOnlyPath(dotPath) : await processExpression(innerExpr, ctx);
|
|
488
|
+
const value = await resolveInnerExpression(innerExpr);
|
|
475
489
|
if (value !== undefined) {
|
|
476
490
|
const replacement = typeof value === 'object' && value !== null ? JSON.stringify(value) : String(value);
|
|
477
491
|
result = result.replace(fullMatch, replacement);
|
|
@@ -116,8 +116,8 @@ export const parsePathnameToViewParams = (pathname: string): ViewParam[] => {
|
|
|
116
116
|
// 解析失败,按字符串保留
|
|
117
117
|
parsed = decoded;
|
|
118
118
|
}
|
|
119
|
-
} else if (decoded &&
|
|
120
|
-
// 形如 a=b&c=d 的整体段
|
|
119
|
+
} else if (decoded && /^[^=&]+=[^=&]*(?:&[^=&]+=[^=&]*)*$/.test(decoded)) {
|
|
120
|
+
// 形如 a=b 或 a=b&c=d 的整体段
|
|
121
121
|
parsed = parseKeyValuePairs(decoded);
|
|
122
122
|
}
|
|
123
123
|
currentView.filterByTk = parsed;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 解析模块 URL,将相对路径转换为完整的 CDN URL
|
|
12
|
+
*
|
|
13
|
+
* @param url - 模块地址(支持相对路径或完整 URL)
|
|
14
|
+
* @param options - 可选配置
|
|
15
|
+
* @param options.addSuffix - 是否添加 ESM_CDN_SUFFIX 后缀(如 `+esm`),默认为 `true`
|
|
16
|
+
* @returns 解析后的完整 URL
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* // 相对路径会被拼接上 CDN 前缀和后缀(默认添加)
|
|
21
|
+
* // 如果使用 esm.sh(默认),不需要后缀
|
|
22
|
+
* resolveModuleUrl('vue@3.4.0')
|
|
23
|
+
* // => 'https://esm.sh/vue@3.4.0'
|
|
24
|
+
*
|
|
25
|
+
* // 如果使用 jsdelivr,需要配置 ESM_CDN_SUFFIX='/+esm'
|
|
26
|
+
* // resolveModuleUrl('vue@3.4.0') => 'https://cdn.jsdelivr.net/npm/vue@3.4.0/+esm'
|
|
27
|
+
*
|
|
28
|
+
* // 不添加后缀(适用于 UMD 库或 CSS 文件)
|
|
29
|
+
* resolveModuleUrl('vue@3.4.0', { addSuffix: false })
|
|
30
|
+
* // => 'https://esm.sh/vue@3.4.0' (即使配置了 suffix 也不会添加)
|
|
31
|
+
*
|
|
32
|
+
* // 原始 URL(适用于 UMD 库)
|
|
33
|
+
* resolveModuleUrl('lodash@4.17.21/lodash.js', { raw: true })
|
|
34
|
+
* // => 'https://esm.sh/lodash@4.17.21/lodash.js?raw' (即使配置了 suffix 也不会添加)
|
|
35
|
+
*
|
|
36
|
+
* // 完整 URL 保持不变
|
|
37
|
+
* resolveModuleUrl('https://cdn.jsdelivr.net/npm/vue@3.4.0')
|
|
38
|
+
* // => 'https://cdn.jsdelivr.net/npm/vue@3.4.0'
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function resolveModuleUrl(url: string, options?: { addSuffix?: boolean; raw?: boolean }): string {
|
|
42
|
+
if (!url || typeof url !== 'string') {
|
|
43
|
+
throw new Error('invalid url');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const u = url.trim();
|
|
47
|
+
|
|
48
|
+
// 如果是完整 URL(http:// 或 https://),直接返回
|
|
49
|
+
if (u.startsWith('http://') || u.startsWith('https://')) {
|
|
50
|
+
return u;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// 相对路径:拼接 CDN 前缀和后缀
|
|
54
|
+
const ESM_CDN_BASE_URL = (window as any)['__esm_cdn_base_url__'] || 'https://esm.sh';
|
|
55
|
+
const ESM_CDN_SUFFIX = options?.addSuffix ? (window as any)['__esm_cdn_suffix__'] || '' : '';
|
|
56
|
+
|
|
57
|
+
// 移除 base URL 末尾的斜杠,移除相对路径开头的斜杠
|
|
58
|
+
const base = ESM_CDN_BASE_URL.replace(/\/$/, '');
|
|
59
|
+
const path = u.replace(/^\//, '');
|
|
60
|
+
|
|
61
|
+
if (options?.raw) {
|
|
62
|
+
const sep = path.includes('?') ? '&' : '?';
|
|
63
|
+
return `${base}/${path}${sep}raw`;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return `${base}/${path}${ESM_CDN_SUFFIX}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 判断 URL 是否为 CSS 文件
|
|
71
|
+
*
|
|
72
|
+
* @param url - 文件 URL(支持带 query 和 hash,如 `example.css?v=123`)
|
|
73
|
+
* @returns 如果是 CSS 文件返回 `true`,否则返回 `false`
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* isCssFile('style.css') // => true
|
|
78
|
+
* isCssFile('style.css?v=123') // => true
|
|
79
|
+
* isCssFile('style.css#section') // => true
|
|
80
|
+
* isCssFile('script.js') // => false
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function isCssFile(url: string): boolean {
|
|
84
|
+
if (!url || typeof url !== 'string') {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 去掉 query 和 hash 后判断文件扩展名
|
|
89
|
+
const pathPart = url.split('?')[0].split('#')[0];
|
|
90
|
+
return pathPart.endsWith('.css');
|
|
91
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { isRunJSValue, normalizeRunJSValue } from './runjsValue';
|
|
11
|
+
import { runjsWithSafeGlobals } from './safeGlobals';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Resolve an object's values, executing any RunJSValue entries via ctx.runjs.
|
|
15
|
+
*
|
|
16
|
+
* - Skips `undefined` values
|
|
17
|
+
* - Skips empty RunJS code (treated as not configured)
|
|
18
|
+
* - Throws when a RunJS execution fails
|
|
19
|
+
*/
|
|
20
|
+
export async function resolveRunJSObjectValues(ctx: unknown, raw: unknown): Promise<Record<string, any>> {
|
|
21
|
+
const out: Record<string, any> = {};
|
|
22
|
+
|
|
23
|
+
if (!raw || typeof raw !== 'object') return out;
|
|
24
|
+
if (Array.isArray(raw)) return out;
|
|
25
|
+
|
|
26
|
+
for (const [key, value] of Object.entries(raw as Record<string, any>)) {
|
|
27
|
+
if (typeof value === 'undefined') continue;
|
|
28
|
+
|
|
29
|
+
if (isRunJSValue(value)) {
|
|
30
|
+
const { code, version } = normalizeRunJSValue(value);
|
|
31
|
+
if (!code.trim()) continue;
|
|
32
|
+
const ret = await runjsWithSafeGlobals(ctx, code, { version });
|
|
33
|
+
if (!ret?.success) {
|
|
34
|
+
throw new Error(`RunJS execution failed for "${key}"`);
|
|
35
|
+
}
|
|
36
|
+
if (typeof ret.value !== 'undefined') {
|
|
37
|
+
out[key] = ret.value;
|
|
38
|
+
}
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
out[key] = value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return out;
|
|
46
|
+
}
|