@nocobase/client-v2 2.2.0-beta.1 → 2.2.0-beta.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/es/BaseApplication.d.ts +3 -0
- package/es/PluginSettingsManager.d.ts +33 -0
- package/es/RouteRepository.d.ts +29 -6
- package/es/RouterManager.d.ts +15 -0
- package/es/authRedirect.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +1 -0
- package/es/entry-actions/EntryActionManager.d.ts +24 -0
- package/es/entry-actions/index.d.ts +9 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
- package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +5 -0
- package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
- package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/models/base/ActionModelCore.d.ts +2 -0
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/form/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
- package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
- package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
- package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
- package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
- package/es/flow/routeTransientInputArgs.d.ts +14 -0
- package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
- package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
- package/es/flow/utils/formValueDeps.d.ts +32 -0
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/index.d.ts +4 -1
- package/es/index.mjs +305 -157
- package/es/utils/markdownSanitize.d.ts +11 -0
- package/lib/index.js +305 -157
- package/package.json +7 -7
- package/src/BaseApplication.tsx +6 -0
- package/src/PluginSettingsManager.ts +53 -0
- package/src/RouteRepository.ts +151 -24
- package/src/RouterManager.tsx +142 -1
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/RouteRepository.test.ts +239 -0
- package/src/__tests__/RouterManager.test.ts +125 -0
- package/src/__tests__/authRedirect.test.ts +17 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +40 -1
- package/src/authRedirect.ts +38 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-configure.ts +1 -1
- package/src/collection-manager/interfaces/id.ts +1 -1
- package/src/collection-manager/interfaces/m2m.tsx +2 -2
- package/src/collection-manager/interfaces/m2o.tsx +2 -2
- package/src/collection-manager/interfaces/nanoid.ts +1 -1
- package/src/collection-manager/interfaces/o2m.tsx +2 -2
- package/src/collection-manager/interfaces/obo.tsx +2 -2
- package/src/collection-manager/interfaces/oho.tsx +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +2 -2
- package/src/collection-manager/interfaces/uuid.ts +1 -1
- package/src/components/AppComponents.tsx +19 -3
- package/src/components/form/JsonTextArea.tsx +21 -11
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +1 -0
- package/src/entry-actions/EntryActionManager.ts +76 -0
- package/src/entry-actions/index.ts +10 -0
- package/src/flow/FlowPage.tsx +38 -3
- package/src/flow/__tests__/FlowPage.test.tsx +201 -27
- package/src/flow/__tests__/FlowRoute.test.tsx +1088 -105
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +120 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
- package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +164 -0
- package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/dataScopeFilter.ts +20 -1
- package/src/flow/actions/dateTimeFormat.tsx +42 -28
- package/src/flow/actions/index.ts +1 -1
- package/src/flow/actions/linkageRules.tsx +123 -35
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
- package/src/flow/actions/openView.tsx +59 -5
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/BaseLayoutModel.tsx +149 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +187 -42
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1020 -110
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +74 -13
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +486 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +64 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +23 -9
- package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
- package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +518 -2
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +214 -2
- package/src/flow/admin-shell/admin-layout/index.ts +1 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
- package/src/flow/common/Markdown/Display.tsx +14 -3
- package/src/flow/common/Markdown/Edit.tsx +19 -7
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FieldAssignValueInput.tsx +15 -11
- package/src/flow/components/FlowRoute.tsx +186 -25
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +134 -0
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/components/fieldAssignOptions.ts +155 -27
- package/src/flow/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +1 -1
- package/src/flow/internal/components/Markdown/util.ts +2 -1
- package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +21 -8
- package/src/flow/models/base/ActionModelCore.tsx +11 -4
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +37 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +32 -3
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +111 -0
- package/src/flow/models/blocks/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
- package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +36 -13
- package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
- package/src/flow/models/blocks/table/TableColumnModel.tsx +42 -5
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +210 -1
- package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
- package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
- package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
- package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +30 -6
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +61 -17
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
- package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
- package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
- package/src/flow/models/fields/InputFieldModel.tsx +40 -2
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
- package/src/flow/models/fields/TextareaFieldModel.tsx +72 -3
- package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
- package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +131 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
- package/src/flow/models/topbar/TopbarActionModel.tsx +168 -10
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/flow/routeTransientInputArgs.ts +27 -0
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +300 -0
- package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
- package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
- package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
- package/src/flow/utils/formValueDeps.ts +170 -0
- package/src/flow-compat/FieldValidation.tsx +122 -60
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/index.ts +10 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/nocobase-buildin-plugin/index.tsx +51 -15
- package/src/settings-center/AdminSettingsLayout.tsx +10 -2
- package/src/settings-center/SystemSettingsPage.tsx +1 -2
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/plugin-manager/index.tsx +3 -0
- package/src/settings-center/utils.tsx +0 -6
- package/src/utils/markdownSanitize.ts +88 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "2.2.0-beta.
|
|
3
|
+
"version": "2.2.0-beta.10",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"@formily/antd-v5": "1.2.3",
|
|
28
28
|
"@formily/react": "^2.2.27",
|
|
29
29
|
"@formily/shared": "^2.2.27",
|
|
30
|
-
"@nocobase/evaluators": "2.2.0-beta.
|
|
31
|
-
"@nocobase/flow-engine": "2.2.0-beta.
|
|
32
|
-
"@nocobase/sdk": "2.2.0-beta.
|
|
33
|
-
"@nocobase/shared": "2.2.0-beta.
|
|
34
|
-
"@nocobase/utils": "2.2.0-beta.
|
|
30
|
+
"@nocobase/evaluators": "2.2.0-beta.10",
|
|
31
|
+
"@nocobase/flow-engine": "2.2.0-beta.10",
|
|
32
|
+
"@nocobase/sdk": "2.2.0-beta.10",
|
|
33
|
+
"@nocobase/shared": "2.2.0-beta.10",
|
|
34
|
+
"@nocobase/utils": "2.2.0-beta.10",
|
|
35
35
|
"ahooks": "^3.7.2",
|
|
36
36
|
"antd": "5.24.2",
|
|
37
37
|
"antd-style": "3.7.1",
|
|
@@ -46,5 +46,5 @@
|
|
|
46
46
|
"react-i18next": "^11.15.1",
|
|
47
47
|
"react-router-dom": "^6.30.1"
|
|
48
48
|
},
|
|
49
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "d572beec24de46df948f28db72b25303a63bf62d"
|
|
50
50
|
}
|
package/src/BaseApplication.tsx
CHANGED
|
@@ -30,6 +30,7 @@ import AntdAppProvider from './theme/AntdAppProvider';
|
|
|
30
30
|
import { GlobalThemeProvider } from './theme';
|
|
31
31
|
import { AIManager } from './ai';
|
|
32
32
|
import { AppError, AppMaintaining, AppMaintainingDialog, AppNotFound, AppSpin, BlankComponent } from './components';
|
|
33
|
+
import { EntryActionManager } from './entry-actions';
|
|
33
34
|
import { SystemSettingsSource } from './flow/system-settings';
|
|
34
35
|
import { LayoutManager } from './layout-manager/LayoutManager';
|
|
35
36
|
import type { PluginClass, PluginManager, PluginType } from './PluginManager';
|
|
@@ -126,6 +127,7 @@ export abstract class BaseApplication<
|
|
|
126
127
|
public pluginManager: TPluginManager;
|
|
127
128
|
public pluginSettingsManager: TPluginSettingsManager;
|
|
128
129
|
public layoutManager: LayoutManager<this>;
|
|
130
|
+
public entryActionManager = new EntryActionManager();
|
|
129
131
|
public aiManager!: AIManager;
|
|
130
132
|
public devDynamicImport?: DevDynamicImport;
|
|
131
133
|
public requirejs!: RequireJS;
|
|
@@ -431,6 +433,10 @@ export abstract class BaseApplication<
|
|
|
431
433
|
return getSubAppName(this.getPublicPath()) || null;
|
|
432
434
|
}
|
|
433
435
|
|
|
436
|
+
getCdnUrl() {
|
|
437
|
+
return window['__webpack_public_path__'] || this.getPublicPath();
|
|
438
|
+
}
|
|
439
|
+
|
|
434
440
|
getPublicPath() {
|
|
435
441
|
let publicPath = this.options.publicPath || '/';
|
|
436
442
|
if (!publicPath.endsWith('/')) {
|
|
@@ -97,6 +97,7 @@ interface InternalPageItemRecord extends PluginSettingsPageItemOptions {
|
|
|
97
97
|
export class PluginSettingsManager<TApp extends BaseApplication<any> = BaseApplication<any>> {
|
|
98
98
|
protected menus: Record<string, InternalMenuItemRecord> = {};
|
|
99
99
|
protected pages: Record<string, InternalPageItemRecord> = {};
|
|
100
|
+
protected pluginSettingsLinks: Record<string, string> = {};
|
|
100
101
|
protected aclSnippets: string[] = [];
|
|
101
102
|
public app: TApp;
|
|
102
103
|
|
|
@@ -294,6 +295,58 @@ export class PluginSettingsManager<TApp extends BaseApplication<any> = BaseAppli
|
|
|
294
295
|
return !!(this.menus[name] || this.pages[name]);
|
|
295
296
|
}
|
|
296
297
|
|
|
298
|
+
/**
|
|
299
|
+
* 绑定插件管理列表中的 plugin name 到实际 settings 入口。
|
|
300
|
+
*
|
|
301
|
+
* 插件管理列表使用 applicationPlugins.name 判断是否显示 Settings 入口,
|
|
302
|
+
* 但部分插件会把设置页挂到共享菜单或历史路径下。
|
|
303
|
+
*
|
|
304
|
+
* @param pluginName 插件管理列表中的插件名
|
|
305
|
+
* @param settingsName menu 或 page 名称
|
|
306
|
+
* @returns {void}
|
|
307
|
+
*/
|
|
308
|
+
setPluginSettingsLink(pluginName: string, settingsName: string) {
|
|
309
|
+
if (!pluginName) {
|
|
310
|
+
throw new Error('Plugin settings link pluginName is required');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (!settingsName) {
|
|
314
|
+
throw new Error('Plugin settings link settingsName is required');
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
this.pluginSettingsLinks[pluginName] = settingsName;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 获取插件管理列表中的 plugin name 对应的 settings 名称。
|
|
322
|
+
*
|
|
323
|
+
* @param pluginName 插件管理列表中的插件名
|
|
324
|
+
* @returns {string} settings 名称
|
|
325
|
+
*/
|
|
326
|
+
getPluginSettingsName(pluginName: string) {
|
|
327
|
+
return this.pluginSettingsLinks[pluginName] || pluginName;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* 判断插件管理列表中的插件是否有可跳转的 settings 入口。
|
|
332
|
+
*
|
|
333
|
+
* @param pluginName 插件管理列表中的插件名
|
|
334
|
+
* @returns {boolean} 是否已注册 settings 入口
|
|
335
|
+
*/
|
|
336
|
+
hasPluginSettings(pluginName: string) {
|
|
337
|
+
return this.has(this.getPluginSettingsName(pluginName));
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* 获取插件管理列表中的插件对应的 settings 路径。
|
|
342
|
+
*
|
|
343
|
+
* @param pluginName 插件管理列表中的插件名
|
|
344
|
+
* @returns {string} settings 绝对路径
|
|
345
|
+
*/
|
|
346
|
+
getPluginSettingsRoutePath(pluginName: string) {
|
|
347
|
+
return this.getRoutePath(this.getPluginSettingsName(pluginName));
|
|
348
|
+
}
|
|
349
|
+
|
|
297
350
|
/**
|
|
298
351
|
* 获取单个 menu 或 page 的只读快照。
|
|
299
352
|
*
|
package/src/RouteRepository.ts
CHANGED
|
@@ -9,15 +9,24 @@
|
|
|
9
9
|
|
|
10
10
|
import { APIClient } from '@nocobase/sdk';
|
|
11
11
|
import type { NocoBaseDesktopRoute } from './flow-compat';
|
|
12
|
+
import { ADMIN_LAYOUT_MODEL_UID } from './flow/admin-shell/admin-layout/constants';
|
|
12
13
|
|
|
13
14
|
type RouteSubscriber = () => void;
|
|
14
15
|
type RouteCreateValues = Partial<NocoBaseDesktopRoute>;
|
|
15
16
|
type RouteUpdateValues = Partial<NocoBaseDesktopRoute>;
|
|
17
|
+
type RouteLayoutLike = {
|
|
18
|
+
uid?: unknown;
|
|
19
|
+
};
|
|
16
20
|
|
|
17
21
|
type RouteMutationOptions = {
|
|
18
22
|
refreshAfterMutation?: boolean;
|
|
19
23
|
};
|
|
20
24
|
|
|
25
|
+
type AccessibleRouteCache = {
|
|
26
|
+
accessibleLoaded: boolean;
|
|
27
|
+
routes: NocoBaseDesktopRoute[];
|
|
28
|
+
};
|
|
29
|
+
|
|
21
30
|
type MoveRouteOptions = {
|
|
22
31
|
sourceId: string | number;
|
|
23
32
|
targetId?: string | number;
|
|
@@ -30,21 +39,47 @@ type MoveRouteOptions = {
|
|
|
30
39
|
|
|
31
40
|
export class RouteRepository {
|
|
32
41
|
routes: NocoBaseDesktopRoute[] = [];
|
|
33
|
-
protected subscribers = new
|
|
34
|
-
protected
|
|
35
|
-
|
|
42
|
+
protected subscribers = new Map<RouteSubscriber, string>();
|
|
43
|
+
protected accessibleLoadingPromises = new Map<string, Promise<NocoBaseDesktopRoute[]>>();
|
|
44
|
+
private layoutActivations: Array<{ token: symbol; uid: string }> = [];
|
|
45
|
+
private routeCaches = new Map<string, AccessibleRouteCache>();
|
|
46
|
+
private refreshRequestIds = new Map<string, number>();
|
|
47
|
+
private notifiedLayoutUid: string | undefined;
|
|
36
48
|
|
|
37
49
|
constructor(protected ctx: { api?: APIClient }) {}
|
|
38
50
|
|
|
51
|
+
activateLayout(layout?: RouteLayoutLike) {
|
|
52
|
+
const uid = this.normalizeLayoutUid(layout?.uid);
|
|
53
|
+
const token = Symbol('route-layout');
|
|
54
|
+
|
|
55
|
+
this.layoutActivations.push({
|
|
56
|
+
token,
|
|
57
|
+
uid,
|
|
58
|
+
});
|
|
59
|
+
this.syncRoutesProperty();
|
|
60
|
+
|
|
61
|
+
return () => {
|
|
62
|
+
const index = this.layoutActivations.findIndex((item) => item.token === token);
|
|
63
|
+
if (index >= 0) {
|
|
64
|
+
this.layoutActivations.splice(index, 1);
|
|
65
|
+
}
|
|
66
|
+
this.syncRoutesProperty();
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
39
70
|
/**
|
|
40
71
|
* 同步当前可访问桌面路由,并通知订阅方刷新。
|
|
41
72
|
*
|
|
42
73
|
* @param routes 最新的桌面路由树
|
|
43
74
|
*/
|
|
44
|
-
setRoutes(routes: NocoBaseDesktopRoute[]) {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
75
|
+
setRoutes(routes: NocoBaseDesktopRoute[], layoutUid = this.getCurrentLayoutUid()) {
|
|
76
|
+
const cache = this.getRouteCache(layoutUid);
|
|
77
|
+
cache.routes = routes;
|
|
78
|
+
cache.accessibleLoaded = true;
|
|
79
|
+
if (layoutUid === this.getCurrentLayoutUid()) {
|
|
80
|
+
this.routes = routes;
|
|
81
|
+
}
|
|
82
|
+
this.emitChange(layoutUid);
|
|
48
83
|
}
|
|
49
84
|
|
|
50
85
|
/**
|
|
@@ -53,7 +88,9 @@ export class RouteRepository {
|
|
|
53
88
|
* @returns 当前缓存的路由数组
|
|
54
89
|
*/
|
|
55
90
|
listAccessible() {
|
|
56
|
-
|
|
91
|
+
const routes = this.getRouteCache(this.getReadableLayoutUid()).routes;
|
|
92
|
+
this.routes = routes;
|
|
93
|
+
return routes;
|
|
57
94
|
}
|
|
58
95
|
|
|
59
96
|
/**
|
|
@@ -62,7 +99,7 @@ export class RouteRepository {
|
|
|
62
99
|
* @returns {boolean} 是否已初始化完成
|
|
63
100
|
*/
|
|
64
101
|
isAccessibleLoaded() {
|
|
65
|
-
return this.accessibleLoaded;
|
|
102
|
+
return this.getRouteCache().accessibleLoaded;
|
|
66
103
|
}
|
|
67
104
|
|
|
68
105
|
/**
|
|
@@ -71,12 +108,20 @@ export class RouteRepository {
|
|
|
71
108
|
* @returns 最新的路由数组
|
|
72
109
|
*/
|
|
73
110
|
async refreshAccessible() {
|
|
111
|
+
const layoutUid = this.getCurrentLayoutUid();
|
|
112
|
+
return this.refreshAccessibleByLayout(layoutUid);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private async refreshAccessibleByLayout(layoutUid: string) {
|
|
116
|
+
const requestId = this.nextRefreshRequestId(layoutUid);
|
|
74
117
|
const response = await this.getAPIClient().request({
|
|
75
118
|
url: '/desktopRoutes:listAccessible',
|
|
76
|
-
params: { tree: true, sort: 'sort' },
|
|
119
|
+
params: { tree: true, sort: 'sort', layout: layoutUid },
|
|
77
120
|
});
|
|
78
121
|
const routes = response?.data?.data || [];
|
|
79
|
-
this.
|
|
122
|
+
if (requestId === this.getRefreshRequestId(layoutUid)) {
|
|
123
|
+
this.setRoutes(routes, layoutUid);
|
|
124
|
+
}
|
|
80
125
|
return routes;
|
|
81
126
|
}
|
|
82
127
|
|
|
@@ -86,19 +131,26 @@ export class RouteRepository {
|
|
|
86
131
|
* @returns 当前可访问的路由数组
|
|
87
132
|
*/
|
|
88
133
|
async ensureAccessibleLoaded() {
|
|
89
|
-
|
|
90
|
-
|
|
134
|
+
const layoutUid = this.getCurrentLayoutUid();
|
|
135
|
+
const cache = this.getRouteCache(layoutUid);
|
|
136
|
+
if (cache.accessibleLoaded) {
|
|
137
|
+
this.routes = cache.routes;
|
|
138
|
+
return cache.routes;
|
|
91
139
|
}
|
|
92
140
|
|
|
93
|
-
|
|
94
|
-
|
|
141
|
+
const existingLoadingPromise = this.accessibleLoadingPromises.get(layoutUid);
|
|
142
|
+
if (existingLoadingPromise) {
|
|
143
|
+
return existingLoadingPromise;
|
|
95
144
|
}
|
|
96
145
|
|
|
97
|
-
|
|
98
|
-
this.
|
|
146
|
+
const loadingPromise = this.refreshAccessibleByLayout(layoutUid).finally(() => {
|
|
147
|
+
if (this.accessibleLoadingPromises.get(layoutUid) === loadingPromise) {
|
|
148
|
+
this.accessibleLoadingPromises.delete(layoutUid);
|
|
149
|
+
}
|
|
99
150
|
});
|
|
151
|
+
this.accessibleLoadingPromises.set(layoutUid, loadingPromise);
|
|
100
152
|
|
|
101
|
-
return
|
|
153
|
+
return loadingPromise;
|
|
102
154
|
}
|
|
103
155
|
|
|
104
156
|
/**
|
|
@@ -108,7 +160,7 @@ export class RouteRepository {
|
|
|
108
160
|
* @returns 取消订阅函数
|
|
109
161
|
*/
|
|
110
162
|
subscribe(subscriber: RouteSubscriber) {
|
|
111
|
-
this.subscribers.
|
|
163
|
+
this.subscribers.set(subscriber, this.getCurrentLayoutUid());
|
|
112
164
|
return () => {
|
|
113
165
|
this.subscribers.delete(subscriber);
|
|
114
166
|
};
|
|
@@ -132,7 +184,10 @@ export class RouteRepository {
|
|
|
132
184
|
*/
|
|
133
185
|
async createRoute(values: RouteCreateValues, options: RouteMutationOptions = {}) {
|
|
134
186
|
const { refreshAfterMutation = true } = options;
|
|
135
|
-
const res = await this.getResource('desktopRoutes').create({
|
|
187
|
+
const res = await this.getResource('desktopRoutes').create({
|
|
188
|
+
values,
|
|
189
|
+
layout: this.getCurrentLayoutUid(),
|
|
190
|
+
});
|
|
136
191
|
if (refreshAfterMutation) {
|
|
137
192
|
await this.refreshAccessible();
|
|
138
193
|
}
|
|
@@ -210,7 +265,17 @@ export class RouteRepository {
|
|
|
210
265
|
* @returns 匹配到的路由节点
|
|
211
266
|
*/
|
|
212
267
|
getRouteBySchemaUid(schemaUid: string): NocoBaseDesktopRoute | undefined {
|
|
213
|
-
return this.findRoute(this.
|
|
268
|
+
return this.findRoute(this.listAccessible(), schemaUid);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* 通过路由 id 反查对应路由节点。
|
|
273
|
+
*
|
|
274
|
+
* @param routeId 桌面路由主键
|
|
275
|
+
* @returns 匹配到的路由节点
|
|
276
|
+
*/
|
|
277
|
+
getRouteById(routeId: string | number): NocoBaseDesktopRoute | undefined {
|
|
278
|
+
return this.findRouteById(this.listAccessible(), routeId);
|
|
214
279
|
}
|
|
215
280
|
|
|
216
281
|
protected getAPIClient(): APIClient {
|
|
@@ -224,12 +289,59 @@ export class RouteRepository {
|
|
|
224
289
|
return this.getAPIClient().resource(collectionName);
|
|
225
290
|
}
|
|
226
291
|
|
|
227
|
-
protected emitChange() {
|
|
228
|
-
this.subscribers.forEach((subscriber) => {
|
|
229
|
-
|
|
292
|
+
protected emitChange(layoutUid = this.getCurrentLayoutUid()) {
|
|
293
|
+
this.subscribers.forEach((subscriberLayoutUid, subscriber) => {
|
|
294
|
+
if (subscriberLayoutUid !== layoutUid) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const previousNotifiedLayoutUid = this.notifiedLayoutUid;
|
|
298
|
+
this.notifiedLayoutUid = layoutUid;
|
|
299
|
+
try {
|
|
300
|
+
subscriber();
|
|
301
|
+
} finally {
|
|
302
|
+
this.notifiedLayoutUid = previousNotifiedLayoutUid;
|
|
303
|
+
}
|
|
230
304
|
});
|
|
231
305
|
}
|
|
232
306
|
|
|
307
|
+
private getCurrentLayoutUid() {
|
|
308
|
+
return this.layoutActivations[this.layoutActivations.length - 1]?.uid || ADMIN_LAYOUT_MODEL_UID;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
private normalizeLayoutUid(uid: unknown) {
|
|
312
|
+
return typeof uid === 'string' && uid.trim() ? uid : ADMIN_LAYOUT_MODEL_UID;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
private getReadableLayoutUid() {
|
|
316
|
+
return this.notifiedLayoutUid || this.getCurrentLayoutUid();
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
private getRouteCache(layoutUid = this.getCurrentLayoutUid()) {
|
|
320
|
+
let cache = this.routeCaches.get(layoutUid);
|
|
321
|
+
if (!cache) {
|
|
322
|
+
cache = {
|
|
323
|
+
accessibleLoaded: false,
|
|
324
|
+
routes: [],
|
|
325
|
+
};
|
|
326
|
+
this.routeCaches.set(layoutUid, cache);
|
|
327
|
+
}
|
|
328
|
+
return cache;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private syncRoutesProperty() {
|
|
332
|
+
this.routes = this.getRouteCache().routes;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
private nextRefreshRequestId(layoutUid: string) {
|
|
336
|
+
const requestId = this.getRefreshRequestId(layoutUid) + 1;
|
|
337
|
+
this.refreshRequestIds.set(layoutUid, requestId);
|
|
338
|
+
return requestId;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
private getRefreshRequestId(layoutUid: string) {
|
|
342
|
+
return this.refreshRequestIds.get(layoutUid) || 0;
|
|
343
|
+
}
|
|
344
|
+
|
|
233
345
|
private findRoute(routes: NocoBaseDesktopRoute[], schemaUid: string): NocoBaseDesktopRoute | undefined {
|
|
234
346
|
for (const route of routes) {
|
|
235
347
|
if (route.schemaUid === schemaUid) {
|
|
@@ -244,4 +356,19 @@ export class RouteRepository {
|
|
|
244
356
|
}
|
|
245
357
|
return undefined;
|
|
246
358
|
}
|
|
359
|
+
|
|
360
|
+
private findRouteById(routes: NocoBaseDesktopRoute[], routeId: string | number): NocoBaseDesktopRoute | undefined {
|
|
361
|
+
for (const route of routes) {
|
|
362
|
+
if (route.id != null && String(route.id) === String(routeId)) {
|
|
363
|
+
return route;
|
|
364
|
+
}
|
|
365
|
+
if (route.children) {
|
|
366
|
+
const found = this.findRouteById(route.children, routeId);
|
|
367
|
+
if (found) {
|
|
368
|
+
return found;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
247
374
|
}
|
package/src/RouterManager.tsx
CHANGED
|
@@ -26,6 +26,7 @@ import type { BaseApplication } from './BaseApplication';
|
|
|
26
26
|
import { BlankComponent, RouterContextCleaner } from './components';
|
|
27
27
|
import { RouterBridge } from './components/RouterBridge';
|
|
28
28
|
import { Router } from '@remix-run/router';
|
|
29
|
+
import { getV2EffectiveBasePath } from './authRedirect';
|
|
29
30
|
|
|
30
31
|
export interface BrowserRouterOptions extends Omit<BrowserRouterProps, 'children'> {
|
|
31
32
|
type?: 'browser';
|
|
@@ -55,6 +56,87 @@ export interface RouteType extends Omit<RouteObject, 'children' | 'Component'> {
|
|
|
55
56
|
export type RenderComponentType = (Component: ComponentTypeAndString, props?: any) => React.ReactNode;
|
|
56
57
|
export type RouterComponentType = React.FC<{ BaseLayout?: ComponentType }>;
|
|
57
58
|
|
|
59
|
+
const DEFAULT_ADMIN_ROUTE_PATH = '/admin';
|
|
60
|
+
|
|
61
|
+
type AdminRouteNavigationTarget = {
|
|
62
|
+
currentPathname?: string;
|
|
63
|
+
targetPathname: unknown;
|
|
64
|
+
basePath?: string;
|
|
65
|
+
adminRoutePath?: string;
|
|
66
|
+
replace?: boolean;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function trimPathSearchAndHash(pathname: string) {
|
|
70
|
+
return pathname.split(/[?#]/)[0];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function splitPathSearchAndHash(pathname: string) {
|
|
74
|
+
const match = pathname.match(/^([^?#]*)(.*)$/);
|
|
75
|
+
return {
|
|
76
|
+
pathname: match?.[1] || '',
|
|
77
|
+
suffix: match?.[2] || '',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function normalizeRootPath(pathname?: string) {
|
|
82
|
+
const trimmed = trimPathSearchAndHash(pathname || '').trim();
|
|
83
|
+
if (!trimmed || trimmed === '/') {
|
|
84
|
+
return '/';
|
|
85
|
+
}
|
|
86
|
+
return `/${trimmed.replace(/^\/+|\/+$/g, '')}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function isRootRelativePath(pathname: string) {
|
|
90
|
+
return pathname.startsWith('/') && !pathname.startsWith('//') && !pathname.startsWith('/\\');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function removeBasePath(pathname: string, basePath?: string) {
|
|
94
|
+
const normalizedPathname = normalizeRootPath(pathname);
|
|
95
|
+
const normalizedBasePath = normalizeRootPath(basePath);
|
|
96
|
+
|
|
97
|
+
if (normalizedBasePath === '/') {
|
|
98
|
+
return normalizedPathname;
|
|
99
|
+
}
|
|
100
|
+
if (normalizedPathname === normalizedBasePath) {
|
|
101
|
+
return '/';
|
|
102
|
+
}
|
|
103
|
+
if (normalizedPathname.startsWith(`${normalizedBasePath}/`)) {
|
|
104
|
+
return normalizeRootPath(normalizedPathname.slice(normalizedBasePath.length));
|
|
105
|
+
}
|
|
106
|
+
return normalizedPathname;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function removeAppSegment(pathname: string) {
|
|
110
|
+
return normalizeRootPath(pathname).replace(/^\/(?:apps|_app)\/[^/]+(?=\/|$)/, '') || '/';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function normalizePortalRoutePath(pathname: string, basePath?: string) {
|
|
114
|
+
return removeAppSegment(removeBasePath(pathname, basePath));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function isSameOrChildPath(pathname: string, basePath: string) {
|
|
118
|
+
const normalizedPathname = normalizeRootPath(pathname);
|
|
119
|
+
const normalizedBasePath = normalizeRootPath(basePath);
|
|
120
|
+
return normalizedPathname === normalizedBasePath || normalizedPathname.startsWith(`${normalizedBasePath}/`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function shouldOpenAdminRouteInNewWindow(options: AdminRouteNavigationTarget) {
|
|
124
|
+
if (options.replace || typeof options.targetPathname !== 'string') {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const targetPathname = options.targetPathname.trim();
|
|
129
|
+
if (!isRootRelativePath(targetPathname)) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const adminRoutePath = normalizeRootPath(options.adminRoutePath || DEFAULT_ADMIN_ROUTE_PATH);
|
|
134
|
+
const currentRoutePath = normalizePortalRoutePath(options.currentPathname || '/', options.basePath);
|
|
135
|
+
const targetRoutePath = normalizePortalRoutePath(targetPathname, options.basePath);
|
|
136
|
+
|
|
137
|
+
return !isSameOrChildPath(currentRoutePath, adminRoutePath) && isSameOrChildPath(targetRoutePath, adminRoutePath);
|
|
138
|
+
}
|
|
139
|
+
|
|
58
140
|
function removeBasename(pathname: string, basename?: string) {
|
|
59
141
|
if (!basename || basename === '/') {
|
|
60
142
|
return pathname;
|
|
@@ -72,6 +154,7 @@ function removeBasename(pathname: string, basename?: string) {
|
|
|
72
154
|
export class RouterManager<TApp extends BaseApplication<any> = BaseApplication<any>> {
|
|
73
155
|
protected routes: Record<string, RouteType> = {};
|
|
74
156
|
protected options: RouterOptions;
|
|
157
|
+
private routerNavigate?: Router['navigate'];
|
|
75
158
|
public app: TApp;
|
|
76
159
|
public router!: Router;
|
|
77
160
|
get basename() {
|
|
@@ -81,7 +164,7 @@ export class RouterManager<TApp extends BaseApplication<any> = BaseApplication<a
|
|
|
81
164
|
return this.router.state;
|
|
82
165
|
}
|
|
83
166
|
get navigate() {
|
|
84
|
-
return this.
|
|
167
|
+
return this.navigateWithPortalPolicy;
|
|
85
168
|
}
|
|
86
169
|
|
|
87
170
|
constructor(options: RouterOptions = {}, app: TApp) {
|
|
@@ -90,6 +173,62 @@ export class RouterManager<TApp extends BaseApplication<any> = BaseApplication<a
|
|
|
90
173
|
this.routes = options.routes || {};
|
|
91
174
|
}
|
|
92
175
|
|
|
176
|
+
private navigateWithPortalPolicy: Router['navigate'] = ((to, opts) => {
|
|
177
|
+
if (this.shouldOpenAdminRouteInNewWindow(to, opts)) {
|
|
178
|
+
window.open(this.getAdminRouteNavigationHref(to as string), '_blank', 'noopener,noreferrer');
|
|
179
|
+
return Promise.resolve();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const navigate = this.routerNavigate || this.router.navigate.bind(this.router);
|
|
183
|
+
return navigate(to, opts);
|
|
184
|
+
}) as Router['navigate'];
|
|
185
|
+
|
|
186
|
+
private shouldOpenAdminRouteInNewWindow(
|
|
187
|
+
to: Parameters<Router['navigate']>[0],
|
|
188
|
+
opts?: Parameters<Router['navigate']>[1],
|
|
189
|
+
) {
|
|
190
|
+
if (this.options.type && this.options.type !== 'browser') {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return shouldOpenAdminRouteInNewWindow({
|
|
195
|
+
currentPathname:
|
|
196
|
+
typeof window !== 'undefined' ? window.location.pathname : this.router?.state?.location?.pathname,
|
|
197
|
+
targetPathname: to,
|
|
198
|
+
basePath: this.getRuntimeBasePath(),
|
|
199
|
+
adminRoutePath: this.getAdminRoutePath(),
|
|
200
|
+
replace: opts?.replace,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private getRuntimeBasePath() {
|
|
205
|
+
return getV2EffectiveBasePath(this.app);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private getAdminRoutePath() {
|
|
209
|
+
try {
|
|
210
|
+
return this.app.layoutManager?.getLayout?.('admin')?.routePath || DEFAULT_ADMIN_ROUTE_PATH;
|
|
211
|
+
} catch {
|
|
212
|
+
return DEFAULT_ADMIN_ROUTE_PATH;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private getAdminRouteNavigationHref(pathname: string) {
|
|
217
|
+
const { pathname: rawPathname, suffix } = splitPathSearchAndHash(pathname.trim());
|
|
218
|
+
const normalizedPathname = normalizeRootPath(rawPathname);
|
|
219
|
+
const runtimeBasePath = this.getRuntimeBasePath();
|
|
220
|
+
const normalizedBasePath = normalizeRootPath(runtimeBasePath);
|
|
221
|
+
|
|
222
|
+
if (
|
|
223
|
+
normalizedBasePath !== '/' &&
|
|
224
|
+
(normalizedPathname === normalizedBasePath || normalizedPathname.startsWith(`${normalizedBasePath}/`))
|
|
225
|
+
) {
|
|
226
|
+
return `${normalizedPathname}${suffix}`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return `${this.app.getHref(normalizePortalRoutePath(normalizedPathname, runtimeBasePath))}${suffix}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
93
232
|
protected resolveLoadedComponent(moduleOrComponent: ComponentLoaderResult): ComponentTypeAndString | undefined {
|
|
94
233
|
if (!moduleOrComponent) {
|
|
95
234
|
return undefined;
|
|
@@ -257,6 +396,8 @@ export class RouterManager<TApp extends BaseApplication<any> = BaseApplication<a
|
|
|
257
396
|
],
|
|
258
397
|
opts,
|
|
259
398
|
);
|
|
399
|
+
this.routerNavigate = this.router.navigate.bind(this.router) as Router['navigate'];
|
|
400
|
+
this.router.navigate = this.navigateWithPortalPolicy;
|
|
260
401
|
|
|
261
402
|
const RenderRouter: RouterComponentType = ({ BaseLayout = BlankComponent }) => {
|
|
262
403
|
return (
|
|
@@ -78,6 +78,19 @@ describe('PluginSettingsManager v2', () => {
|
|
|
78
78
|
expect(app.router.get('admin.settings.demo.advanced')).toMatchObject({ path: 'advanced' });
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
+
it('should resolve plugin settings links for plugin manager entries', () => {
|
|
82
|
+
const app = createMockClient();
|
|
83
|
+
|
|
84
|
+
app.pluginSettingsManager.addMenuItem({ key: 'shared', title: 'Shared' });
|
|
85
|
+
app.pluginSettingsManager.addPageTabItem({ menuKey: 'shared', key: 'target', title: 'Target' });
|
|
86
|
+
app.pluginSettingsManager.setPluginSettingsLink('demo-plugin', 'shared.target');
|
|
87
|
+
|
|
88
|
+
expect(app.pluginSettingsManager.has('demo-plugin')).toBe(false);
|
|
89
|
+
expect(app.pluginSettingsManager.hasPluginSettings('demo-plugin')).toBe(true);
|
|
90
|
+
expect(app.pluginSettingsManager.getPluginSettingsName('demo-plugin')).toBe('shared.target');
|
|
91
|
+
expect(app.pluginSettingsManager.getPluginSettingsRoutePath('demo-plugin')).toBe('/admin/settings/shared/target');
|
|
92
|
+
});
|
|
93
|
+
|
|
81
94
|
it('should render string icon on both menu and page tab via renderIcon', () => {
|
|
82
95
|
// Previously `renderPage` spread the raw `icon` string straight to the antd
|
|
83
96
|
// Menu item, which displayed "LockOutlinedTitle" as text. Both `renderMenuItem`
|