@arronqzy/vue-view 0.1.20 → 0.1.21
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 +25 -21
- package/package.json +7 -7
- package/src/index.ts +2 -0
- package/src/panel/VueViewPanel.vue +69 -1
- package/src/panel/components/scope-config/ScopeTemplateAutocompleteHost.vue +6 -9
- package/src/panel/components/scope-config/ScopeTemplateUsageHint.vue +1 -0
- package/src/panel/library/event-subscription.ts +20 -0
- package/src/panel/utils/scope-template.ts +20 -8
package/README.md
CHANGED
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
# @arronqzy/vue-view
|
|
2
2
|
|
|
3
|
-
Vue 3
|
|
3
|
+
Vue 3 视图编辑器,功能对齐 `@arronqzy/react-view`,组件用 Ant Design Vue。大多数 Vue 宿主应直接用 [`@arronqzy/abuilder-vue`](../abuilder-vue/README.md)。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+

|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
7
|
+
## 能力
|
|
8
|
+
|
|
9
|
+
- 无限平移 / 缩放(Infinite Viewer)、Moveable、Selecto 框选
|
|
10
|
+
- 图表、文本、表格、图片、音视频、几何、网格、三维、引用节点
|
|
11
|
+
- 多图层、映射图层、主图层、锁定与合并
|
|
12
|
+
- 按物料类型的配置侧栏,支持 Scope 模版
|
|
13
|
+
- IndexedDB 多项目、导入导出、跨标签同步
|
|
14
|
+
- 分屏蓝图(`@arronqzy/vue-blueprint`)和调试时的 Scope 回写
|
|
15
|
+
- `VueViewOnlinePreview` 只读预览
|
|
16
|
+
|
|
17
|
+
打开蓝图后的分屏和 React 版一样:上面视图,下面节点:
|
|
18
|
+
|
|
19
|
+

