@nocobase/flow-engine 2.0.0-beta.8 → 2.0.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/lib/BlockScopedFlowEngine.js +0 -1
- package/lib/FlowDefinition.d.ts +2 -0
- package/lib/JSRunner.d.ts +6 -0
- package/lib/JSRunner.js +32 -2
- package/lib/ViewScopedFlowEngine.js +3 -0
- package/lib/acl/Acl.js +13 -3
- package/lib/components/FlowContextSelector.js +155 -10
- 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 +5 -1
- 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 +81 -51
- package/lib/executor/FlowExecutor.d.ts +2 -1
- package/lib/executor/FlowExecutor.js +163 -22
- package/lib/flowContext.d.ts +230 -7
- package/lib/flowContext.js +2267 -148
- package/lib/flowEngine.d.ts +21 -0
- package/lib/flowEngine.js +56 -8
- package/lib/flowI18n.js +6 -4
- package/lib/flowSettings.js +17 -11
- package/lib/index.d.ts +7 -1
- package/lib/index.js +21 -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.js +12 -1
- package/lib/provider.js +5 -5
- 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/setup.js +6 -0
- 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 +2 -0
- package/lib/scheduler/ModelOperationScheduler.js +25 -21
- package/lib/types.d.ts +27 -0
- 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 +45 -0
- package/lib/utils/params-resolvers.js +16 -9
- 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 +10 -0
- package/lib/utils/schema-utils.js +61 -0
- 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/useDialog.js +7 -2
- package/lib/views/useDrawer.js +7 -2
- package/lib/views/usePage.d.ts +4 -0
- package/lib/views/usePage.js +43 -6
- 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 +4 -4
- package/src/BlockScopedFlowEngine.ts +2 -5
- package/src/JSRunner.ts +44 -2
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +64 -0
- 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__/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__/runjsContext.test.ts +10 -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 +49 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
- package/src/__tests__/runjsSnippets.test.ts +40 -3
- package/src/acl/Acl.tsx +3 -3
- package/src/components/FlowContextSelector.tsx +208 -12
- 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 +13 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
- 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 +85 -110
- package/src/executor/FlowExecutor.ts +200 -23
- package/src/executor/__tests__/flowExecutor.test.ts +66 -0
- package/src/flowContext.ts +2986 -211
- package/src/flowEngine.ts +59 -8
- package/src/flowI18n.ts +7 -5
- package/src/flowSettings.ts +18 -12
- package/src/index.ts +14 -1
- 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 +554 -0
- package/src/models/__tests__/flowModel.test.ts +20 -4
- package/src/models/flowModel.tsx +13 -1
- package/src/provider.tsx +7 -6
- 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/setup.ts +6 -0
- 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 +27 -21
- package/src/types.ts +38 -1
- package/src/utils/__tests__/dateVariable.test.ts +101 -0
- package/src/utils/__tests__/params-resolvers.test.ts +40 -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 +95 -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 +37 -3
- package/src/utils/params-resolvers.ts +23 -9
- 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 +79 -0
- package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
- 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/useDialog.tsx +8 -1
- package/src/views/useDrawer.tsx +8 -1
- package/src/views/usePage.tsx +51 -5
- package/src/views/usePopover.tsx +4 -1
- package/src/views/viewEvents.ts +55 -0
|
@@ -0,0 +1,88 @@
|
|
|
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 { FlowRunJSContext } from '../flowContext';
|
|
11
|
+
import { RunJSContextRegistry, type RunJSVersion } from './registry';
|
|
12
|
+
|
|
13
|
+
export type RunJSContextContributionApi = {
|
|
14
|
+
version: RunJSVersion;
|
|
15
|
+
RunJSContextRegistry: typeof RunJSContextRegistry;
|
|
16
|
+
FlowRunJSContext: typeof FlowRunJSContext;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export type RunJSContextContribution = (api: RunJSContextContributionApi) => void | Promise<void>;
|
|
20
|
+
|
|
21
|
+
const contributions = new Set<RunJSContextContribution>();
|
|
22
|
+
const appliedByVersion = new Map<RunJSVersion, Set<RunJSContextContribution>>();
|
|
23
|
+
const setupDoneVersions = new Set<RunJSVersion>();
|
|
24
|
+
|
|
25
|
+
async function applyContributionOnce(version: RunJSVersion, contribution: RunJSContextContribution) {
|
|
26
|
+
const applied = appliedByVersion.get(version) || new Set<RunJSContextContribution>();
|
|
27
|
+
appliedByVersion.set(version, applied);
|
|
28
|
+
if (applied.has(contribution)) return;
|
|
29
|
+
|
|
30
|
+
// Mark as applied before awaiting to avoid duplicate runs on concurrency.
|
|
31
|
+
// If it fails, remove the marker so a later setup retry can re-apply.
|
|
32
|
+
applied.add(contribution);
|
|
33
|
+
try {
|
|
34
|
+
await contribution({
|
|
35
|
+
version,
|
|
36
|
+
RunJSContextRegistry,
|
|
37
|
+
FlowRunJSContext,
|
|
38
|
+
});
|
|
39
|
+
} catch (error) {
|
|
40
|
+
applied.delete(contribution);
|
|
41
|
+
throw error;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Register a RunJS context/doc contribution.
|
|
47
|
+
*
|
|
48
|
+
* - If RunJS contexts have already been set up for a version, the contribution is applied immediately once.
|
|
49
|
+
* - Each contribution is executed at most once per version.
|
|
50
|
+
*/
|
|
51
|
+
export function registerRunJSContextContribution(contribution: RunJSContextContribution) {
|
|
52
|
+
if (typeof contribution !== 'function') {
|
|
53
|
+
throw new Error('[flow-engine] registerRunJSContextContribution: contribution must be a function');
|
|
54
|
+
}
|
|
55
|
+
if (contributions.has(contribution)) return;
|
|
56
|
+
contributions.add(contribution);
|
|
57
|
+
|
|
58
|
+
// Apply immediately for already-setup versions (late registration).
|
|
59
|
+
for (const version of setupDoneVersions) {
|
|
60
|
+
void applyContributionOnce(version, contribution).catch((error) => {
|
|
61
|
+
// Avoid unhandled rejections in late registrations
|
|
62
|
+
try {
|
|
63
|
+
// eslint-disable-next-line no-console
|
|
64
|
+
console.error('[flow-engine] RunJS context contribution failed:', error);
|
|
65
|
+
} catch (_) {
|
|
66
|
+
void 0;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Apply all registered contributions for a given version.
|
|
74
|
+
* Intended to be called by setupRunJSContexts().
|
|
75
|
+
*/
|
|
76
|
+
export async function applyRunJSContextContributions(version: RunJSVersion) {
|
|
77
|
+
for (const contribution of contributions) {
|
|
78
|
+
await applyContributionOnce(version, contribution);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Mark setupRunJSContexts() as completed for a given version.
|
|
84
|
+
* Used to support late contributions that should take effect without re-running setup.
|
|
85
|
+
*/
|
|
86
|
+
export function markRunJSContextsSetupDone(version: RunJSVersion) {
|
|
87
|
+
setupDoneVersions.add(version);
|
|
88
|
+
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import type { FlowContext } from '../flowContext';
|
|
11
|
+
import { createRunJSDeprecationProxy } from '../flowContext';
|
|
11
12
|
import { JSRunner } from '../JSRunner';
|
|
12
13
|
import type { JSRunnerOptions } from '../JSRunner';
|
|
13
14
|
import { RunJSContextRegistry, getModelClassName, type RunJSVersion } from './registry';
|
|
@@ -36,7 +37,16 @@ export function createJSRunnerWithVersion(this: FlowContext, options?: JSRunnerO
|
|
|
36
37
|
throw new Error('[RunJS] No RunJSContext registered for version/model.');
|
|
37
38
|
}
|
|
38
39
|
const runCtx = new (Ctor as any)(ensureFlowContext(this));
|
|
39
|
-
|
|
40
|
+
let doc: any = {};
|
|
41
|
+
try {
|
|
42
|
+
const locale = getLocale(this);
|
|
43
|
+
if ((Ctor as any)?.getDoc?.length) doc = (Ctor as any).getDoc(locale) || {};
|
|
44
|
+
else doc = (Ctor as any)?.getDoc?.() || {};
|
|
45
|
+
} catch (_) {
|
|
46
|
+
doc = {};
|
|
47
|
+
}
|
|
48
|
+
const deprecatedCtx = createRunJSDeprecationProxy(runCtx, { doc });
|
|
49
|
+
const globals: Record<string, any> = { ctx: deprecatedCtx, ...(options?.globals || {}) };
|
|
40
50
|
// 对字段/区块类上下文,默认注入 window/document 以支持在沙箱中访问 DOM API
|
|
41
51
|
if (modelClass === 'JSFieldModel' || modelClass === 'JSBlockModel') {
|
|
42
52
|
if (typeof window !== 'undefined') globals.window = window as any;
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { RunJSContextRegistry } from './registry';
|
|
14
14
|
import { FlowRunJSContext } from '../flowContext';
|
|
15
15
|
import { defineBaseContextMeta } from './contexts/base';
|
|
16
|
+
import { applyRunJSContextContributions, markRunJSContextsSetupDone } from './contributions';
|
|
16
17
|
|
|
17
18
|
let done = false;
|
|
18
19
|
export async function setupRunJSContexts() {
|
|
@@ -23,6 +24,7 @@ export async function setupRunJSContexts() {
|
|
|
23
24
|
const [
|
|
24
25
|
{ JSBlockRunJSContext },
|
|
25
26
|
{ JSFieldRunJSContext },
|
|
27
|
+
{ JSEditableFieldRunJSContext },
|
|
26
28
|
{ JSItemRunJSContext },
|
|
27
29
|
{ JSColumnRunJSContext },
|
|
28
30
|
{ FormJSFieldItemRunJSContext },
|
|
@@ -31,6 +33,7 @@ export async function setupRunJSContexts() {
|
|
|
31
33
|
] = await Promise.all([
|
|
32
34
|
import('./contexts/JSBlockRunJSContext'),
|
|
33
35
|
import('./contexts/JSFieldRunJSContext'),
|
|
36
|
+
import('./contexts/JSEditableFieldRunJSContext'),
|
|
34
37
|
import('./contexts/JSItemRunJSContext'),
|
|
35
38
|
import('./contexts/JSColumnRunJSContext'),
|
|
36
39
|
import('./contexts/FormJSFieldItemRunJSContext'),
|
|
@@ -42,10 +45,13 @@ export async function setupRunJSContexts() {
|
|
|
42
45
|
RunJSContextRegistry.register(v1, '*', FlowRunJSContext);
|
|
43
46
|
RunJSContextRegistry.register(v1, 'JSBlockModel', JSBlockRunJSContext, { scenes: ['block'] });
|
|
44
47
|
RunJSContextRegistry.register(v1, 'JSFieldModel', JSFieldRunJSContext, { scenes: ['detail'] });
|
|
48
|
+
RunJSContextRegistry.register(v1, 'JSEditableFieldModel', JSEditableFieldRunJSContext, { scenes: ['form'] });
|
|
45
49
|
RunJSContextRegistry.register(v1, 'JSItemModel', JSItemRunJSContext, { scenes: ['form'] });
|
|
46
50
|
RunJSContextRegistry.register(v1, 'JSColumnModel', JSColumnRunJSContext, { scenes: ['table'] });
|
|
47
51
|
RunJSContextRegistry.register(v1, 'FormJSFieldItemModel', FormJSFieldItemRunJSContext, { scenes: ['form'] });
|
|
48
52
|
RunJSContextRegistry.register(v1, 'JSRecordActionModel', JSRecordActionRunJSContext, { scenes: ['table'] });
|
|
49
53
|
RunJSContextRegistry.register(v1, 'JSCollectionActionModel', JSCollectionActionRunJSContext, { scenes: ['table'] });
|
|
54
|
+
await applyRunJSContextContributions(v1);
|
|
50
55
|
done = true;
|
|
56
|
+
markRunJSContextsSetupDone(v1);
|
|
51
57
|
}
|
|
@@ -13,16 +13,16 @@ const snippet: SnippetModule = {
|
|
|
13
13
|
contexts: ['*'],
|
|
14
14
|
prefix: 'sn-api-request',
|
|
15
15
|
label: 'API request template',
|
|
16
|
-
description: 'Basic template to send HTTP requests via ctx.
|
|
16
|
+
description: 'Basic template to send HTTP requests via ctx.request',
|
|
17
17
|
locales: {
|
|
18
18
|
'zh-CN': {
|
|
19
19
|
label: 'API 请求模板',
|
|
20
|
-
description: '使用 ctx.
|
|
20
|
+
description: '使用 ctx.request 发送 HTTP 请求的基础模板',
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
23
|
content: `
|
|
24
24
|
// Replace url/method/params/data as needed
|
|
25
|
-
const response = await ctx.
|
|
25
|
+
const response = await ctx.request({
|
|
26
26
|
url: 'users:list',
|
|
27
27
|
method: 'get',
|
|
28
28
|
params: {
|
|
@@ -23,14 +23,13 @@ const snippet: SnippetModule = {
|
|
|
23
23
|
content: `
|
|
24
24
|
// Import an ESM module by URL
|
|
25
25
|
// Works in yarn dev and yarn start
|
|
26
|
-
const mod = await ctx.importAsync('
|
|
26
|
+
const mod = await ctx.importAsync('lit-html@2');
|
|
27
27
|
const { html, render } = mod;
|
|
28
28
|
|
|
29
|
-
ctx.element.innerHTML = '';
|
|
30
29
|
const container = document.createElement('div');
|
|
31
30
|
container.style.padding = '8px';
|
|
32
31
|
container.style.border = '1px dashed #999';
|
|
33
|
-
ctx.
|
|
32
|
+
ctx.render(container);
|
|
34
33
|
|
|
35
34
|
render(html\`<span style="color:#52c41a;">lit-html loaded and rendered</span>\`, container);
|
|
36
35
|
`,
|
|
@@ -16,15 +16,20 @@ const snippet: SnippetModule = {
|
|
|
16
16
|
contexts: [JSBlockRunJSContext, JSFieldRunJSContext, FormJSFieldItemRunJSContext],
|
|
17
17
|
prefix: 'sn-query-selector',
|
|
18
18
|
label: 'Query selector',
|
|
19
|
-
description: 'Find a child element inside
|
|
19
|
+
description: 'Find a child element inside rendered DOM using querySelector',
|
|
20
20
|
locales: {
|
|
21
21
|
'zh-CN': {
|
|
22
22
|
label: '查询子元素',
|
|
23
|
-
description: '使用 querySelector
|
|
23
|
+
description: '使用 querySelector 在渲染的 DOM 内查找子元素',
|
|
24
24
|
},
|
|
25
25
|
},
|
|
26
26
|
content: `
|
|
27
|
-
const
|
|
27
|
+
const wrapper = document.createElement('div');
|
|
28
|
+
wrapper.innerHTML = '<div class="child-class"></div>';
|
|
29
|
+
|
|
30
|
+
ctx.render(wrapper);
|
|
31
|
+
|
|
32
|
+
const child = wrapper.querySelector('.child-class');
|
|
28
33
|
if (child) {
|
|
29
34
|
child.textContent = ctx.t('Hello from querySelector');
|
|
30
35
|
}
|
|
@@ -22,7 +22,7 @@ const snippet: SnippetModule = {
|
|
|
22
22
|
},
|
|
23
23
|
content: `
|
|
24
24
|
// Load an external library (AMD/RequireJS)
|
|
25
|
-
const dayjs = await ctx.requireAsync('
|
|
25
|
+
const dayjs = await ctx.requireAsync('dayjs@1/dayjs.min.js');
|
|
26
26
|
console.log('dayjs loaded:', dayjs?.default || dayjs);
|
|
27
27
|
`,
|
|
28
28
|
};
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import { RunJSContextRegistry } from '../registry';
|
|
11
11
|
|
|
12
|
+
export type RunJSSnippetLoader = () => Promise<any>;
|
|
13
|
+
|
|
12
14
|
// Simple manual exports - no build-time magic needed
|
|
13
|
-
const snippets: Record<string,
|
|
15
|
+
const snippets: Record<string, RunJSSnippetLoader | undefined> = {
|
|
14
16
|
// global
|
|
15
17
|
'global/message-success': () => import('./global/message-success.snippet'),
|
|
16
18
|
'global/message-error': () => import('./global/message-error.snippet'),
|
|
@@ -69,6 +71,32 @@ const snippets: Record<string, () => Promise<any>> = {
|
|
|
69
71
|
|
|
70
72
|
export default snippets;
|
|
71
73
|
|
|
74
|
+
/**
|
|
75
|
+
* Register a RunJS snippet loader for editors/AI coding.
|
|
76
|
+
*
|
|
77
|
+
* - By default, an existing ref will NOT be overwritten (returns false).
|
|
78
|
+
* - Use { override: true } to overwrite an existing ref (returns true).
|
|
79
|
+
*/
|
|
80
|
+
export function registerRunJSSnippet(
|
|
81
|
+
ref: string,
|
|
82
|
+
loader: RunJSSnippetLoader,
|
|
83
|
+
options?: {
|
|
84
|
+
override?: boolean;
|
|
85
|
+
},
|
|
86
|
+
): boolean {
|
|
87
|
+
if (typeof ref !== 'string' || !ref.trim()) {
|
|
88
|
+
throw new Error('[flow-engine] registerRunJSSnippet: ref must be a non-empty string');
|
|
89
|
+
}
|
|
90
|
+
if (typeof loader !== 'function') {
|
|
91
|
+
throw new Error('[flow-engine] registerRunJSSnippet: loader must be a function returning a Promise');
|
|
92
|
+
}
|
|
93
|
+
const key = ref.trim();
|
|
94
|
+
const existed = typeof snippets[key] === 'function';
|
|
95
|
+
if (existed && !options?.override) return false;
|
|
96
|
+
snippets[key] = loader;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
72
100
|
// Cohesive snippet helpers for clients (editor, etc.)
|
|
73
101
|
type EngineSnippetEntry = {
|
|
74
102
|
name: string;
|
|
@@ -127,8 +155,8 @@ function resolveLocaleMeta(def: any, locale?: string) {
|
|
|
127
155
|
}
|
|
128
156
|
|
|
129
157
|
export async function getSnippetBody(ref: string): Promise<string> {
|
|
130
|
-
const loader =
|
|
131
|
-
if (
|
|
158
|
+
const loader = snippets[ref];
|
|
159
|
+
if (typeof loader !== 'function') throw new Error(`[flow-engine] snippet not found: ${ref}`);
|
|
132
160
|
const mod = await loader();
|
|
133
161
|
const def = mod?.default;
|
|
134
162
|
// engine snippet modules export a SnippetModule as default
|
|
@@ -152,46 +180,52 @@ export async function listSnippetsForContext(
|
|
|
152
180
|
}
|
|
153
181
|
await Promise.all(
|
|
154
182
|
Object.entries(snippets).map(async ([key, loader]) => {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
183
|
+
if (typeof loader !== 'function') return;
|
|
184
|
+
try {
|
|
185
|
+
const mod = await loader();
|
|
186
|
+
const def = mod?.default || {};
|
|
187
|
+
const body: any = def?.content ?? mod?.content;
|
|
188
|
+
if (typeof body !== 'string') return;
|
|
189
|
+
let ok = true;
|
|
190
|
+
if (Array.isArray(def?.contexts) && def.contexts.length) {
|
|
191
|
+
const ctxNames = def.contexts.map((item: any) => {
|
|
192
|
+
if (item === '*') return '*';
|
|
193
|
+
if (typeof item === 'string') return item;
|
|
194
|
+
if (typeof item === 'function') return item.name || '*';
|
|
195
|
+
if (item && typeof item === 'object' && typeof item.name === 'string') return item.name;
|
|
196
|
+
return String(item ?? '');
|
|
197
|
+
});
|
|
198
|
+
if (ctxClassName === '*') {
|
|
199
|
+
// '*' means return all snippets without filtering by context
|
|
200
|
+
ok = true;
|
|
201
|
+
} else {
|
|
202
|
+
ok = ctxNames.includes('*') || ctxNames.some((name: string) => allowedContextNames.has(name));
|
|
203
|
+
}
|
|
173
204
|
}
|
|
205
|
+
if (ok && Array.isArray(def?.versions) && def.versions.length) {
|
|
206
|
+
ok = def.versions.includes('*') || def.versions.includes(version);
|
|
207
|
+
}
|
|
208
|
+
if (!ok) return;
|
|
209
|
+
const localeMeta = resolveLocaleMeta(def, locale);
|
|
210
|
+
const name = localeMeta.label || def?.label || deriveNameFromKey(key);
|
|
211
|
+
const description = localeMeta.description ?? def?.description;
|
|
212
|
+
const prefix = def?.prefix || name;
|
|
213
|
+
const groups = computeGroups(def, key);
|
|
214
|
+
const scenes = normalizeScenes(def, key);
|
|
215
|
+
entries.push({
|
|
216
|
+
name,
|
|
217
|
+
prefix,
|
|
218
|
+
description,
|
|
219
|
+
body,
|
|
220
|
+
ref: key,
|
|
221
|
+
group: groups[0],
|
|
222
|
+
groups,
|
|
223
|
+
scenes,
|
|
224
|
+
});
|
|
225
|
+
} catch (_) {
|
|
226
|
+
// fail-open: ignore broken snippet loader
|
|
227
|
+
return;
|
|
174
228
|
}
|
|
175
|
-
if (ok && Array.isArray(def?.versions) && def.versions.length) {
|
|
176
|
-
ok = def.versions.includes('*') || def.versions.includes(version);
|
|
177
|
-
}
|
|
178
|
-
if (!ok) return;
|
|
179
|
-
const localeMeta = resolveLocaleMeta(def, locale);
|
|
180
|
-
const name = localeMeta.label || def?.label || deriveNameFromKey(key);
|
|
181
|
-
const description = localeMeta.description ?? def?.description;
|
|
182
|
-
const prefix = def?.prefix || name;
|
|
183
|
-
const groups = computeGroups(def, key);
|
|
184
|
-
const scenes = normalizeScenes(def, key);
|
|
185
|
-
entries.push({
|
|
186
|
-
name,
|
|
187
|
-
prefix,
|
|
188
|
-
description,
|
|
189
|
-
body,
|
|
190
|
-
ref: key,
|
|
191
|
-
group: groups[0],
|
|
192
|
-
groups,
|
|
193
|
-
scenes,
|
|
194
|
-
});
|
|
195
229
|
}),
|
|
196
230
|
);
|
|
197
231
|
return entries;
|
|
@@ -21,20 +21,18 @@ const snippet: SnippetModule = {
|
|
|
21
21
|
description: '渲染按钮并绑定点击事件处理',
|
|
22
22
|
},
|
|
23
23
|
},
|
|
24
|
-
content:
|
|
25
|
-
`
|
|
24
|
+
content: `
|
|
26
25
|
// Render a button and bind a click handler
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
26
|
+
const button = document.createElement('button');
|
|
27
|
+
button.textContent = ctx.t('Click me');
|
|
28
|
+
button.style.padding = '6px 12px';
|
|
29
|
+
button.addEventListener('click', () => ctx.message.success(ctx.t('Clicked!')));
|
|
30
|
+
|
|
31
|
+
const wrapper = document.createElement('div');
|
|
32
|
+
wrapper.style.padding = '12px';
|
|
33
|
+
wrapper.appendChild(button);
|
|
34
|
+
|
|
35
|
+
ctx.render(wrapper);
|
|
38
36
|
`,
|
|
39
37
|
};
|
|
40
38
|
|
|
@@ -23,7 +23,7 @@ const snippet: SnippetModule = {
|
|
|
23
23
|
},
|
|
24
24
|
content: `
|
|
25
25
|
// Fetch users
|
|
26
|
-
const { data } = await ctx.
|
|
26
|
+
const { data } = await ctx.request({
|
|
27
27
|
url: 'users:list',
|
|
28
28
|
method: 'get',
|
|
29
29
|
params: { pageSize: 5 },
|
|
@@ -31,14 +31,14 @@ const { data } = await ctx.api.request({
|
|
|
31
31
|
const rows = Array.isArray(data?.data) ? data.data : (Array.isArray(data) ? data : []);
|
|
32
32
|
|
|
33
33
|
// Render as a simple HTML list
|
|
34
|
-
ctx.
|
|
34
|
+
ctx.render([
|
|
35
35
|
'<div style="padding:12px">',
|
|
36
36
|
'<h4 style="margin:0 0 8px">' + ctx.t('Users') + '</h4>',
|
|
37
37
|
'<ul style="margin:0; padding-left:20px">',
|
|
38
38
|
...rows.map((r, i) => '<li>#' + (i + 1) + ': ' + String((r && (r.nickname ?? r.username ?? r.id)) ?? '') + '</li>'),
|
|
39
39
|
'</ul>',
|
|
40
40
|
'</div>'
|
|
41
|
-
].join('');
|
|
41
|
+
].join(''));
|
|
42
42
|
`,
|
|
43
43
|
};
|
|
44
44
|
|
|
@@ -32,10 +32,10 @@ const canvas = document.createElement('canvas');
|
|
|
32
32
|
canvas.width = 480;
|
|
33
33
|
canvas.height = 320;
|
|
34
34
|
wrapper.appendChild(canvas);
|
|
35
|
-
ctx.
|
|
35
|
+
ctx.render(wrapper);
|
|
36
36
|
|
|
37
37
|
async function renderChart() {
|
|
38
|
-
const loaded = await ctx.requireAsync('
|
|
38
|
+
const loaded = await ctx.requireAsync('chart.js@4.4.0/dist/chart.umd.min.js');
|
|
39
39
|
const Chart = loaded?.Chart || loaded?.default?.Chart || loaded?.default;
|
|
40
40
|
if (!Chart) {
|
|
41
41
|
throw new Error('Chart.js is not available');
|
|
@@ -25,8 +25,8 @@ const snippet: SnippetModule = {
|
|
|
25
25
|
const container = document.createElement('div');
|
|
26
26
|
container.style.height = '400px';
|
|
27
27
|
container.style.width = '100%';
|
|
28
|
-
ctx.
|
|
29
|
-
const echarts = await ctx.requireAsync('
|
|
28
|
+
ctx.render(container);
|
|
29
|
+
const echarts = await ctx.requireAsync('echarts@5/dist/echarts.min.js');
|
|
30
30
|
if (!echarts) {
|
|
31
31
|
throw new Error('ECharts library not loaded');
|
|
32
32
|
}
|
|
@@ -30,8 +30,8 @@ iframe.style.width = '100%';
|
|
|
30
30
|
iframe.style.height = '100%';
|
|
31
31
|
iframe.style.border = 'none';
|
|
32
32
|
|
|
33
|
-
//
|
|
34
|
-
ctx.
|
|
33
|
+
// Render the iframe as the only content
|
|
34
|
+
ctx.render(iframe);
|
|
35
35
|
`,
|
|
36
36
|
};
|
|
37
37
|
|
|
@@ -14,32 +14,27 @@ const snippet: SnippetModule = {
|
|
|
14
14
|
contexts: [JSBlockRunJSContext],
|
|
15
15
|
prefix: 'sn-resource-example',
|
|
16
16
|
label: 'Resource example',
|
|
17
|
-
description: 'Create a resource via ctx.
|
|
17
|
+
description: 'Create a resource via ctx.makeResource and render JSON output',
|
|
18
18
|
locales: {
|
|
19
19
|
'zh-CN': {
|
|
20
20
|
label: '资源示例',
|
|
21
|
-
description: '使用 ctx.
|
|
21
|
+
description: '使用 ctx.initResource 加载数据并渲染 JSON 输出',
|
|
22
22
|
},
|
|
23
23
|
},
|
|
24
|
-
content:
|
|
25
|
-
`
|
|
24
|
+
content: `
|
|
26
25
|
// Create a resource and load a single record
|
|
27
|
-
const resource = ctx.
|
|
26
|
+
const resource = ctx.makeResource('SingleRecordResource');
|
|
28
27
|
resource.setDataSourceKey('main');
|
|
29
28
|
resource.setResourceName('users');
|
|
30
29
|
// Optionally set filterByTk to target a specific record:
|
|
31
30
|
// resource.setRequestOptions('params', { filterByTk: 1 });
|
|
32
31
|
await resource.refresh();
|
|
33
32
|
|
|
34
|
-
ctx.
|
|
35
|
-
'`' +
|
|
36
|
-
`
|
|
33
|
+
ctx.render(\`
|
|
37
34
|
<pre style="padding: 12px; background: #f5f5f5; border-radius: 6px;">
|
|
38
35
|
\${JSON.stringify(resource.getData(), null, 2)}
|
|
39
36
|
</pre>
|
|
40
|
-
|
|
41
|
-
'`' +
|
|
42
|
-
`;
|
|
37
|
+
\`);
|
|
43
38
|
`,
|
|
44
39
|
};
|
|
45
40
|
|
|
@@ -30,12 +30,12 @@ container.style.position = 'relative';
|
|
|
30
30
|
container.style.borderRadius = '10px';
|
|
31
31
|
container.style.overflow = 'hidden';
|
|
32
32
|
container.style.background = 'radial-gradient(700px 300px at 20% 25%, #172036, #0b0f19 60%), radial-gradient(600px 240px at 80% 70%, rgba(56,189,248,0.12), transparent 60%)';
|
|
33
|
-
ctx.
|
|
33
|
+
ctx.render(container);
|
|
34
34
|
|
|
35
35
|
// 不做显式清理逻辑;如需存储信息,统一挂在 ctx.model 上
|
|
36
36
|
|
|
37
|
-
// 使用 ctx.
|
|
38
|
-
ctx.
|
|
37
|
+
// 使用 ctx.initResource 加载 users:list(真实数据)
|
|
38
|
+
ctx.initResource('MultiRecordResource');
|
|
39
39
|
const resource = ctx.resource;
|
|
40
40
|
resource.setDataSourceKey && resource.setDataSourceKey('main');
|
|
41
41
|
resource.setResourceName && resource.setResourceName('users');
|
|
@@ -44,7 +44,7 @@ try {
|
|
|
44
44
|
await resource.refresh();
|
|
45
45
|
} catch (err) {
|
|
46
46
|
var msg = (err && err.message) ? err.message : 'users:list 请求失败';
|
|
47
|
-
|
|
47
|
+
container.innerHTML = '<div style="color:#cbd5e1; padding: 12px; text-align:center;">' + msg + '</div>';
|
|
48
48
|
throw err;
|
|
49
49
|
}
|
|
50
50
|
|
|
@@ -80,7 +80,7 @@ function makeAvatarTexture(user, idx) {
|
|
|
80
80
|
return tex;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
const THREE = await ctx.importAsync('
|
|
83
|
+
const THREE = await ctx.importAsync('three@0.160.0');
|
|
84
84
|
const { Scene, PerspectiveCamera, WebGLRenderer, Color, AmbientLight, DirectionalLight, Group, Mesh, MeshStandardMaterial, SphereGeometry, Raycaster, Vector2 } = THREE;
|
|
85
85
|
|
|
86
86
|
const scene = new Scene();
|
|
@@ -186,7 +186,7 @@ async function getPrimaryKeyField() {
|
|
|
186
186
|
if (__pkField) return __pkField;
|
|
187
187
|
const name = (resource && resource.getResourceName) ? resource.getResourceName() : 'users';
|
|
188
188
|
try {
|
|
189
|
-
const meta = await ctx.
|
|
189
|
+
const meta = await ctx.request({ url: 'collections:get', method: 'get', params: { filterByTk: name } });
|
|
190
190
|
const data = (meta && meta.data) ? meta.data : {};
|
|
191
191
|
// prefer filterTargetKey, fallback to fields.primaryKey
|
|
192
192
|
const ft = (data && data.filterTargetKey) ? data.filterTargetKey : (data && data.options && data.options.filterTargetKey);
|
|
@@ -29,10 +29,10 @@ mountNode.style.borderRadius = '8px';
|
|
|
29
29
|
const target = document.createElement('div');
|
|
30
30
|
target.className = 'nb-vue-counter';
|
|
31
31
|
mountNode.appendChild(target);
|
|
32
|
-
ctx.
|
|
32
|
+
ctx.render(mountNode);
|
|
33
33
|
|
|
34
34
|
async function bootstrap() {
|
|
35
|
-
const mod = await ctx.importAsync('
|
|
35
|
+
const mod = await ctx.importAsync('vue@3.4.27/dist/vue.runtime.esm-browser.js');
|
|
36
36
|
const createApp = mod?.createApp;
|
|
37
37
|
const ref = mod?.ref;
|
|
38
38
|
const h = mod?.h;
|
|
@@ -91,8 +91,7 @@ async function bootstrap() {
|
|
|
91
91
|
};
|
|
92
92
|
|
|
93
93
|
const app = createApp(Counter);
|
|
94
|
-
|
|
95
|
-
app.mount(mountTarget || ctx.element);
|
|
94
|
+
app.mount(target);
|
|
96
95
|
}
|
|
97
96
|
|
|
98
97
|
bootstrap().catch((error) => {
|
|
@@ -26,7 +26,7 @@ const snippet: SnippetModule = {
|
|
|
26
26
|
// Colorize based on numeric sign
|
|
27
27
|
const n = Number(ctx.value ?? 0);
|
|
28
28
|
const color = Number.isFinite(n) ? (n > 0 ? 'green' : n < 0 ? 'red' : '#999') : '#555';
|
|
29
|
-
ctx.
|
|
29
|
+
ctx.render('<span style=' + JSON.stringify('color:' + color) + '>' + String(ctx.value ?? '') + '</span>');
|
|
30
30
|
`,
|
|
31
31
|
};
|
|
32
32
|
|
|
@@ -23,10 +23,22 @@ const snippet: SnippetModule = {
|
|
|
23
23
|
},
|
|
24
24
|
content: `
|
|
25
25
|
const text = String(ctx.value ?? '');
|
|
26
|
-
ctx.element.innerHTML = '<a class="nb-copy" style="cursor:pointer;color:#1677ff">' +
|
|
27
|
-
ctx.t('Copy') + '</a>';
|
|
28
26
|
|
|
29
|
-
|
|
27
|
+
const wrapper = document.createElement('span');
|
|
28
|
+
wrapper.style.display = 'inline-flex';
|
|
29
|
+
wrapper.style.alignItems = 'center';
|
|
30
|
+
wrapper.style.gap = '8px';
|
|
31
|
+
|
|
32
|
+
const valueEl = document.createElement('span');
|
|
33
|
+
valueEl.textContent = text;
|
|
34
|
+
valueEl.style.color = '#666';
|
|
35
|
+
|
|
36
|
+
const copyEl = document.createElement('a');
|
|
37
|
+
copyEl.textContent = ctx.t('Copy');
|
|
38
|
+
copyEl.style.cursor = 'pointer';
|
|
39
|
+
copyEl.style.color = '#1677ff';
|
|
40
|
+
|
|
41
|
+
copyEl.addEventListener('click', async () => {
|
|
30
42
|
if (navigator?.clipboard?.writeText) {
|
|
31
43
|
await navigator.clipboard.writeText(text);
|
|
32
44
|
} else {
|
|
@@ -39,6 +51,11 @@ ctx.element.querySelector('.nb-copy')?.addEventListener('click', async () => {
|
|
|
39
51
|
}
|
|
40
52
|
ctx.message.success(ctx.t('Copied'));
|
|
41
53
|
});
|
|
54
|
+
|
|
55
|
+
wrapper.appendChild(valueEl);
|
|
56
|
+
wrapper.appendChild(copyEl);
|
|
57
|
+
|
|
58
|
+
ctx.render(wrapper);
|
|
42
59
|
`,
|
|
43
60
|
};
|
|
44
61
|
|