@nocobase/client-v2 2.2.0-alpha.1 → 2.2.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/APIClient.d.ts +1 -3
- 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/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/filter/CollectionFilter.d.ts +2 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +2 -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 +2 -2
- package/es/flow/actions/linkageRules.d.ts +2 -0
- 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 +3 -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/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/RunJSValueEditor.d.ts +1 -0
- package/es/flow/components/filter/FilterGroup.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
- package/es/flow/index.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/models/base/ActionModelCore.d.ts +2 -0
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
- package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
- package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
- 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/TableColumnModel.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/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- 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/flow-compat/fieldValidationConstants.d.ts +1 -1
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +8 -1
- package/es/index.mjs +367 -181
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/es/utils/markdownSanitize.d.ts +11 -0
- package/lib/index.js +367 -181
- package/lib/locale/languageCodes.js +3 -1
- package/package.json +9 -7
- package/src/APIClient.ts +1 -10
- package/src/Application.tsx +4 -0
- package/src/BaseApplication.tsx +13 -6
- 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__/app.test.tsx +73 -0
- package/src/__tests__/authRedirect.test.ts +17 -0
- package/src/__tests__/browserChecker.test.ts +61 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +234 -2
- 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/field-validation.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/README.md +7 -1
- package/src/components/README.zh-CN.md +6 -1
- package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
- package/src/components/category-tabs/index.ts +10 -0
- package/src/components/form/JsonTextArea.tsx +25 -11
- package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
- package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
- package/src/components/form/TypedVariableInput.tsx +452 -101
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/filter/CollectionFilter.tsx +4 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/filter/useFilterActionProps.ts +37 -6
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +2 -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 +1181 -80
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +115 -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 +1124 -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__/linkageRules.tab.test.ts +171 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- 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 +3 -1
- package/src/flow/actions/linkageRules.tsx +209 -47
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- 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 -14
- 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 +435 -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/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FieldAssignValueInput.tsx +91 -20
- package/src/flow/components/FlowRoute.tsx +256 -53
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- 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 +216 -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/components/filter/FilterGroup.tsx +33 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
- package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
- package/src/flow/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +4 -1
- package/src/flow/internal/components/Markdown/util.ts +2 -1
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- 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/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -50
- package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
- package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +817 -13
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
- 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__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -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 +59 -6
- 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 +143 -4
- 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 +38 -14
- package/src/flow/models/blocks/table/TableBlockModel.tsx +42 -19
- package/src/flow/models/blocks/table/TableColumnModel.tsx +72 -6
- 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.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +243 -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 +31 -7
- 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/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- 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/VariableFieldFormModel.tsx +3 -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/__tests__/VariableFieldFormModel.test.tsx +51 -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 +219 -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/flow-compat/fieldValidationConstants.ts +1 -1
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +14 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/locale/languageCodes.ts +3 -1
- package/src/nocobase-buildin-plugin/index.tsx +63 -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/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
- package/src/utils/markdownSanitize.ts +88 -0
|
@@ -12,6 +12,10 @@ import { act, render, screen, waitFor } from '@testing-library/react';
|
|
|
12
12
|
import React from 'react';
|
|
13
13
|
import { NocoBaseBuildInPlugin } from '../nocobase-buildin-plugin';
|
|
14
14
|
|
|
15
|
+
const TestingAppSpin = () => <div data-testid="app-spin">app spin</div>;
|
|
16
|
+
|
|
17
|
+
const TestingAppError = ({ error }: { error: Error }) => <div role="alert">{error.message}</div>;
|
|
18
|
+
|
|
15
19
|
class SkippedPublicRoutePlugin extends Plugin {
|
|
16
20
|
async load() {
|
|
17
21
|
this.router.add('public', {
|
|
@@ -22,10 +26,36 @@ class SkippedPublicRoutePlugin extends Plugin {
|
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
class DummySigninRoutePlugin extends Plugin {
|
|
30
|
+
async load() {
|
|
31
|
+
this.router.add('signin-test', {
|
|
32
|
+
path: '/signin',
|
|
33
|
+
skipAuthCheck: true,
|
|
34
|
+
Component: () => <div>signin page</div>,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class AuthBootstrapRoutePlugin extends Plugin {
|
|
40
|
+
async load() {
|
|
41
|
+
this.router.add('secure', {
|
|
42
|
+
path: '/secure',
|
|
43
|
+
authCheck: true,
|
|
44
|
+
Component: () => <div>secure page</div>,
|
|
45
|
+
});
|
|
46
|
+
this.router.add('guest', {
|
|
47
|
+
path: '/guest',
|
|
48
|
+
authCheck: false,
|
|
49
|
+
Component: () => <div>guest page</div>,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
25
54
|
describe('nocobase buildin plugin auth redirect', () => {
|
|
26
55
|
const originalLocation = globalThis.window.location;
|
|
27
56
|
|
|
28
57
|
beforeEach(() => {
|
|
58
|
+
globalThis.window.localStorage.clear();
|
|
29
59
|
// These fixtures mount the modern client under the `v2` segment; tell the
|
|
30
60
|
// runtime-prefix helper so v2-runtime detection matches (server injects it
|
|
31
61
|
// in production).
|
|
@@ -46,6 +76,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
46
76
|
});
|
|
47
77
|
|
|
48
78
|
afterEach(() => {
|
|
79
|
+
globalThis.window.localStorage.clear();
|
|
49
80
|
delete (globalThis.window as any).__nocobase_modern_client_prefix__;
|
|
50
81
|
Object.defineProperty(globalThis.window, 'location', {
|
|
51
82
|
configurable: true,
|
|
@@ -87,6 +118,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
87
118
|
plugins: [NocoBaseBuildInPlugin as any],
|
|
88
119
|
router: { type: 'memory', initialEntries: ['/v2/admin'] },
|
|
89
120
|
});
|
|
121
|
+
app.apiClient.auth.setToken('test-token');
|
|
90
122
|
app.apiMock.onGet('app:getLang').reply(200, {
|
|
91
123
|
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
92
124
|
});
|
|
@@ -101,6 +133,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
101
133
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
102
134
|
expect(app.router.router.state.location.pathname).toBe('/v2/admin');
|
|
103
135
|
expect(app.router.router.state.location.search).toBe('');
|
|
136
|
+
expect(app.apiMock.history.post.filter((request) => request.url === 'auth:syncCookies')).toHaveLength(0);
|
|
104
137
|
});
|
|
105
138
|
|
|
106
139
|
it('should redirect unauthenticated v2 root access to v2 signin via <Navigate />', async () => {
|
|
@@ -155,6 +188,200 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
155
188
|
});
|
|
156
189
|
});
|
|
157
190
|
|
|
191
|
+
it('should defer data source bootstrap until auth-required route is authenticated after signin redirect', async () => {
|
|
192
|
+
const app = createMockClient({
|
|
193
|
+
publicPath: '/v2/',
|
|
194
|
+
plugins: [NocoBaseBuildInPlugin as any, DummySigninRoutePlugin as any, AuthBootstrapRoutePlugin as any],
|
|
195
|
+
components: { AppSpin: TestingAppSpin },
|
|
196
|
+
router: { type: 'memory', initialEntries: ['/v2/secure'] },
|
|
197
|
+
});
|
|
198
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
199
|
+
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
200
|
+
});
|
|
201
|
+
const events: string[] = [];
|
|
202
|
+
app.apiMock.onGet('/auth:check').replyOnce(() => {
|
|
203
|
+
events.push('auth:first');
|
|
204
|
+
return [200, { data: {} }];
|
|
205
|
+
});
|
|
206
|
+
app.apiMock.onGet('/auth:check').reply(() => {
|
|
207
|
+
events.push('auth:second');
|
|
208
|
+
return [200, { data: { id: 1 } }];
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
const ensureLoaded = vi.spyOn(app.dataSourceManager, 'ensureLoaded').mockImplementation(() => {
|
|
212
|
+
if (!app.apiClient.auth.token) {
|
|
213
|
+
const pending = new Promise<void>(() => undefined);
|
|
214
|
+
app.dataSourceManager.loadingPromise = pending;
|
|
215
|
+
return pending;
|
|
216
|
+
}
|
|
217
|
+
events.push('bootstrap');
|
|
218
|
+
expect(events).toContain('auth:second');
|
|
219
|
+
return Promise.resolve();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const Root = app.getRootComponent();
|
|
223
|
+
render(<Root />);
|
|
224
|
+
|
|
225
|
+
await waitFor(() => {
|
|
226
|
+
expect(app.router.router.state.location.pathname).toBe('/v2/signin');
|
|
227
|
+
expect(app.router.router.state.location.search).toBe('?redirect=%2Fv2%2Fsecure');
|
|
228
|
+
});
|
|
229
|
+
expect(await screen.findByText('signin page')).toBeInTheDocument();
|
|
230
|
+
expect(screen.queryByTestId('app-spin')).not.toBeInTheDocument();
|
|
231
|
+
expect(ensureLoaded).not.toHaveBeenCalled();
|
|
232
|
+
expect(app.dataSourceManager.loadingPromise).toBeNull();
|
|
233
|
+
expect(events).toEqual(['auth:first']);
|
|
234
|
+
|
|
235
|
+
act(() => {
|
|
236
|
+
app.apiClient.auth.setToken('test-token');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
await act(async () => {
|
|
240
|
+
await app.router.router.navigate('/secure');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
expect(await screen.findByText('secure page')).toBeInTheDocument();
|
|
244
|
+
await waitFor(() => {
|
|
245
|
+
expect(document.querySelector('.ant-spin-spinning')).not.toBeInTheDocument();
|
|
246
|
+
});
|
|
247
|
+
expect(ensureLoaded).toHaveBeenCalledTimes(1);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('should redirect on auth-required /auth:check 401 without bootstrapping data sources', async () => {
|
|
251
|
+
const app = createMockClient({
|
|
252
|
+
publicPath: '/v2/',
|
|
253
|
+
plugins: [NocoBaseBuildInPlugin as any, DummySigninRoutePlugin as any, AuthBootstrapRoutePlugin as any],
|
|
254
|
+
components: { AppSpin: TestingAppSpin },
|
|
255
|
+
router: { type: 'memory', initialEntries: ['/v2/secure'] },
|
|
256
|
+
});
|
|
257
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
258
|
+
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
259
|
+
});
|
|
260
|
+
app.apiMock.onGet('/auth:check').reply(401, { errors: [{ code: 'EMPTY_TOKEN' }] });
|
|
261
|
+
const ensureLoaded = vi.spyOn(app.dataSourceManager, 'ensureLoaded').mockImplementation(() => {
|
|
262
|
+
const pending = new Promise<void>(() => undefined);
|
|
263
|
+
app.dataSourceManager.loadingPromise = pending;
|
|
264
|
+
return pending;
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
const Root = app.getRootComponent();
|
|
268
|
+
render(<Root />);
|
|
269
|
+
|
|
270
|
+
await waitFor(() => {
|
|
271
|
+
expect(app.router.router.state.location.pathname).toBe('/v2/signin');
|
|
272
|
+
expect(app.router.router.state.location.search).toBe('?redirect=%2Fv2%2Fsecure');
|
|
273
|
+
});
|
|
274
|
+
expect(await screen.findByText('signin page')).toBeInTheDocument();
|
|
275
|
+
expect(screen.queryByTestId('app-spin')).not.toBeInTheDocument();
|
|
276
|
+
expect(ensureLoaded).not.toHaveBeenCalled();
|
|
277
|
+
expect(app.dataSourceManager.loadingPromise).toBeNull();
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('should surface non-auth /auth:check errors instead of leaving auth-required route spinning', async () => {
|
|
281
|
+
const app = createMockClient({
|
|
282
|
+
publicPath: '/v2/',
|
|
283
|
+
plugins: [NocoBaseBuildInPlugin as any, AuthBootstrapRoutePlugin as any],
|
|
284
|
+
components: { AppSpin: TestingAppSpin, AppError: TestingAppError },
|
|
285
|
+
router: { type: 'memory', initialEntries: ['/v2/secure'] },
|
|
286
|
+
});
|
|
287
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
288
|
+
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
289
|
+
});
|
|
290
|
+
app.apiMock.onGet('/auth:check').reply(500, { errors: [{ message: 'auth check failed' }] });
|
|
291
|
+
const ensureLoaded = vi.spyOn(app.dataSourceManager, 'ensureLoaded').mockResolvedValue(undefined);
|
|
292
|
+
|
|
293
|
+
const Root = app.getRootComponent();
|
|
294
|
+
render(<Root />);
|
|
295
|
+
|
|
296
|
+
expect(await screen.findByRole('alert')).toHaveTextContent('Request failed with status code 500');
|
|
297
|
+
expect(screen.queryByTestId('app-spin')).not.toBeInTheDocument();
|
|
298
|
+
expect(ensureLoaded).not.toHaveBeenCalled();
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it.each(['/v2/signin', '/v2/public'])('should not bootstrap data sources on skipped auth route: %s', async (path) => {
|
|
302
|
+
const app = createMockClient({
|
|
303
|
+
publicPath: '/v2/',
|
|
304
|
+
plugins: [NocoBaseBuildInPlugin as any, DummySigninRoutePlugin as any, SkippedPublicRoutePlugin as any],
|
|
305
|
+
components: { AppSpin: TestingAppSpin },
|
|
306
|
+
router: { type: 'memory', initialEntries: [path] },
|
|
307
|
+
});
|
|
308
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
309
|
+
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
310
|
+
});
|
|
311
|
+
const ensureLoaded = vi.spyOn(app.dataSourceManager, 'ensureLoaded').mockResolvedValue(undefined);
|
|
312
|
+
|
|
313
|
+
const Root = app.getRootComponent();
|
|
314
|
+
render(<Root />);
|
|
315
|
+
|
|
316
|
+
expect(await screen.findByText(path === '/v2/signin' ? 'signin page' : 'public page')).toBeInTheDocument();
|
|
317
|
+
expect(screen.queryByTestId('app-spin')).not.toBeInTheDocument();
|
|
318
|
+
expect(ensureLoaded).not.toHaveBeenCalled();
|
|
319
|
+
expect(app.apiMock.history.get.filter((request) => request.url === '/auth:check')).toHaveLength(0);
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
it('should bootstrap data sources for authenticated auth-required route access', async () => {
|
|
323
|
+
const app = createMockClient({
|
|
324
|
+
publicPath: '/v2/',
|
|
325
|
+
plugins: [NocoBaseBuildInPlugin as any, AuthBootstrapRoutePlugin as any],
|
|
326
|
+
router: { type: 'memory', initialEntries: ['/v2/secure'] },
|
|
327
|
+
});
|
|
328
|
+
app.apiClient.auth.setToken('test-token');
|
|
329
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
330
|
+
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
331
|
+
});
|
|
332
|
+
app.apiMock.onGet('/auth:check').reply(200, { data: { id: 1 } });
|
|
333
|
+
app.apiMock.onPost('auth:syncCookies').reply(200, { data: { synced: true } });
|
|
334
|
+
const ensureLoaded = vi.spyOn(app.dataSourceManager, 'ensureLoaded').mockResolvedValue(undefined);
|
|
335
|
+
|
|
336
|
+
const Root = app.getRootComponent();
|
|
337
|
+
render(<Root />);
|
|
338
|
+
|
|
339
|
+
expect(await screen.findByText('secure page')).toBeInTheDocument();
|
|
340
|
+
expect(ensureLoaded).toHaveBeenCalledTimes(1);
|
|
341
|
+
expect(app.apiMock.history.post.filter((request) => request.url === 'auth:syncCookies')).toHaveLength(1);
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
it('should keep authenticated v2 startup working when cookie bootstrap fails', async () => {
|
|
345
|
+
const app = createMockClient({
|
|
346
|
+
publicPath: '/v2/',
|
|
347
|
+
plugins: [NocoBaseBuildInPlugin as any, AuthBootstrapRoutePlugin as any],
|
|
348
|
+
router: { type: 'memory', initialEntries: ['/v2/secure'] },
|
|
349
|
+
});
|
|
350
|
+
app.apiClient.auth.setToken('test-token');
|
|
351
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
352
|
+
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
353
|
+
});
|
|
354
|
+
app.apiMock.onGet('/auth:check').reply(200, { data: { id: 1 } });
|
|
355
|
+
app.apiMock.onPost('auth:syncCookies').reply(500, { errors: [{ message: 'cookie sync failed' }] });
|
|
356
|
+
const ensureLoaded = vi.spyOn(app.dataSourceManager, 'ensureLoaded').mockResolvedValue(undefined);
|
|
357
|
+
|
|
358
|
+
const Root = app.getRootComponent();
|
|
359
|
+
render(<Root />);
|
|
360
|
+
|
|
361
|
+
expect(await screen.findByText('secure page')).toBeInTheDocument();
|
|
362
|
+
expect(ensureLoaded).toHaveBeenCalledTimes(1);
|
|
363
|
+
expect(app.apiMock.history.post.filter((request) => request.url === 'auth:syncCookies')).toHaveLength(1);
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('should not auth-check or bootstrap authCheck false routes', async () => {
|
|
367
|
+
const app = createMockClient({
|
|
368
|
+
publicPath: '/v2/',
|
|
369
|
+
plugins: [NocoBaseBuildInPlugin as any, AuthBootstrapRoutePlugin as any],
|
|
370
|
+
router: { type: 'memory', initialEntries: ['/v2/guest'] },
|
|
371
|
+
});
|
|
372
|
+
app.apiMock.onGet('app:getLang').reply(200, {
|
|
373
|
+
data: { lang: 'en-US', resources: { client: {} }, cron: {} },
|
|
374
|
+
});
|
|
375
|
+
const ensureLoaded = vi.spyOn(app.dataSourceManager, 'ensureLoaded').mockResolvedValue(undefined);
|
|
376
|
+
|
|
377
|
+
const Root = app.getRootComponent();
|
|
378
|
+
render(<Root />);
|
|
379
|
+
|
|
380
|
+
expect(await screen.findByText('guest page')).toBeInTheDocument();
|
|
381
|
+
expect(ensureLoaded).not.toHaveBeenCalled();
|
|
382
|
+
expect(app.apiMock.history.get.filter((request) => request.url === '/auth:check')).toHaveLength(0);
|
|
383
|
+
});
|
|
384
|
+
|
|
158
385
|
it('should render v2 admin root without redirecting away', async () => {
|
|
159
386
|
const app = createMockClient({
|
|
160
387
|
publicPath: '/v2/',
|
|
@@ -188,7 +415,7 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
188
415
|
});
|
|
189
416
|
|
|
190
417
|
it.each(['/v2/admin/legacy-page/tab/tab-1', '/v2/admin/legacy-page/view/detail'])(
|
|
191
|
-
'should
|
|
418
|
+
'should explain authenticated direct v1-style v2 page access: %s',
|
|
192
419
|
async (pathname) => {
|
|
193
420
|
const app = createMockClient({
|
|
194
421
|
publicPath: '/v2/',
|
|
@@ -214,7 +441,12 @@ describe('nocobase buildin plugin auth redirect', () => {
|
|
|
214
441
|
const Root = app.getRootComponent();
|
|
215
442
|
render(<Root />);
|
|
216
443
|
|
|
217
|
-
expect(await screen.findByText('
|
|
444
|
+
expect(await screen.findByText('This page is not supported in the /v2/ branch')).toBeInTheDocument();
|
|
445
|
+
expect(screen.queryByText('404')).not.toBeInTheDocument();
|
|
446
|
+
expect(screen.getByRole('link', { name: 'Open from the original entry' })).toHaveAttribute(
|
|
447
|
+
'href',
|
|
448
|
+
pathname.replace(/^\/v2/, ''),
|
|
449
|
+
);
|
|
218
450
|
expect(app.router.router.state.location.pathname).toBe(pathname);
|
|
219
451
|
},
|
|
220
452
|
);
|
|
@@ -18,6 +18,19 @@ class TestAclPlugin extends Plugin {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
class TestSettingsLinkPlugin extends Plugin {
|
|
22
|
+
async load() {
|
|
23
|
+
this.app.pluginSettingsManager.addMenuItem({ key: 'shared-settings', title: 'Shared settings' });
|
|
24
|
+
this.app.pluginSettingsManager.addPageTabItem({
|
|
25
|
+
menuKey: 'shared-settings',
|
|
26
|
+
key: 'target',
|
|
27
|
+
title: 'Target settings',
|
|
28
|
+
Component: () => <div>Target settings page</div>,
|
|
29
|
+
});
|
|
30
|
+
this.app.pluginSettingsManager.setPluginSettingsLink('demo-plugin', 'shared-settings.target');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
21
34
|
type MockClientApplication = ReturnType<typeof createMockClient>;
|
|
22
35
|
|
|
23
36
|
const renderApp = (app: MockClientApplication) => {
|
|
@@ -35,9 +48,9 @@ const waitForGetRequests = async (app: MockClientApplication, urls: string[]) =>
|
|
|
35
48
|
);
|
|
36
49
|
};
|
|
37
50
|
|
|
38
|
-
const setupApp = (pmList: any[]) => {
|
|
51
|
+
const setupApp = (pmList: any[], plugins: Array<typeof Plugin> = []) => {
|
|
39
52
|
const app = createMockClient({
|
|
40
|
-
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
|
53
|
+
plugins: [NocoBaseBuildInPlugin, TestAclPlugin, ...plugins],
|
|
41
54
|
router: { type: 'memory', initialEntries: ['/admin/settings/plugin-manager'] },
|
|
42
55
|
});
|
|
43
56
|
|
|
@@ -174,4 +187,33 @@ describe('plugin-manager page', () => {
|
|
|
174
187
|
expect(removeCall?.params).toMatchObject({ filterByTk: 'demo-plugin' });
|
|
175
188
|
});
|
|
176
189
|
});
|
|
190
|
+
|
|
191
|
+
it('shows Settings for a plugin linked to a different settings page', async () => {
|
|
192
|
+
const app = setupApp(
|
|
193
|
+
[
|
|
194
|
+
{
|
|
195
|
+
name: 'demo-plugin',
|
|
196
|
+
packageName: '@nocobase/demo-plugin',
|
|
197
|
+
displayName: 'Demo plugin',
|
|
198
|
+
description: 'A demo',
|
|
199
|
+
enabled: true,
|
|
200
|
+
builtIn: false,
|
|
201
|
+
removable: false,
|
|
202
|
+
version: '0.1.0',
|
|
203
|
+
isCompatible: true,
|
|
204
|
+
keywords: [],
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
[TestSettingsLinkPlugin],
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
renderApp(app);
|
|
211
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check', 'pm:list']);
|
|
212
|
+
|
|
213
|
+
const card = await screen.findByRole('button', { name: 'Demo plugin' });
|
|
214
|
+
const settingsLink = within(card.closest('.ant-card') as HTMLElement).getByText('Settings');
|
|
215
|
+
fireEvent.click(settingsLink);
|
|
216
|
+
|
|
217
|
+
expect(await screen.findByText('Target settings page')).toBeInTheDocument();
|
|
218
|
+
});
|
|
177
219
|
});
|
|
@@ -14,7 +14,7 @@ import { message } from 'antd';
|
|
|
14
14
|
import { AdminSettingsLayoutModel as ClientV2AdminSettingsLayoutModel } from '../settings-center';
|
|
15
15
|
import { AdminSettingsLayoutModel as ClientV1AdminSettingsLayoutModel } from '../../../client/src/pm/AdminSettingsLayoutModel';
|
|
16
16
|
import { NocoBaseBuildInPlugin } from '../nocobase-buildin-plugin';
|
|
17
|
-
import { matchSettingsRoute } from '../settings-center/utils';
|
|
17
|
+
import { matchSettingsRoute, sortTopLevelSettings } from '../settings-center/utils';
|
|
18
18
|
|
|
19
19
|
class TestAclPlugin extends Plugin {
|
|
20
20
|
async load() {
|
|
@@ -168,6 +168,12 @@ describe('settings center', () => {
|
|
|
168
168
|
});
|
|
169
169
|
});
|
|
170
170
|
|
|
171
|
+
it('should sort system-settings with other top-level settings by normal ordering', () => {
|
|
172
|
+
const settings = [{ name: 'system-settings' }, { name: 'api-keys' }, { name: 'backups' }] as any;
|
|
173
|
+
|
|
174
|
+
expect(sortTopLevelSettings(settings).map((item) => item.name)).toEqual(['api-keys', 'backups', 'system-settings']);
|
|
175
|
+
});
|
|
176
|
+
|
|
171
177
|
it('should redirect /admin/settings to system-settings by default', async () => {
|
|
172
178
|
const app = createMockClient({
|
|
173
179
|
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
|
@@ -354,6 +360,39 @@ describe('settings center', () => {
|
|
|
354
360
|
expect(screen.getByRole('menuitem', { name: 'Menu ACL Demo' })).toBeInTheDocument();
|
|
355
361
|
});
|
|
356
362
|
|
|
363
|
+
it('should allow the settings sidebar menu to scroll independently', async () => {
|
|
364
|
+
class ManySettingsPlugin extends Plugin {
|
|
365
|
+
async load() {
|
|
366
|
+
for (let index = 0; index < 30; index += 1) {
|
|
367
|
+
this.pluginSettingsManager.addMenuItem({
|
|
368
|
+
key: `scroll-demo-${index}`,
|
|
369
|
+
title: `Scroll demo ${index}`,
|
|
370
|
+
});
|
|
371
|
+
this.pluginSettingsManager.addPageTabItem({
|
|
372
|
+
menuKey: `scroll-demo-${index}`,
|
|
373
|
+
key: 'index',
|
|
374
|
+
title: `Scroll demo ${index}`,
|
|
375
|
+
Component: () => <div>{`Scroll demo page ${index}`}</div>,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const app = createMockClient({
|
|
382
|
+
plugins: [NocoBaseBuildInPlugin, TestAclPlugin, ManySettingsPlugin],
|
|
383
|
+
router: { type: 'memory', initialEntries: ['/admin/settings/scroll-demo-29'] },
|
|
384
|
+
});
|
|
385
|
+
mockAdminRuntime(app);
|
|
386
|
+
|
|
387
|
+
await renderApp(app);
|
|
388
|
+
await waitForGetRequests(app, ['/auth:check', 'roles:check']);
|
|
389
|
+
|
|
390
|
+
expect(await screen.findByText('Scroll demo page 29')).toBeInTheDocument();
|
|
391
|
+
|
|
392
|
+
const sidebar = screen.getByRole('menuitem', { name: 'Scroll demo 29' }).closest('.ant-layout-sider');
|
|
393
|
+
expect(sidebar).toHaveStyle({ overflowY: 'auto' });
|
|
394
|
+
});
|
|
395
|
+
|
|
357
396
|
it('should save system settings through systemSettings:put', async () => {
|
|
358
397
|
const app = createMockClient({
|
|
359
398
|
plugins: [NocoBaseBuildInPlugin, TestAclPlugin],
|
package/src/authRedirect.ts
CHANGED
|
@@ -204,6 +204,44 @@ function getDefaultV2AdminRedirectPath(app: AppLike) {
|
|
|
204
204
|
return joinRootRelativePath(getV2EffectiveBasePath(app), '/admin');
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
function isSafeRootRelativePath(value?: string | null) {
|
|
208
|
+
return !!value && value.startsWith('/') && !value.startsWith('//') && !value.startsWith('/\\');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function preserveTrailingSlash(originalPathname: string, value: string) {
|
|
212
|
+
if (originalPathname !== '/' && originalPathname.endsWith('/') && !value.endsWith('/')) {
|
|
213
|
+
return `${value}/`;
|
|
214
|
+
}
|
|
215
|
+
return value;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export function normalizeV2RedirectPath(app: AppLike, target?: string | null, fallbackPath = '/admin/') {
|
|
219
|
+
// In a v2 sub-app, publicPath can be `/v/` while the active router
|
|
220
|
+
// basename is `/v/apps/a/`. Redirects must resolve under the basename.
|
|
221
|
+
const basePath = trimTrailingSlashes(getV2EffectiveBasePath(app)) || '/';
|
|
222
|
+
const fallbackTarget = isSafeRootRelativePath(fallbackPath) ? fallbackPath : '/admin/';
|
|
223
|
+
const rawTarget = isSafeRootRelativePath(target) ? target : fallbackTarget;
|
|
224
|
+
let { pathname, search, hash } = splitPathLike(rawTarget);
|
|
225
|
+
let normalizedPathname = normalizePathname(pathname);
|
|
226
|
+
|
|
227
|
+
// Already under the current v2 runtime, e.g. `/v/apps/a/admin/`.
|
|
228
|
+
if (basePath === '/' || normalizedPathname === basePath || normalizedPathname.startsWith(`${basePath}/`)) {
|
|
229
|
+
return `${preserveTrailingSlash(pathname, normalizedPathname)}${normalizeSearch(search)}${normalizeHash(hash)}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const publicPath = trimTrailingSlashes(getV2PublicPath(app)) || '/';
|
|
233
|
+
// Under v2 publicPath but outside the current basename, e.g. `/v/admin/`
|
|
234
|
+
// inside `/v/apps/a/`; use fallback so it lands on `/v/apps/a/admin/`.
|
|
235
|
+
if (publicPath !== '/' && (normalizedPathname === publicPath || normalizedPathname.startsWith(`${publicPath}/`))) {
|
|
236
|
+
({ pathname, search, hash } = splitPathLike(fallbackTarget));
|
|
237
|
+
normalizedPathname = normalizePathname(pathname);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Basename-relative target, e.g. `/admin/`, becomes `/v/apps/a/admin/`.
|
|
241
|
+
const joinedPathname = preserveTrailingSlash(pathname, joinRootRelativePath(basePath, normalizedPathname));
|
|
242
|
+
return `${joinedPathname}${normalizeSearch(search)}${normalizeHash(hash)}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
207
245
|
/**
|
|
208
246
|
* 将当前 v2 页面地址转换为根相对 redirect 路径。
|
|
209
247
|
*
|
|
@@ -115,6 +115,7 @@ export abstract class CollectionFieldInterface {
|
|
|
115
115
|
titleUsable?: boolean;
|
|
116
116
|
usePathOptions?(field: any): any;
|
|
117
117
|
hidden?: boolean;
|
|
118
|
+
creatable?: boolean;
|
|
118
119
|
|
|
119
120
|
addComponentOption(componentOption: CollectionFieldInterfaceComponentOption) {
|
|
120
121
|
if (!this.componentOptions) {
|
|
@@ -530,7 +530,7 @@ export function reverseFieldConfigureItems(): FieldConfigureItem[] {
|
|
|
530
530
|
component: 'Input',
|
|
531
531
|
required: true,
|
|
532
532
|
description:
|
|
533
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
533
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
534
534
|
hidden: ({ context, values }) => !context.showReverseFieldConfig && !get(values, 'autoCreateReverseField'),
|
|
535
535
|
disabled: ({ context }) => !context.showReverseFieldConfig,
|
|
536
536
|
},
|
|
@@ -132,7 +132,7 @@ fieldValidationConfigureRegistry.registerMany([
|
|
|
132
132
|
{ key: 'uri', label: 'URI', hasValue: false, params: [], paramsType: 'object' },
|
|
133
133
|
{
|
|
134
134
|
key: 'multiple',
|
|
135
|
-
label: 'Multiple',
|
|
135
|
+
label: 'Multiple of',
|
|
136
136
|
hasValue: true,
|
|
137
137
|
params: [{ key: 'base', label: 'Base', componentType: 'inputNumber', required: true }],
|
|
138
138
|
},
|
|
@@ -48,7 +48,7 @@ export class IdFieldInterface extends CollectionFieldInterface {
|
|
|
48
48
|
'x-decorator': 'FormItem',
|
|
49
49
|
'x-component': 'Input',
|
|
50
50
|
description:
|
|
51
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
51
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
52
52
|
},
|
|
53
53
|
};
|
|
54
54
|
filterable = {
|
|
@@ -158,7 +158,7 @@ export class M2MFieldInterface extends CollectionFieldInterface {
|
|
|
158
158
|
required: true,
|
|
159
159
|
default: '{{ useNewId("f_") }}',
|
|
160
160
|
description:
|
|
161
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
161
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
162
162
|
'x-decorator': 'FormItem',
|
|
163
163
|
'x-component': 'ForeignKey',
|
|
164
164
|
'x-validator': 'uid',
|
|
@@ -192,7 +192,7 @@ export class M2MFieldInterface extends CollectionFieldInterface {
|
|
|
192
192
|
required: true,
|
|
193
193
|
default: '{{ useNewId("f_") }}',
|
|
194
194
|
description:
|
|
195
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
195
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
196
196
|
'x-decorator': 'FormItem',
|
|
197
197
|
'x-component': 'ForeignKey',
|
|
198
198
|
'x-validator': 'uid',
|
|
@@ -69,7 +69,7 @@ export class M2OFieldInterface extends CollectionFieldInterface {
|
|
|
69
69
|
'x-decorator': 'FormItem',
|
|
70
70
|
'x-component': 'Input',
|
|
71
71
|
description:
|
|
72
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
72
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
73
73
|
},
|
|
74
74
|
type: relationshipType,
|
|
75
75
|
grid: {
|
|
@@ -123,7 +123,7 @@ export class M2OFieldInterface extends CollectionFieldInterface {
|
|
|
123
123
|
required: true,
|
|
124
124
|
default: '{{ useNewId("f_") }}',
|
|
125
125
|
description:
|
|
126
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
126
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
127
127
|
'x-decorator': 'FormItem',
|
|
128
128
|
'x-component': 'ForeignKey',
|
|
129
129
|
'x-validator': 'uid',
|
|
@@ -42,7 +42,7 @@ export class NanoidFieldInterface extends CollectionFieldInterface {
|
|
|
42
42
|
'x-component': 'Input',
|
|
43
43
|
'x-disabled': '{{ !createOnly }}',
|
|
44
44
|
description:
|
|
45
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
45
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
46
46
|
},
|
|
47
47
|
customAlphabet: {
|
|
48
48
|
type: 'string',
|
|
@@ -68,7 +68,7 @@ export class O2MFieldInterface extends CollectionFieldInterface {
|
|
|
68
68
|
'x-decorator': 'FormItem',
|
|
69
69
|
'x-component': 'Input',
|
|
70
70
|
description:
|
|
71
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
71
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
72
72
|
},
|
|
73
73
|
type: relationshipType,
|
|
74
74
|
grid: {
|
|
@@ -135,7 +135,7 @@ export class O2MFieldInterface extends CollectionFieldInterface {
|
|
|
135
135
|
required: true,
|
|
136
136
|
default: '{{ useNewId("f_") }}',
|
|
137
137
|
description:
|
|
138
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
138
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
139
139
|
'x-decorator': 'FormItem',
|
|
140
140
|
'x-component': 'ForeignKey',
|
|
141
141
|
'x-validator': 'uid',
|
|
@@ -55,7 +55,7 @@ export class OBOFieldInterface extends CollectionFieldInterface {
|
|
|
55
55
|
'x-decorator': 'FormItem',
|
|
56
56
|
'x-component': 'Input',
|
|
57
57
|
description:
|
|
58
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
58
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
59
59
|
},
|
|
60
60
|
type: relationshipType,
|
|
61
61
|
grid: {
|
|
@@ -109,7 +109,7 @@ export class OBOFieldInterface extends CollectionFieldInterface {
|
|
|
109
109
|
required: true,
|
|
110
110
|
default: '{{ useNewId("f_") }}',
|
|
111
111
|
description:
|
|
112
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
112
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
113
113
|
'x-decorator': 'FormItem',
|
|
114
114
|
'x-component': 'ForeignKey',
|
|
115
115
|
'x-validator': 'uid',
|
|
@@ -55,7 +55,7 @@ export class OHOFieldInterface extends CollectionFieldInterface {
|
|
|
55
55
|
'x-decorator': 'FormItem',
|
|
56
56
|
'x-component': 'Input',
|
|
57
57
|
description:
|
|
58
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
58
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
59
59
|
},
|
|
60
60
|
type: relationshipType,
|
|
61
61
|
grid: {
|
|
@@ -123,7 +123,7 @@ export class OHOFieldInterface extends CollectionFieldInterface {
|
|
|
123
123
|
required: true,
|
|
124
124
|
default: '{{ useNewId("f_") }}',
|
|
125
125
|
description:
|
|
126
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
126
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
127
127
|
'x-decorator': 'FormItem',
|
|
128
128
|
'x-component': 'ForeignKey',
|
|
129
129
|
'x-validator': 'uid',
|
|
@@ -141,7 +141,7 @@ export const reverseFieldProperties: Record<string, ISchema> = {
|
|
|
141
141
|
'x-validator': 'uid',
|
|
142
142
|
'x-disabled': '{{ !showReverseFieldConfig }}',
|
|
143
143
|
description:
|
|
144
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
144
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
145
145
|
},
|
|
146
146
|
},
|
|
147
147
|
},
|
|
@@ -399,7 +399,7 @@ export const defaultProps = {
|
|
|
399
399
|
'x-component': 'Input',
|
|
400
400
|
'x-validator': 'uid',
|
|
401
401
|
description:
|
|
402
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
402
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
403
403
|
},
|
|
404
404
|
};
|
|
405
405
|
|
|
@@ -46,7 +46,7 @@ export class UUIDFieldInterface extends CollectionFieldInterface {
|
|
|
46
46
|
'x-component': 'Input',
|
|
47
47
|
'x-disabled': '{{ !createOnly }}',
|
|
48
48
|
description:
|
|
49
|
-
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with
|
|
49
|
+
"{{t('Randomly generated and can be modified. Support letters, numbers and underscores, must start with a letter.')}}",
|
|
50
50
|
},
|
|
51
51
|
autoFill,
|
|
52
52
|
layout: {
|