|
|
14
20
|
|
|
15
21
|
## 使用
|
|
16
22
|
|
|
@@ -29,7 +35,7 @@ createApp(VueViewPanel).use(Antd).mount("#app");
|
|
|
29
35
|
import { VueViewOnlinePreview, parseOnlinePreviewSearchParams } from "@arronqzy/vue-view";
|
|
30
36
|
```
|
|
31
37
|
|
|
32
|
-
URL
|
|
38
|
+
URL:`?preview=online&projectId=<id>&pid=<instanceId>`。传入 `workspace` 时优先于 `projectId`。
|
|
33
39
|
|
|
34
40
|
### 宿主集成
|
|
35
41
|
|
|
@@ -43,21 +49,19 @@ import {
|
|
|
43
49
|
} from "@arronqzy/vue-view";
|
|
44
50
|
```
|
|
45
51
|
|
|
46
|
-
创建 /
|
|
52
|
+
创建 / 同步发出 `workspace:add` / `workspace:sync`。`parseWorkspaceData` 的 `value` 可作 `initialWorkspace`。
|
|
47
53
|
|
|
48
|
-
|
|
54
|
+
IndexedDB CRUD 与 `getPreviewSnapshot` **不导出**。缩略图只在 React 包实现。
|
|
55
|
+
|
|
56
|
+
## 和 React 版的差别
|
|
49
57
|
|
|
50
|
-
-
|
|
51
|
-
-
|
|
52
|
-
- `@arronqzy/
|
|
53
|
-
- `@arronqzy/blueprint-dsl` — 蓝图 DSL
|
|
54
|
-
- `ant-design-vue`、`echarts`、`moveable`、`selecto`、`infinite-viewer`
|
|
58
|
+
- UI 是 Ant Design Vue
|
|
59
|
+
- 标尺是 canvas 实现(可换成 `@scena/ruler`)
|
|
60
|
+
- 画布状态仍是共享的 `@arronqzy/rx-store`
|
|
55
61
|
|
|
56
|
-
##
|
|
62
|
+
## 依赖
|
|
57
63
|
|
|
58
|
-
-
|
|
59
|
-
- 标尺使用 canvas 兼容实现(可替换为 `@scena/ruler`)
|
|
60
|
-
- 共享 `@arronqzy/rx-store` 与 `panel/utils` 数据逻辑
|
|
64
|
+
`@arronqzy/rx-store`、`@arronqzy/vue-rx-store`、`@arronqzy/vue-blueprint`、`@arronqzy/blueprint-dsl`、`ant-design-vue`、`echarts`、`moveable`、`selecto`、`infinite-viewer`。
|
|
61
65
|
|
|
62
66
|
## 许可证
|
|
63
67
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arronqzy/vue-view",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Vue 3 visual view editor for Abuilder (Ant Design Vue)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -23,12 +23,12 @@
|
|
|
23
23
|
"selecto": "^1.26.3",
|
|
24
24
|
"uuid": "^13.0.0",
|
|
25
25
|
"vue": "^3.5.13",
|
|
26
|
-
"@arronqzy/blueprint-dsl": "1.0.
|
|
27
|
-
"@arronqzy/i18n": "0.1.14",
|
|
28
|
-
"@arronqzy/view-table": "0.1.8",
|
|
26
|
+
"@arronqzy/blueprint-dsl": "1.0.16",
|
|
29
27
|
"@arronqzy/view-scene3d": "0.1.5",
|
|
30
|
-
"@arronqzy/
|
|
28
|
+
"@arronqzy/view-table": "0.1.8",
|
|
29
|
+
"@arronqzy/i18n": "0.1.16",
|
|
31
30
|
"@arronqzy/vue-rx-store": "0.1.6",
|
|
31
|
+
"@arronqzy/vue-blueprint": "0.1.16",
|
|
32
32
|
"@arronqzy/rx-store": "1.0.8"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"eslint": "^8.57.0",
|
|
40
40
|
"typescript": "^5.5.4",
|
|
41
41
|
"vue-tsc": "^2.2.0",
|
|
42
|
-
"@arronqzy/
|
|
43
|
-
"@arronqzy/
|
|
42
|
+
"@arronqzy/typescript-config": "1.0.0",
|
|
43
|
+
"@arronqzy/eslint-config": "0.0.0"
|
|
44
44
|
},
|
|
45
45
|
"publishConfig": {
|
|
46
46
|
"access": "public"
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,8 @@ export type {
|
|
|
22
22
|
AbuilderEventPayloadMap,
|
|
23
23
|
WorkspaceAddEventPayload,
|
|
24
24
|
WorkspaceSyncEventPayload,
|
|
25
|
+
ApiCollectionUploadStartEventPayload,
|
|
26
|
+
ApiCollectionUploadSuccessEventPayload,
|
|
25
27
|
WorkspaceData,
|
|
26
28
|
} from "./panel/library/event-subscription";
|
|
27
29
|
export type {
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { computed, onMounted, onUnmounted, ref, shallowRef, watch } from "vue";
|
|
3
3
|
import type { State } from "@arronqzy/rx-store";
|
|
4
4
|
import {
|
|
5
|
+
ApiCollectionManagerDialog,
|
|
5
6
|
BlueprintGraph,
|
|
6
7
|
abortClockNode,
|
|
7
8
|
blueprintDocumentsEqual,
|
|
@@ -12,6 +13,7 @@ import {
|
|
|
12
13
|
documentToRunnableGraph,
|
|
13
14
|
downloadBlueprintExport,
|
|
14
15
|
getBlueprintLibraryRecord,
|
|
16
|
+
hydrateApiCollections,
|
|
15
17
|
libraryRecordFromImport,
|
|
16
18
|
listBlueprintLibrary,
|
|
17
19
|
parseBlueprintImportFile,
|
|
@@ -27,7 +29,7 @@ import {
|
|
|
27
29
|
resolveRunnableNodeType,
|
|
28
30
|
resolveViewElementIds,
|
|
29
31
|
} from "@arronqzy/vue-blueprint";
|
|
30
|
-
import { collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE, type LibraryBlueprintResolver } from "@arronqzy/blueprint-dsl";
|
|
32
|
+
import { collectArmedViewEventBindings, EVENT_NODE_TYPE, LIFECYCLE_NODE_TYPE, buildApiCollectionSyncPatches, toApiCollectionSyncResult, type LibraryBlueprintResolver } from "@arronqzy/blueprint-dsl";
|
|
31
33
|
import {
|
|
32
34
|
Button,
|
|
33
35
|
Checkbox,
|
|
@@ -49,6 +51,7 @@ import {
|
|
|
49
51
|
useWorkspaceProjects,
|
|
50
52
|
} from "./hooks/useWorkspaceProjects";
|
|
51
53
|
import type { WorkspaceProjectRecord } from "./library/workspace-project-db";
|
|
54
|
+
import { AbuilderEvents, emitAbuilderEvent } from "./library/event-subscription";
|
|
52
55
|
import { type ViewportZoom } from "./viewportZoom";
|
|
53
56
|
import PanelCanvas from "./components/PanelCanvas.vue";
|
|
54
57
|
import ElementsLayer from "./components/ElementsLayer.vue";
|
|
@@ -158,6 +161,7 @@ const leftWidth = ref(240);
|
|
|
158
161
|
const rightWidth = ref(300);
|
|
159
162
|
const importInputRef = ref<HTMLInputElement | null>(null);
|
|
160
163
|
const blueprintImportInputRef = ref<HTMLInputElement | null>(null);
|
|
164
|
+
const apiCollectionManagerOpen = ref(false);
|
|
161
165
|
|
|
162
166
|
const viewportEl = ref<HTMLDivElement | null>(null);
|
|
163
167
|
const canvasEl = ref<HTMLDivElement | null>(null);
|
|
@@ -391,6 +395,16 @@ async function refreshBlueprintLibrary() {
|
|
|
391
395
|
blueprintLibraryItems.value = await listBlueprintLibrary(props.nameSpace);
|
|
392
396
|
}
|
|
393
397
|
|
|
398
|
+
watch(
|
|
399
|
+
() => props.nameSpace,
|
|
400
|
+
(nameSpace) => {
|
|
401
|
+
void hydrateApiCollections(nameSpace).catch((error) => {
|
|
402
|
+
console.error("[abuilder] hydrate api collections failed", error);
|
|
403
|
+
});
|
|
404
|
+
},
|
|
405
|
+
{ immediate: true }
|
|
406
|
+
);
|
|
407
|
+
|
|
394
408
|
onMounted(() => {
|
|
395
409
|
void refreshBlueprintLibrary();
|
|
396
410
|
});
|
|
@@ -604,6 +618,50 @@ function openBlueprintExport() {
|
|
|
604
618
|
});
|
|
605
619
|
}
|
|
606
620
|
|
|
621
|
+
function handleApiCollectionUploadStart(info: {
|
|
622
|
+
fileNames: string[];
|
|
623
|
+
count: number;
|
|
624
|
+
}) {
|
|
625
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadStart, info);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function handleApiCollectionUploadSuccess(info: {
|
|
629
|
+
collections: Array<{
|
|
630
|
+
id: string;
|
|
631
|
+
name: string;
|
|
632
|
+
apiCount: number;
|
|
633
|
+
persistLocal: boolean;
|
|
634
|
+
}>;
|
|
635
|
+
}) {
|
|
636
|
+
emitAbuilderEvent(AbuilderEvents.apiCollectionUploadSuccess, info);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
function handleApiCollectionSync(record: {
|
|
640
|
+
id: string;
|
|
641
|
+
document: import("@arronqzy/blueprint-dsl").ApiCollectionDocument;
|
|
642
|
+
}) {
|
|
643
|
+
const patches = buildApiCollectionSyncPatches({
|
|
644
|
+
collectionId: record.id,
|
|
645
|
+
document: record.document,
|
|
646
|
+
nodes: blueprintGraph.value.document.nodes
|
|
647
|
+
.filter((node) => node.role === "fetch" || Boolean(node.fetchConfig))
|
|
648
|
+
.map((node) => ({
|
|
649
|
+
id: node.id,
|
|
650
|
+
label: node.label,
|
|
651
|
+
fetchConfig: node.fetchConfig,
|
|
652
|
+
})),
|
|
653
|
+
});
|
|
654
|
+
if (patches.length === 0) return toApiCollectionSyncResult([]);
|
|
655
|
+
let next = blueprintGraph.value;
|
|
656
|
+
for (const patch of patches) {
|
|
657
|
+
next = next.updateNode(patch.nodeId, {
|
|
658
|
+
fetchConfig: patch.fetchConfigPatch as import("@arronqzy/blueprint-dsl").FetchRequestConfig,
|
|
659
|
+
});
|
|
660
|
+
}
|
|
661
|
+
blueprintGraph.value = next;
|
|
662
|
+
return toApiCollectionSyncResult(patches);
|
|
663
|
+
}
|
|
664
|
+
|
|
607
665
|
async function handleBlueprintImportFile(file: File) {
|
|
608
666
|
try {
|
|
609
667
|
await runBusyTask(t("common.importingBlueprint"), async () => {
|
|
@@ -757,6 +815,8 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
757
815
|
<Menu>
|
|
758
816
|
<Menu.Item @click="openBlueprintExport">{{ t("panel.menubar.exportBlueprint") }}</Menu.Item>
|
|
759
817
|
<Menu.Item @click="blueprintImportInputRef?.click()">{{ t("panel.menubar.importBlueprint") }}</Menu.Item>
|
|
818
|
+
<Menu.Divider />
|
|
819
|
+
<Menu.Item @click="apiCollectionManagerOpen = true">{{ t("panel.menubar.apiCollections") }}</Menu.Item>
|
|
760
820
|
</Menu>
|
|
761
821
|
</template>
|
|
762
822
|
</Dropdown>
|
|
@@ -1021,6 +1081,14 @@ onUnmounted(() => window.removeEventListener("keydown", onKeyDown));
|
|
|
1021
1081
|
<Input v-model:value="mergeLayerName" :placeholder="t('panel.layers.mergedNamePlaceholderOptional')" />
|
|
1022
1082
|
<div class="mt-2 text-xs text-gray-500">{{ t("panel.layers.mergeSelectedCount", { count: mergeSelectedCount }) }}</div>
|
|
1023
1083
|
</Modal>
|
|
1084
|
+
|
|
1085
|
+
<ApiCollectionManagerDialog
|
|
1086
|
+
:open="apiCollectionManagerOpen"
|
|
1087
|
+
:on-open-change="(open) => (apiCollectionManagerOpen = open)"
|
|
1088
|
+
:on-upload-start="handleApiCollectionUploadStart"
|
|
1089
|
+
:on-upload-success="handleApiCollectionUploadSuccess"
|
|
1090
|
+
:on-sync-collection="handleApiCollectionSync"
|
|
1091
|
+
/>
|
|
1024
1092
|
</Layout>
|
|
1025
1093
|
</template>
|
|
1026
1094
|
|
|
@@ -69,17 +69,13 @@ function closeDropdown() {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
function refreshForInput(input: HTMLInputElement | HTMLTextAreaElement) {
|
|
72
|
-
if (props.scope === undefined) {
|
|
73
|
-
closeDropdown();
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
72
|
const cursor = input.selectionStart ?? input.value.length;
|
|
77
|
-
const parsed = parseScopeAutocomplete(input.value, cursor, props.scope);
|
|
73
|
+
const parsed = parseScopeAutocomplete(input.value, cursor, props.scope ?? {});
|
|
78
74
|
if (!parsed) {
|
|
79
75
|
closeDropdown();
|
|
80
76
|
return;
|
|
81
77
|
}
|
|
82
|
-
const suggestions = getScopeAutocompleteSuggestions(props.scope, parsed);
|
|
78
|
+
const suggestions = getScopeAutocompleteSuggestions(props.scope ?? {}, parsed);
|
|
83
79
|
if (suggestions.length === 0) {
|
|
84
80
|
closeDropdown();
|
|
85
81
|
return;
|
|
@@ -173,7 +169,7 @@ function onPointerDown(event: MouseEvent) {
|
|
|
173
169
|
watch(
|
|
174
170
|
() => toValue(props.containerRef),
|
|
175
171
|
(container, _prev, onCleanup) => {
|
|
176
|
-
if (!container
|
|
172
|
+
if (!container) return;
|
|
177
173
|
|
|
178
174
|
container.addEventListener("focusin", onFocusIn);
|
|
179
175
|
container.addEventListener("input", onInput, true);
|
|
@@ -197,13 +193,14 @@ watch(
|
|
|
197
193
|
watch(
|
|
198
194
|
() => props.scope,
|
|
199
195
|
() => {
|
|
200
|
-
|
|
196
|
+
const current = dropdown.value;
|
|
197
|
+
if (current) refreshForInput(current.input);
|
|
201
198
|
}
|
|
202
199
|
);
|
|
203
200
|
</script>
|
|
204
201
|
|
|
205
202
|
<template>
|
|
206
|
-
<Teleport v-if="dropdown
|
|
203
|
+
<Teleport v-if="dropdown" to="body">
|
|
207
204
|
<div
|
|
208
205
|
data-scope-ac-list
|
|
209
206
|
class="fixed z-[10120] max-h-44 min-w-[140px] overflow-auto rounded-md border border-border bg-popover py-1 text-popover-foreground shadow-md"
|
|
@@ -5,6 +5,10 @@ export const AbuilderEvents = {
|
|
|
5
5
|
workspaceAdd: "workspace:add",
|
|
6
6
|
/** 同步工作区 */
|
|
7
7
|
workspaceSync: "workspace:sync",
|
|
8
|
+
/** 开始上传接口集合 */
|
|
9
|
+
apiCollectionUploadStart: "api-collection:upload-start",
|
|
10
|
+
/** 上传接口集合成功 */
|
|
11
|
+
apiCollectionUploadSuccess: "api-collection:upload-success",
|
|
8
12
|
} as const;
|
|
9
13
|
|
|
10
14
|
export type AbuilderEventName = (typeof AbuilderEvents)[keyof typeof AbuilderEvents];
|
|
@@ -15,9 +19,25 @@ export type WorkspaceData = WorkspaceProjectRecord;
|
|
|
15
19
|
export type WorkspaceAddEventPayload = WorkspaceData;
|
|
16
20
|
export type WorkspaceSyncEventPayload = WorkspaceData;
|
|
17
21
|
|
|
22
|
+
export type ApiCollectionUploadStartEventPayload = {
|
|
23
|
+
fileNames: string[];
|
|
24
|
+
count: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type ApiCollectionUploadSuccessEventPayload = {
|
|
28
|
+
collections: Array<{
|
|
29
|
+
id: string;
|
|
30
|
+
name: string;
|
|
31
|
+
apiCount: number;
|
|
32
|
+
persistLocal: boolean;
|
|
33
|
+
}>;
|
|
34
|
+
};
|
|
35
|
+
|
|
18
36
|
export type AbuilderEventPayloadMap = {
|
|
19
37
|
[AbuilderEvents.workspaceAdd]: WorkspaceAddEventPayload;
|
|
20
38
|
[AbuilderEvents.workspaceSync]: WorkspaceSyncEventPayload;
|
|
39
|
+
[AbuilderEvents.apiCollectionUploadStart]: ApiCollectionUploadStartEventPayload;
|
|
40
|
+
[AbuilderEvents.apiCollectionUploadSuccess]: ApiCollectionUploadSuccessEventPayload;
|
|
21
41
|
};
|
|
22
42
|
|
|
23
43
|
type EventCallback = (payload: unknown) => void;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { PanelElement } from "../types";
|
|
2
2
|
import type { PanelTableConfig } from "@arronqzy/view-table";
|
|
3
3
|
import { isUsableTableRawData } from "@arronqzy/view-table";
|
|
4
|
+
import {
|
|
5
|
+
createSystemRoot,
|
|
6
|
+
isDayjsValue,
|
|
7
|
+
} from "@arronqzy/blueprint-dsl";
|
|
4
8
|
import { materializeChartLabelsFromScope, materializeChartValuesFromScope } from "./scope-template-chart";
|
|
5
9
|
import {
|
|
6
10
|
SCOPE_SPREAD_TEMPLATE_RE,
|
|
@@ -11,12 +15,12 @@ import {
|
|
|
11
15
|
resolveSpreadScopePath,
|
|
12
16
|
} from "./scope-template-spread";
|
|
13
17
|
|
|
14
|
-
const
|
|
18
|
+
const TEMPLATE_TOKEN_RE = /\{((?:scope|system)[^}]*)\}/g;
|
|
15
19
|
|
|
16
20
|
export function hasScopeTemplate(value: string): boolean {
|
|
17
|
-
|
|
21
|
+
TEMPLATE_TOKEN_RE.lastIndex = 0;
|
|
18
22
|
SCOPE_SPREAD_TEMPLATE_RE.lastIndex = 0;
|
|
19
|
-
return
|
|
23
|
+
return TEMPLATE_TOKEN_RE.test(value) || SCOPE_SPREAD_TEMPLATE_RE.test(value);
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export function evaluateScopeExpression(
|
|
@@ -26,8 +30,13 @@ export function evaluateScopeExpression(
|
|
|
26
30
|
const trimmed = expression.trim();
|
|
27
31
|
if (!trimmed) return undefined;
|
|
28
32
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
33
|
+
const system = createSystemRoot();
|
|
34
|
+
const fn = new Function(
|
|
35
|
+
"scope",
|
|
36
|
+
"system",
|
|
37
|
+
`"use strict"; return (${trimmed});`
|
|
38
|
+
);
|
|
39
|
+
return fn(scope, system);
|
|
31
40
|
} catch {
|
|
32
41
|
return undefined;
|
|
33
42
|
}
|
|
@@ -39,6 +48,9 @@ function stringifyScopeValue(value: unknown): string {
|
|
|
39
48
|
if (typeof value === "number" || typeof value === "boolean") {
|
|
40
49
|
return String(value);
|
|
41
50
|
}
|
|
51
|
+
if (isDayjsValue(value)) {
|
|
52
|
+
return value.toISOString();
|
|
53
|
+
}
|
|
42
54
|
try {
|
|
43
55
|
return JSON.stringify(value);
|
|
44
56
|
} catch {
|
|
@@ -60,8 +72,8 @@ export function evaluateScopeTemplate(
|
|
|
60
72
|
}
|
|
61
73
|
);
|
|
62
74
|
|
|
63
|
-
// Only `{scope...}`
|
|
64
|
-
return withSpread.replace(
|
|
75
|
+
// Only `{scope...}` / `{system...}` — keep ECharts / CSS braces like `{b}` intact.
|
|
76
|
+
return withSpread.replace(TEMPLATE_TOKEN_RE, (_: string, rawExpr: string) =>
|
|
65
77
|
stringifyScopeValue(evaluateScopeExpression(rawExpr, scope))
|
|
66
78
|
);
|
|
67
79
|
}
|
|
@@ -91,7 +103,7 @@ function materializeTemplateValue(template: string, scope: unknown): unknown {
|
|
|
91
103
|
);
|
|
92
104
|
}
|
|
93
105
|
|
|
94
|
-
const singleMatch = /^\{(scope[^}]*)\}$/.exec(trimmed);
|
|
106
|
+
const singleMatch = /^\{((?:scope|system)[^}]*)\}$/.exec(trimmed);
|
|
95
107
|
if (singleMatch) {
|
|
96
108
|
return evaluateScopeExpression(singleMatch[1]!, scope);
|
|
97
109
|
}
|