@nocobase/client-v2 2.1.0-alpha.40 → 2.1.0-alpha.46

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.
Files changed (338) hide show
  1. package/es/Application.d.ts +7 -0
  2. package/es/BaseApplication.d.ts +13 -0
  3. package/es/RouterManager.d.ts +1 -0
  4. package/es/authRedirect.d.ts +12 -0
  5. package/es/collection-field-interface/CollectionFieldInterface.d.ts +51 -15
  6. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +82 -3
  7. package/es/collection-manager/field-configure.d.ts +80 -0
  8. package/es/collection-manager/field-validation.d.ts +43 -0
  9. package/es/collection-manager/filter-operators/index.d.ts +46 -0
  10. package/es/collection-manager/filter-operators/operators.d.ts +30 -0
  11. package/es/collection-manager/interfaces/checkbox.d.ts +1 -41
  12. package/es/collection-manager/interfaces/checkboxGroup.d.ts +12 -44
  13. package/es/collection-manager/interfaces/collection.d.ts +12 -51
  14. package/es/collection-manager/interfaces/color.d.ts +1 -16
  15. package/es/collection-manager/interfaces/createdAt.d.ts +1 -44
  16. package/es/collection-manager/interfaces/createdBy.d.ts +0 -4
  17. package/es/collection-manager/interfaces/dateOnly.d.ts +7 -44
  18. package/es/collection-manager/interfaces/datetime.d.ts +1 -44
  19. package/es/collection-manager/interfaces/datetimeNoTz.d.ts +1 -44
  20. package/es/collection-manager/interfaces/email.d.ts +1 -29
  21. package/es/collection-manager/interfaces/id.d.ts +1 -16
  22. package/es/collection-manager/interfaces/index.d.ts +2 -3
  23. package/es/collection-manager/interfaces/input.d.ts +1 -102
  24. package/es/collection-manager/interfaces/integer.d.ts +1 -95
  25. package/es/collection-manager/interfaces/json.d.ts +16 -7
  26. package/es/collection-manager/interfaces/m2m.d.ts +11 -19
  27. package/es/collection-manager/interfaces/m2o.d.ts +11 -19
  28. package/es/collection-manager/interfaces/markdown.d.ts +1 -63
  29. package/es/collection-manager/interfaces/multipleSelect.d.ts +12 -44
  30. package/es/collection-manager/interfaces/nanoid.d.ts +1 -34
  31. package/es/collection-manager/interfaces/number.d.ts +1 -87
  32. package/es/collection-manager/interfaces/o2m.d.ts +12 -24
  33. package/es/collection-manager/interfaces/obo.d.ts +207 -0
  34. package/es/collection-manager/interfaces/oho.d.ts +207 -0
  35. package/es/collection-manager/interfaces/password.d.ts +1 -56
  36. package/es/collection-manager/interfaces/percent.d.ts +1 -84
  37. package/es/collection-manager/interfaces/phone.d.ts +1 -25
  38. package/es/collection-manager/interfaces/properties/index.d.ts +0 -28
  39. package/es/collection-manager/interfaces/radioGroup.d.ts +1 -29
  40. package/es/collection-manager/interfaces/richText.d.ts +1 -63
  41. package/es/collection-manager/interfaces/select.d.ts +12 -44
  42. package/es/collection-manager/interfaces/snowflake-id.d.ts +1 -34
  43. package/es/collection-manager/interfaces/tableoid.d.ts +1 -10
  44. package/es/collection-manager/interfaces/textarea.d.ts +1 -51
  45. package/es/collection-manager/interfaces/time.d.ts +1 -16
  46. package/es/collection-manager/interfaces/types.d.ts +3 -12
  47. package/es/collection-manager/interfaces/unixTimestamp.d.ts +1 -44
  48. package/es/collection-manager/interfaces/updatedAt.d.ts +1 -44
  49. package/es/collection-manager/interfaces/updatedBy.d.ts +0 -4
  50. package/es/collection-manager/interfaces/url.d.ts +1 -20
  51. package/es/collection-manager/interfaces/uuid.d.ts +1 -34
  52. package/es/collection-manager/template-fields.d.ts +53 -0
  53. package/es/components/KeepAlive.d.ts +22 -0
  54. package/es/components/RouterBridge.d.ts +9 -0
  55. package/es/components/form/DialogFormLayout.d.ts +5 -29
  56. package/es/components/form/TypedVariableInput.d.ts +75 -0
  57. package/es/components/form/VariableInput.d.ts +53 -2
  58. package/es/components/form/filter/CollectionFilter.d.ts +49 -0
  59. package/es/components/form/filter/CollectionFilterItem.d.ts +49 -0
  60. package/es/components/form/filter/DateFilterDynamicComponent.d.ts +57 -0
  61. package/es/components/form/filter/FilterValueInput.d.ts +29 -0
  62. package/es/components/form/filter/index.d.ts +11 -0
  63. package/es/components/form/filter/useFilterActionProps.d.ts +96 -0
  64. package/es/components/form/index.d.ts +2 -0
  65. package/es/data-source/ExtendCollectionsProvider.d.ts +50 -0
  66. package/es/data-source/index.d.ts +9 -0
  67. package/es/flow/FlowPage.d.ts +2 -1
  68. package/es/flow/admin-shell/AdminLayoutRouteCoordinator.d.ts +8 -40
  69. package/es/flow/admin-shell/BaseLayoutModel.d.ts +89 -0
  70. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +74 -0
  71. package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +12 -0
  72. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  73. package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -92
  74. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +11 -0
  75. package/es/flow/admin-shell/admin-layout/index.d.ts +3 -0
  76. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +3 -2
  77. package/es/flow/admin-shell/useAdminLayoutRoutePage.d.ts +2 -2
  78. package/es/flow/admin-shell/useLayoutRoutePage.d.ts +23 -0
  79. package/es/flow/components/ConditionBuilder.d.ts +1 -0
  80. package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
  81. package/es/flow/components/FieldAssignValueInput.d.ts +1 -0
  82. package/es/flow/components/FlowRoute.d.ts +10 -1
  83. package/es/flow/components/fieldAssignOptions.d.ts +10 -0
  84. package/es/flow/components/filter/LinkageFilterItem.d.ts +1 -0
  85. package/es/flow/components/filter/VariableFilterItem.d.ts +1 -0
  86. package/es/flow/components/filter/index.d.ts +2 -0
  87. package/es/flow/components/filter/metaTreeAssociationDepth.d.ts +13 -0
  88. package/es/flow/components/filter/useFilterOptions.d.ts +66 -0
  89. package/es/flow/index.d.ts +4 -0
  90. package/es/flow/models/base/AssociationFieldGroupModel.d.ts +2 -33
  91. package/es/flow/models/base/PageModel/PageModel.d.ts +3 -1
  92. package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +92 -0
  93. package/es/flow/models/blocks/assign-form/index.d.ts +1 -0
  94. package/es/flow/models/blocks/form/FormActionGroupModel.d.ts +1 -0
  95. package/es/flow/models/blocks/form/FormActionModel.d.ts +9 -2
  96. package/es/flow/models/blocks/table/TableBlockModel.d.ts +10 -0
  97. package/es/flow/models/blocks/table/TableColumnModel.d.ts +2 -0
  98. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.d.ts +1 -1
  99. package/es/flow-compat/passwordUtils.d.ts +1 -1
  100. package/es/index.d.ts +6 -0
  101. package/es/index.mjs +552 -459
  102. package/es/layout-manager/LayoutContentRoute.d.ts +14 -0
  103. package/es/layout-manager/LayoutManager.d.ts +22 -0
  104. package/es/layout-manager/LayoutRoute.d.ts +14 -0
  105. package/es/layout-manager/index.d.ts +13 -0
  106. package/es/layout-manager/types.d.ts +20 -0
  107. package/es/layout-manager/utils.d.ts +14 -0
  108. package/es/nocobase-buildin-plugin/index.d.ts +3 -10
  109. package/es/settings-center/index.d.ts +1 -1
  110. package/es/settings-center/plugin-manager/BulkEnableButton.d.ts +15 -0
  111. package/es/settings-center/plugin-manager/PluginCard.d.ts +15 -0
  112. package/es/settings-center/plugin-manager/PluginDetail.d.ts +16 -0
  113. package/es/settings-center/{PluginManagerPage.d.ts → plugin-manager/index.d.ts} +1 -7
  114. package/es/settings-center/plugin-manager/types.d.ts +34 -0
  115. package/lib/index.js +552 -459
  116. package/package.json +8 -7
  117. package/src/Application.tsx +51 -12
  118. package/src/BaseApplication.tsx +32 -0
  119. package/src/PluginSettingsManager.ts +1 -1
  120. package/src/RouterManager.tsx +17 -1
  121. package/src/__tests__/PluginSettingsManager.test.ts +41 -2
  122. package/src/__tests__/app.test.tsx +17 -1
  123. package/src/__tests__/globalDeps.test.ts +1 -0
  124. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +50 -2
  125. package/src/__tests__/plugin-manager.test.tsx +177 -0
  126. package/src/__tests__/settings-center.test.tsx +24 -2
  127. package/src/authRedirect.ts +43 -0
  128. package/src/collection-field-interface/CollectionFieldInterface.ts +71 -77
  129. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +201 -4
  130. package/src/collection-manager/field-configure.ts +548 -0
  131. package/src/collection-manager/field-validation.ts +195 -0
  132. package/src/collection-manager/filter-operators/index.ts +176 -0
  133. package/src/collection-manager/{interfaces/properties → filter-operators}/operators.ts +24 -13
  134. package/src/collection-manager/interfaces/checkbox.ts +2 -9
  135. package/src/collection-manager/interfaces/checkboxGroup.ts +2 -10
  136. package/src/collection-manager/interfaces/collection.ts +2 -15
  137. package/src/collection-manager/interfaces/color.ts +2 -2
  138. package/src/collection-manager/interfaces/createdAt.ts +2 -2
  139. package/src/collection-manager/interfaces/createdBy.ts +1 -12
  140. package/src/collection-manager/interfaces/dateOnly.ts +8 -2
  141. package/src/collection-manager/interfaces/datetime.ts +2 -2
  142. package/src/collection-manager/interfaces/datetimeNoTz.ts +2 -2
  143. package/src/collection-manager/interfaces/email.ts +2 -9
  144. package/src/collection-manager/interfaces/id.ts +1 -2
  145. package/src/collection-manager/interfaces/index.ts +2 -3
  146. package/src/collection-manager/interfaces/input.ts +2 -133
  147. package/src/collection-manager/interfaces/integer.ts +2 -71
  148. package/src/collection-manager/interfaces/json.tsx +17 -11
  149. package/src/collection-manager/interfaces/m2m.tsx +0 -21
  150. package/src/collection-manager/interfaces/m2o.tsx +0 -22
  151. package/src/collection-manager/interfaces/markdown.ts +2 -51
  152. package/src/collection-manager/interfaces/multipleSelect.ts +2 -14
  153. package/src/collection-manager/interfaces/nanoid.ts +2 -2
  154. package/src/collection-manager/interfaces/number.ts +2 -85
  155. package/src/collection-manager/interfaces/o2m.tsx +1 -22
  156. package/src/collection-manager/interfaces/obo.tsx +145 -0
  157. package/src/collection-manager/interfaces/oho.tsx +145 -0
  158. package/src/collection-manager/interfaces/password.ts +2 -44
  159. package/src/collection-manager/interfaces/percent.ts +2 -74
  160. package/src/collection-manager/interfaces/phone.ts +2 -2
  161. package/src/collection-manager/interfaces/properties/index.ts +0 -133
  162. package/src/collection-manager/interfaces/radioGroup.ts +2 -2
  163. package/src/collection-manager/interfaces/richText.ts +2 -51
  164. package/src/collection-manager/interfaces/select.ts +2 -14
  165. package/src/collection-manager/interfaces/snowflake-id.ts +2 -2
  166. package/src/collection-manager/interfaces/tableoid.ts +1 -2
  167. package/src/collection-manager/interfaces/textarea.ts +2 -51
  168. package/src/collection-manager/interfaces/time.ts +2 -2
  169. package/src/collection-manager/interfaces/types.ts +4 -12
  170. package/src/collection-manager/interfaces/unixTimestamp.tsx +2 -2
  171. package/src/collection-manager/interfaces/updatedAt.ts +2 -2
  172. package/src/collection-manager/interfaces/updatedBy.ts +1 -12
  173. package/src/collection-manager/interfaces/url.ts +2 -4
  174. package/src/collection-manager/interfaces/uuid.ts +2 -2
  175. package/src/collection-manager/template-fields.ts +109 -0
  176. package/src/components/KeepAlive.tsx +131 -0
  177. package/src/components/README.md +138 -6
  178. package/src/components/README.zh-CN.md +138 -7
  179. package/src/components/RouterBridge.tsx +28 -4
  180. package/src/components/__tests__/KeepAlive.test.tsx +63 -0
  181. package/src/components/__tests__/RouterBridge.test.tsx +27 -0
  182. package/src/components/form/DialogFormLayout.tsx +5 -29
  183. package/src/components/form/TypedVariableInput.tsx +441 -0
  184. package/src/components/form/VariableInput.tsx +101 -28
  185. package/src/components/form/__tests__/TypedVariableInput.test.tsx +152 -0
  186. package/src/components/form/__tests__/VariableInput.test.ts +85 -0
  187. package/src/components/form/filter/CollectionFilter.tsx +111 -0
  188. package/src/components/form/filter/CollectionFilterItem.tsx +184 -0
  189. package/src/components/form/filter/DateFilterDynamicComponent.tsx +283 -0
  190. package/src/components/form/filter/FilterValueInput.tsx +198 -0
  191. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +247 -0
  192. package/src/components/form/filter/__tests__/DateFilterDynamicComponent.test.tsx +148 -0
  193. package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +243 -0
  194. package/src/components/form/filter/__tests__/compileFilterGroup.test.ts +146 -0
  195. package/src/components/form/filter/index.ts +13 -0
  196. package/src/components/form/filter/useFilterActionProps.ts +203 -0
  197. package/src/components/form/index.tsx +2 -0
  198. package/src/data-source/ExtendCollectionsProvider.tsx +144 -0
  199. package/src/data-source/__tests__/ExtendCollectionsProvider.test.tsx +264 -0
  200. package/src/data-source/index.ts +10 -0
  201. package/src/flow/FlowPage.tsx +35 -7
  202. package/src/flow/__tests__/FlowPage.test.tsx +79 -0
  203. package/src/flow/__tests__/FlowRoute.test.tsx +537 -3
  204. package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +100 -0
  205. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +191 -0
  206. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +23 -1
  207. package/src/flow/actions/__tests__/openView.subModelKey.test.tsx +33 -0
  208. package/src/flow/actions/aclCheck.tsx +4 -0
  209. package/src/flow/actions/aclCheckRefresh.tsx +4 -0
  210. package/src/flow/actions/dataScope.tsx +8 -1
  211. package/src/flow/actions/dateTimeFormat.tsx +12 -8
  212. package/src/flow/actions/linkageRules.tsx +127 -2
  213. package/src/flow/actions/linkageRulesRefresh.tsx +2 -3
  214. package/src/flow/actions/openView.tsx +28 -4
  215. package/src/flow/actions/setTargetDataScope.tsx +1 -1
  216. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +11 -329
  217. package/src/flow/admin-shell/BaseLayoutModel.tsx +455 -0
  218. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +502 -0
  219. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +547 -3
  220. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +35 -7
  221. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +160 -0
  222. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +15 -13
  223. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +28 -201
  224. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +11 -2
  225. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +139 -0
  226. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +76 -27
  227. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +149 -27
  228. package/src/flow/admin-shell/admin-layout/index.ts +3 -0
  229. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +11 -0
  230. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +2 -4
  231. package/src/flow/admin-shell/admin-layout/useApplications.tsx +34 -1
  232. package/src/flow/admin-shell/useAdminLayoutRoutePage.ts +10 -26
  233. package/src/flow/admin-shell/useLayoutRoutePage.ts +61 -0
  234. package/src/flow/common/Markdown/Edit.tsx +32 -4
  235. package/src/flow/components/AdminLayout.tsx +4 -154
  236. package/src/flow/components/ConditionBuilder.tsx +12 -2
  237. package/src/flow/components/FieldAssignRulesEditor.tsx +37 -46
  238. package/src/flow/components/FieldAssignValueInput.tsx +8 -4
  239. package/src/flow/components/FlowRoute.tsx +105 -15
  240. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +112 -1
  241. package/src/flow/components/__tests__/fieldAssignOptions.test.ts +71 -1
  242. package/src/flow/components/code-editor/__tests__/runjsCompletions.test.ts +272 -27
  243. package/src/flow/components/code-editor/runjsCompletions.ts +490 -9
  244. package/src/flow/components/fieldAssignOptions.ts +23 -6
  245. package/src/flow/components/filter/LinkageFilterItem.tsx +22 -9
  246. package/src/flow/components/filter/VariableFilterItem.tsx +27 -9
  247. package/src/flow/components/filter/__tests__/fieldsToOptions.test.ts +48 -0
  248. package/src/flow/components/filter/__tests__/metaTreeAssociationDepth.test.ts +60 -0
  249. package/src/flow/components/filter/fieldsToOptions.ts +9 -4
  250. package/src/flow/components/filter/index.ts +3 -0
  251. package/src/flow/components/filter/metaTreeAssociationDepth.ts +69 -0
  252. package/src/flow/components/filter/useFilterOptions.ts +102 -0
  253. package/src/flow/index.ts +4 -0
  254. package/src/flow/models/actions/FilterActionModel.tsx +15 -3
  255. package/src/flow/models/actions/UpdateRecordActionModel.tsx +14 -95
  256. package/src/flow/models/actions/UpdateRecordActionUtils.ts +4 -7
  257. package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +116 -1
  258. package/src/flow/models/base/ActionModel.tsx +8 -1
  259. package/src/flow/models/base/AssociationFieldGroupModel.tsx +20 -3
  260. package/src/flow/models/base/GridModel.tsx +1 -1
  261. package/src/flow/models/base/PageModel/ChildPageModel.tsx +5 -1
  262. package/src/flow/models/base/PageModel/PageModel.tsx +51 -18
  263. package/src/flow/models/base/PageModel/RootPageModel.tsx +6 -13
  264. package/src/flow/models/base/PageModel/__tests__/ChildPageModel.test.ts +12 -0
  265. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +102 -1
  266. package/src/flow/models/base/RouteModel.tsx +1 -1
  267. package/src/flow/models/base/__tests__/AssociationFieldGroupModel.test.ts +86 -0
  268. package/src/flow/models/base/__tests__/GridModel.render.test.tsx +53 -0
  269. package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +12 -3
  270. package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +77 -6
  271. package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +216 -0
  272. package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +308 -0
  273. package/src/flow/models/blocks/assign-form/index.ts +1 -0
  274. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +1 -1
  275. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +6 -1
  276. package/src/flow/models/blocks/filter-manager/flow-actions/connectFields.tsx +48 -6
  277. package/src/flow/models/blocks/form/FormActionGroupModel.tsx +14 -0
  278. package/src/flow/models/blocks/form/FormActionModel.tsx +30 -3
  279. package/src/flow/models/blocks/form/FormAssociationItemModel.tsx +9 -0
  280. package/src/flow/models/blocks/form/FormItemModel.tsx +17 -1
  281. package/src/flow/models/blocks/form/__tests__/FormActionGroupModel.test.ts +46 -0
  282. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
  283. package/src/flow/models/blocks/form/__tests__/FormItemModel.defineChildren.test.ts +27 -2
  284. package/src/flow/models/blocks/form/__tests__/submitHandler.test.ts +71 -0
  285. package/src/flow/models/blocks/form/submitHandler.ts +8 -1
  286. package/src/flow/models/blocks/form/submitValues.ts +4 -1
  287. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +29 -0
  288. package/src/flow/models/blocks/form/value-runtime/runtime.ts +5 -1
  289. package/src/flow/models/blocks/table/TableBlockModel.tsx +118 -16
  290. package/src/flow/models/blocks/table/TableColumnModel.tsx +15 -5
  291. package/src/flow/models/blocks/table/TableCustomColumnModel.tsx +2 -2
  292. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +114 -0
  293. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +18 -0
  294. package/src/flow/models/fields/AssociationFieldModel/SubFormFieldModel.tsx +7 -1
  295. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +6 -7
  296. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +1 -1
  297. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -5
  298. package/src/flow/models/fields/ClickableFieldModel.tsx +9 -1
  299. package/src/flow/models/fields/CollectionSelectorFieldModel.tsx +8 -2
  300. package/src/flow/models/fields/DisplayEnumFieldModel.tsx +8 -2
  301. package/src/flow/models/fields/DisplayTimeFieldModel.tsx +1 -1
  302. package/src/flow/models/fields/TimeFieldModel.tsx +1 -1
  303. package/src/flow/models/fields/__tests__/TimeFieldModel.test.tsx +61 -0
  304. package/src/flow/models/fields/mobile-components/MobileDatePicker.tsx +19 -3
  305. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +5 -3
  306. package/src/flow/models/fields/mobile-components/__tests__/MobileDatePicker.test.tsx +94 -0
  307. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +70 -0
  308. package/src/flow/models/topbar/TopbarActionModel.tsx +1 -1
  309. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +91 -0
  310. package/src/index.ts +6 -0
  311. package/src/layout-manager/LayoutContentRoute.tsx +90 -0
  312. package/src/layout-manager/LayoutManager.tsx +185 -0
  313. package/src/layout-manager/LayoutRoute.tsx +138 -0
  314. package/src/layout-manager/__tests__/LayoutManager.test.tsx +335 -0
  315. package/src/layout-manager/__tests__/LayoutRoute.test.tsx +473 -0
  316. package/src/layout-manager/index.ts +14 -0
  317. package/src/layout-manager/types.ts +22 -0
  318. package/src/layout-manager/utils.ts +37 -0
  319. package/src/nocobase-buildin-plugin/index.tsx +69 -67
  320. package/src/nocobase-buildin-plugin/plugins/LocalePlugin.ts +1 -0
  321. package/src/settings-center/index.ts +1 -1
  322. package/src/settings-center/plugin-manager/BulkEnableButton.tsx +111 -0
  323. package/src/settings-center/plugin-manager/PluginCard.tsx +270 -0
  324. package/src/settings-center/plugin-manager/PluginDetail.tsx +195 -0
  325. package/src/settings-center/plugin-manager/index.tsx +254 -0
  326. package/src/settings-center/plugin-manager/types.ts +35 -0
  327. package/src/settings-center/utils.tsx +8 -1
  328. package/src/theme/__tests__/globalStyles.test.ts +24 -0
  329. package/src/theme/globalStyles.ts +10 -0
  330. package/src/utils/globalDeps.ts +2 -0
  331. package/es/collection-manager/interfaces/linkTo.d.ts +0 -90
  332. package/es/collection-manager/interfaces/o2o.d.ts +0 -621
  333. package/es/collection-manager/interfaces/properties/operators.d.ts +0 -294
  334. package/es/collection-manager/interfaces/subTable.d.ts +0 -172
  335. package/src/collection-manager/interfaces/linkTo.ts +0 -120
  336. package/src/collection-manager/interfaces/o2o.tsx +0 -561
  337. package/src/collection-manager/interfaces/subTable.ts +0 -218
  338. package/src/settings-center/PluginManagerPage.tsx +0 -162
@@ -9,7 +9,7 @@
9
9
 
10
10
  import React from 'react';
11
11
  import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
12
- import { beforeEach, describe, expect, it, vi } from 'vitest';
12
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
13
13
  import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
14
14
  import { observer } from '@nocobase/flow-engine';
15
15
  import { MemoryRouter } from 'react-router-dom';
@@ -45,6 +45,9 @@ describe('AdminLayoutModel menu items', () => {
45
45
  let modalConfirmMock: ReturnType<typeof vi.fn>;
46
46
 
47
47
  beforeEach(() => {
48
+ // Fixtures mount the modern client under the `v2` segment; tell the
49
+ // runtime-prefix helper so v2-runtime detection matches.
50
+ (globalThis.window as any).__nocobase_modern_client_prefix__ = 'v2';
48
51
  engine = new FlowEngine();
49
52
  modalConfirmMock = vi.fn().mockResolvedValue(true);
50
53
  engine.registerModels({
@@ -104,6 +107,10 @@ describe('AdminLayoutModel menu items', () => {
104
107
  vi.spyOn(window, 'open').mockImplementation(() => null);
105
108
  });
106
109
 
110
+ afterEach(() => {
111
+ delete (globalThis.window as any).__nocobase_modern_client_prefix__;
112
+ });
113
+
107
114
  const createRoute = (options?: Partial<import('../../../../flow-compat').NocoBaseDesktopRoute>) => ({
108
115
  id: 1,
109
116
  title: 'Page 1',
@@ -891,34 +898,9 @@ describe('AdminLayoutModel menu items', () => {
891
898
  });
892
899
  });
893
900
 
894
- it('should persist hidden setting through route repository', async () => {
895
- const updateRoute = vi.fn().mockResolvedValue(undefined);
896
- engine.context.routeRepository.updateRoute = updateRoute;
897
-
898
- const model = engine.createModel<AdminLayoutMenuItemModel>({
899
- uid: 'menu-item-hidden',
900
- use: AdminLayoutMenuItemModel,
901
- props: {
902
- route: {
903
- id: 1,
904
- title: 'Page 1',
905
- schemaUid: 'current-page',
906
- type: NocoBaseDesktopRouteType.page,
907
- hideInMenu: false,
908
- },
909
- },
910
- });
911
-
912
- const menuSettingsFlow = AdminLayoutMenuItemModel.globalFlowRegistry.getFlow('menuSettings');
913
- await menuSettingsFlow?.steps?.hidden?.beforeParamsSave?.({ model } as any, { hideInMenu: true }, {});
914
-
915
- expect(updateRoute).toHaveBeenCalledWith(1, {
916
- hideInMenu: true,
917
- });
918
- });
919
-
920
901
  it('should expose menu linkage rules only for existing menu items', async () => {
921
902
  const menuSettingsFlow = AdminLayoutMenuItemModel.globalFlowRegistry.getFlow('menuSettings');
903
+ expect(menuSettingsFlow?.steps?.hidden).toBeUndefined();
922
904
  expect(menuSettingsFlow?.steps?.linkageRules?.use).toBe('menuLinkageRules');
923
905
 
924
906
  const model = engine.createModel<AdminLayoutMenuItemModel>({
@@ -975,6 +957,73 @@ describe('AdminLayoutModel menu items', () => {
975
957
  });
976
958
  });
977
959
 
960
+ it('should save menu linkage rules without serializing runtime render props', async () => {
961
+ type SerializedFlowModel = Record<string, unknown> & {
962
+ props?: Record<string, unknown>;
963
+ subModels?: unknown;
964
+ };
965
+ const save = vi.fn(
966
+ async (targetModel: { serialize: () => SerializedFlowModel }, options?: { onlyStepParams?: boolean }) => {
967
+ const data = targetModel.serialize();
968
+ if (options?.onlyStepParams) {
969
+ delete data.subModels;
970
+ }
971
+
972
+ expect(data.props?.item).toBeUndefined();
973
+ expect(data.props?.dom).toBeUndefined();
974
+ expect(data.props?.options).toBeUndefined();
975
+ expect(data.props?.renderType).toBeUndefined();
976
+ expect(() => JSON.stringify(data)).not.toThrow();
977
+ return data;
978
+ },
979
+ );
980
+ const updateRoute = vi.fn().mockResolvedValue(undefined);
981
+ engine.setModelRepository({ save } as Parameters<FlowEngine['setModelRepository']>[0]);
982
+ engine.context.routeRepository.updateRoute = updateRoute;
983
+
984
+ const route = createRoute();
985
+ const model = engine.createModel<AdminLayoutMenuItemModel>({
986
+ uid: 'menu-item-linkage-runtime-props',
987
+ use: AdminLayoutMenuItemModel,
988
+ props: {
989
+ route,
990
+ },
991
+ });
992
+ const item = {
993
+ name: 'Page 1',
994
+ path: '/admin/page-1',
995
+ _route: route,
996
+ _model: model,
997
+ };
998
+
999
+ model.setProps({
1000
+ item,
1001
+ dom: React.createElement('span', null, 'Page 1'),
1002
+ renderType: 'item',
1003
+ options: { collapsed: false },
1004
+ });
1005
+ model.setStepParams('menuSettings', 'linkageRules', {
1006
+ value: [
1007
+ {
1008
+ key: 'r1',
1009
+ title: 'Hide menu item',
1010
+ enable: true,
1011
+ condition: { logic: '$and', items: [] },
1012
+ actions: [{ key: 'a1', name: 'linkageSetMenuItemProps', params: { value: 'hidden' } }],
1013
+ },
1014
+ ],
1015
+ });
1016
+
1017
+ await model.saveStepParams();
1018
+
1019
+ expect(save).toHaveBeenCalledWith(model, { onlyStepParams: true });
1020
+ expect(updateRoute).toHaveBeenCalledWith(1, {
1021
+ options: {
1022
+ hasPersistedMenuInstanceFlow: true,
1023
+ },
1024
+ });
1025
+ });
1026
+
978
1027
  it('should clear persisted menu linkage rules when no persisted state remains', async () => {
979
1028
  const saveModel = vi.spyOn(engine, 'saveModel').mockResolvedValue(undefined as any);
980
1029
  const destroy = vi.fn().mockResolvedValue(true);
@@ -43,6 +43,7 @@ import {
43
43
  type FlowModel,
44
44
  } from '@nocobase/flow-engine';
45
45
  import { AdminLayoutModel, getAdminLayoutModel } from '..';
46
+ import { getLayoutPageRouteName, getLayoutPageViewRouteName } from '../../../../layout-manager/utils';
46
47
  import { TopbarActionModel } from '../../../models/topbar/TopbarActionModel';
47
48
  import { UserCenterTopbarActionModel } from '../../../models/topbar/UserCenterTopbarActionModel';
48
49
  import { TopbarActionsBar } from '../TopbarActionsBar';
@@ -113,7 +114,7 @@ describe('AdminLayoutModel runtime', () => {
113
114
  }).toThrowError(/admin-layout-model/);
114
115
  });
115
116
 
116
- it('should expose live layoutContentElement on engine context', async () => {
117
+ it('should expose live layoutContentElement on layout context', async () => {
117
118
  const engine = new FlowEngine();
118
119
 
119
120
  render(
@@ -131,29 +132,99 @@ describe('AdminLayoutModel runtime', () => {
131
132
  model.setLayoutContentElement(element);
132
133
  });
133
134
 
134
- expect(engine.context.layoutContentElement).toBe(element);
135
+ expect(model.context.layoutContentElement).toBe(element);
135
136
 
136
137
  act(() => {
137
138
  model.setLayoutContentElement(null);
138
139
  });
139
140
 
140
- expect(engine.context.layoutContentElement).toBeNull();
141
+ expect(model.context.layoutContentElement).toBeNull();
141
142
  });
142
143
 
143
- it('should expose live engine currentRoute when active page changes', async () => {
144
+ it('should expose layout definition only while mounted', async () => {
145
+ const engine = new FlowEngine();
146
+ const { unmount } = render(
147
+ <FlowEngineProvider engine={engine}>
148
+ <TestAdminLayoutHost />
149
+ </FlowEngineProvider>,
150
+ );
151
+ const model = engine.getModel<TestAdminLayoutModel>('admin-layout-model');
152
+ expect(model).toBeTruthy();
153
+
154
+ expect(model.context.layout).toMatchObject({
155
+ routeName: 'admin',
156
+ routePath: '/admin',
157
+ rootRouteName: 'admin',
158
+ rootPageModelClass: 'RootPageModel',
159
+ childPageModelClass: 'ChildPageModel',
160
+ });
161
+ expect(engine.context.layout).toBeUndefined();
162
+
163
+ unmount();
164
+
165
+ expect(model.context.layout).toBeUndefined();
166
+ expect(model.context.currentRoute).toEqual({});
167
+ expect(model.context.layoutContentElement).toBeNull();
168
+ });
169
+
170
+ it('should expose layoutRoute from local layout route sync', async () => {
144
171
  const engine = new FlowEngine();
145
- const routeMap = {
146
- 'page-1': { title: 'Page 1' },
147
- 'page-2': { title: 'Page 2' },
148
- };
149
172
  engine.context.defineProperty('routeRepository', {
150
173
  value: {
151
- getRouteBySchemaUid: (pageUid: string) => routeMap[pageUid],
174
+ getRouteBySchemaUid: (pageUid: string) => ({ title: pageUid }),
152
175
  },
153
176
  });
177
+
178
+ render(
179
+ <FlowEngineProvider engine={engine}>
180
+ <TestAdminLayoutHost />
181
+ </FlowEngineProvider>,
182
+ );
183
+ const model = engine.getModel<TestAdminLayoutModel>('admin-layout-model');
184
+ expect(model).toBeTruthy();
185
+
186
+ act(() => {
187
+ model.syncLayoutRoute({
188
+ name: getLayoutPageViewRouteName('admin'),
189
+ pathname: '/admin/page-1/view/popup',
190
+ layoutBasePathname: '/admin',
191
+ });
192
+ });
193
+
194
+ await waitFor(() => {
195
+ expect(model.context.layoutRoute).toMatchObject({
196
+ type: 'page',
197
+ pageUid: 'page-1',
198
+ viewStack: [{ viewUid: 'page-1' }, { viewUid: 'popup' }],
199
+ });
200
+ expect(model.context.currentRoute.title).toBe('page-1');
201
+ });
202
+
203
+ act(() => {
204
+ model.syncLayoutRoute({
205
+ name: 'admin.settings',
206
+ pathname: '/admin/settings',
207
+ });
208
+ });
209
+
210
+ await waitFor(() => {
211
+ expect(model.context.layoutRoute).toBeNull();
212
+ expect(model.context.currentRoute).toEqual({});
213
+ });
214
+ });
215
+
216
+ it('should not consume global routes that belong to nested layouts', async () => {
217
+ const engine = new FlowEngine();
154
218
  const routeRef = observable.ref({
155
- params: { name: 'page-1' },
156
- pathname: '/admin/page-1',
219
+ name: getLayoutPageViewRouteName('admin.settings.publicForms'),
220
+ pathname: '/admin/settings/public-forms/form-1/view/popup',
221
+ params: { name: 'form-1' },
222
+ layoutBasePathname: '/admin/settings/public-forms',
223
+ });
224
+ engine.context.defineProperty('routeRepository', {
225
+ value: {
226
+ getRouteBySchemaUid: (pageUid: string) => ({ title: pageUid }),
227
+ },
157
228
  });
158
229
  engine.context.defineProperty('route', {
159
230
  get: () => routeRef.value,
@@ -169,6 +240,55 @@ describe('AdminLayoutModel runtime', () => {
169
240
  const model = engine.getModel<TestAdminLayoutModel>('admin-layout-model');
170
241
  expect(model).toBeTruthy();
171
242
 
243
+ await waitFor(() => {
244
+ expect(model.context.layoutRoute).toBeNull();
245
+ expect(model.context.currentRoute).toEqual({});
246
+ });
247
+
248
+ act(() => {
249
+ routeRef.value = {
250
+ name: getLayoutPageViewRouteName('admin.settings.publicForms'),
251
+ pathname: '/admin/settings/public-forms/form-2/view/popup',
252
+ params: { name: 'form-2' },
253
+ layoutBasePathname: '/admin/settings/public-forms',
254
+ };
255
+ });
256
+
257
+ await waitFor(() => {
258
+ expect(model.context.layoutRoute).toBeNull();
259
+ expect(model.context.currentRoute).toEqual({});
260
+ });
261
+ });
262
+
263
+ it('should expose live layout currentRoute when active page changes', async () => {
264
+ const engine = new FlowEngine();
265
+ const routeMap = {
266
+ 'page-1': { title: 'Page 1' },
267
+ 'page-2': { title: 'Page 2' },
268
+ };
269
+ engine.context.defineProperty('routeRepository', {
270
+ value: {
271
+ getRouteBySchemaUid: (pageUid: string) => routeMap[pageUid],
272
+ },
273
+ });
274
+
275
+ render(
276
+ <FlowEngineProvider engine={engine}>
277
+ <TestAdminLayoutHost />
278
+ </FlowEngineProvider>,
279
+ );
280
+
281
+ const model = engine.getModel<TestAdminLayoutModel>('admin-layout-model');
282
+ expect(model).toBeTruthy();
283
+
284
+ act(() => {
285
+ model.syncLayoutRoute({
286
+ name: getLayoutPageRouteName('admin'),
287
+ pathname: '/admin/page-1',
288
+ layoutBasePathname: '/admin',
289
+ });
290
+ });
291
+
172
292
  model.registerRoutePage('page-1', {
173
293
  active: true,
174
294
  });
@@ -177,36 +297,29 @@ describe('AdminLayoutModel runtime', () => {
177
297
  });
178
298
 
179
299
  await waitFor(() => {
180
- expect(engine.context.currentRoute.title).toBe('Page 1');
300
+ expect(model.context.currentRoute.title).toBe('Page 1');
181
301
  });
182
302
 
183
303
  act(() => {
184
- routeRef.value = {
185
- params: { name: 'page-2' },
304
+ model.syncLayoutRoute({
305
+ name: getLayoutPageRouteName('admin'),
186
306
  pathname: '/admin/page-2',
187
- };
307
+ layoutBasePathname: '/admin',
308
+ });
188
309
  });
189
310
 
190
311
  await waitFor(() => {
191
- expect(engine.context.currentRoute.title).toBe('Page 2');
312
+ expect(model.context.currentRoute.title).toBe('Page 2');
192
313
  });
193
314
  });
194
315
 
195
316
  it('should keep pageActive in sync after non-active route page updates', async () => {
196
317
  const engine = new FlowEngine();
197
- const routeRef = observable.ref({
198
- params: { name: 'page-1' },
199
- pathname: '/admin/page-1',
200
- });
201
318
  engine.context.defineProperty('routeRepository', {
202
319
  value: {
203
320
  getRouteBySchemaUid: (pageUid: string) => ({ title: pageUid }),
204
321
  },
205
322
  });
206
- engine.context.defineProperty('route', {
207
- get: () => routeRef.value,
208
- cache: false,
209
- });
210
323
 
211
324
  render(
212
325
  <FlowEngineProvider engine={engine}>
@@ -217,6 +330,14 @@ describe('AdminLayoutModel runtime', () => {
217
330
  const model = engine.getModel<TestAdminLayoutModel>('admin-layout-model');
218
331
  expect(model).toBeTruthy();
219
332
 
333
+ act(() => {
334
+ model.syncLayoutRoute({
335
+ name: getLayoutPageRouteName('admin'),
336
+ pathname: '/admin/page-1',
337
+ layoutBasePathname: '/admin',
338
+ });
339
+ });
340
+
220
341
  model.registerRoutePage('page-1', {
221
342
  active: false,
222
343
  });
@@ -236,10 +357,11 @@ describe('AdminLayoutModel runtime', () => {
236
357
  expect(routeModel.context.pageActive.value).toBe(true);
237
358
 
238
359
  act(() => {
239
- routeRef.value = {
240
- params: { name: 'page-2' },
360
+ model.syncLayoutRoute({
361
+ name: getLayoutPageRouteName('admin'),
241
362
  pathname: '/admin/page-2',
242
- };
363
+ layoutBasePathname: '/admin',
364
+ });
243
365
  });
244
366
 
245
367
  await waitFor(() => {
@@ -9,6 +9,8 @@
9
9
 
10
10
  export * from './AdminLayoutComponent';
11
11
  export * from './AdminLayoutModel';
12
+ export * from '../BaseLayoutModel';
13
+ export * from '../BaseLayoutRouteCoordinator';
12
14
  export * from './AdminLayoutSlotModels';
13
15
  export * from './AdminLayoutMenuModels';
14
16
  export * from './AdminLayoutMenuFlowUtils';
@@ -16,5 +18,6 @@ export * from './resolveAdminRouteRuntimeTarget';
16
18
  export * from './constants';
17
19
  export * from './ResetThemeTokenAndKeepAlgorithm';
18
20
  export * from './useApplications';
21
+ export * from './AppListRender';
19
22
  export * from './PinnedPluginListLite';
20
23
  export * from './mobile-layout';
@@ -23,6 +23,17 @@ const app = {
23
23
  },
24
24
  } as any;
25
25
 
26
+ // These fixtures mount the modern client under the `v2` segment; tell the
27
+ // runtime-prefix helper so `isV2AdminRuntime` detects it (the server injects
28
+ // this in production).
29
+ beforeAll(() => {
30
+ (window as any).__nocobase_modern_client_prefix__ = 'v2';
31
+ });
32
+
33
+ afterAll(() => {
34
+ delete (window as any).__nocobase_modern_client_prefix__;
35
+ });
36
+
26
37
  describe('resolveAdminRouteRuntimeTarget', () => {
27
38
  it('should resolve flowPage to v2 spa runtime target', () => {
28
39
  expect(
@@ -7,7 +7,7 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { getV2EffectiveBasePath } from '../../../authRedirect';
10
+ import { getModernClientPrefix, getV2EffectiveBasePath } from '../../../authRedirect';
11
11
  import type { BaseApplication } from '../../../BaseApplication';
12
12
  import { NocoBaseDesktopRouteType, type NocoBaseDesktopRoute } from '../../../flow-compat';
13
13
 
@@ -26,8 +26,6 @@ export type AdminRouteRuntimeTarget = {
26
26
  reason: AdminRouteRuntimeTargetReason;
27
27
  };
28
28
 
29
- const V2_PUBLIC_PATH_SUFFIX = '/v2/';
30
-
31
29
  type LocationLike = {
32
30
  pathname: string;
33
31
  search?: string;
@@ -69,7 +67,7 @@ function normalizePublicPath(value = '/') {
69
67
  }
70
68
 
71
69
  export function isV2AdminRuntime(app?: ResolveAdminRouteRuntimeTargetOptions['app']) {
72
- return !!app?.getPublicPath && normalizePublicPath(app.getPublicPath()).endsWith(V2_PUBLIC_PATH_SUFFIX);
70
+ return !!app?.getPublicPath && normalizePublicPath(app.getPublicPath()).endsWith(`/${getModernClientPrefix()}/`);
73
71
  }
74
72
 
75
73
  export function toRouterNavigationPath(pathname: string, basename?: string) {
@@ -8,8 +8,41 @@
8
8
  */
9
9
 
10
10
  import { useApp } from '../../../flow-compat';
11
+ import React from 'react';
11
12
 
12
13
  export const useApplications = () => {
13
14
  const app = useApp();
14
- return { Component: app.apps.Component };
15
+ const loadAppList = app.apps.loadAppList;
16
+ const [appList, setAppList] = React.useState([]);
17
+
18
+ React.useEffect(() => {
19
+ let canceled = false;
20
+
21
+ if (!loadAppList) {
22
+ setAppList([]);
23
+ return;
24
+ }
25
+
26
+ void Promise.resolve(loadAppList(app))
27
+ .then((list) => {
28
+ if (!canceled) {
29
+ setAppList(Array.isArray(list) ? list : []);
30
+ }
31
+ })
32
+ .catch((error) => {
33
+ console.error('[NocoBase] Failed to load application switcher list.', error);
34
+ if (!canceled) {
35
+ setAppList([]);
36
+ }
37
+ });
38
+
39
+ return () => {
40
+ canceled = true;
41
+ };
42
+ }, [app, loadAppList]);
43
+
44
+ return {
45
+ Component: app.apps.Component,
46
+ appList,
47
+ };
15
48
  };
@@ -8,14 +8,15 @@
8
8
  */
9
9
 
10
10
  import type { FlowEngine } from '@nocobase/flow-engine';
11
- import { useEffect, useRef } from 'react';
11
+ import type { RefObject } from 'react';
12
12
  import { getAdminLayoutModel, type AdminLayoutModel } from './admin-layout/AdminLayoutModel';
13
+ import { useLayoutRoutePage } from './useLayoutRoutePage';
13
14
 
14
15
  type UseAdminLayoutRoutePageOptions = {
15
16
  flowEngine: FlowEngine;
16
17
  pageUid: string;
17
18
  refreshDesktopRoutes?: () => Promise<unknown>;
18
- layoutContentRef: React.RefObject<HTMLDivElement>;
19
+ layoutContentRef: RefObject<HTMLDivElement>;
19
20
  };
20
21
 
21
22
  /**
@@ -29,28 +30,11 @@ type UseAdminLayoutRoutePageOptions = {
29
30
  */
30
31
  export function useAdminLayoutRoutePage(options: UseAdminLayoutRoutePageOptions) {
31
32
  const { flowEngine, pageUid, refreshDesktopRoutes, layoutContentRef } = options;
32
- const adminLayoutModel = getAdminLayoutModel<AdminLayoutModel>(flowEngine, { required: true });
33
- const refreshRef = useRef(refreshDesktopRoutes);
34
-
35
- refreshRef.current = refreshDesktopRoutes;
36
-
37
- useEffect(() => {
38
- adminLayoutModel.registerRoutePage(pageUid, {
39
- active: false,
40
- refreshDesktopRoutes: refreshRef.current,
41
- layoutContentElement: layoutContentRef.current,
42
- });
43
- return () => {
44
- adminLayoutModel.unregisterRoutePage(pageUid);
45
- };
46
- }, [adminLayoutModel, pageUid, layoutContentRef]);
47
-
48
- useEffect(() => {
49
- adminLayoutModel.updateRoutePage(pageUid, {
50
- refreshDesktopRoutes,
51
- layoutContentElement: layoutContentRef.current,
52
- });
53
- }, [adminLayoutModel, pageUid, refreshDesktopRoutes, layoutContentRef]);
54
-
55
- return adminLayoutModel;
33
+ return useLayoutRoutePage<AdminLayoutModel>({
34
+ flowEngine,
35
+ pageUid,
36
+ refreshDesktopRoutes,
37
+ layoutContentRef,
38
+ getLayoutModel: (engine) => getAdminLayoutModel<AdminLayoutModel>(engine, { required: true }),
39
+ });
56
40
  }
@@ -0,0 +1,61 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import type { FlowEngine } from '@nocobase/flow-engine';
11
+ import { useEffect, useRef, type RefObject } from 'react';
12
+ import type { BaseLayoutModel } from './BaseLayoutModel';
13
+
14
+ export type UseLayoutRoutePageOptions<TModel extends BaseLayoutModel = BaseLayoutModel> = {
15
+ flowEngine: FlowEngine;
16
+ pageUid: string;
17
+ active?: boolean;
18
+ refreshDesktopRoutes?: () => Promise<unknown>;
19
+ layoutContentRef: RefObject<HTMLElement>;
20
+ getLayoutModel: (flowEngine: FlowEngine) => TModel | undefined;
21
+ };
22
+
23
+ /**
24
+ * 把 FlowRoute 页面的生命周期桥接到指定 Layout host model。
25
+ */
26
+ export function useLayoutRoutePage<TModel extends BaseLayoutModel = BaseLayoutModel>(
27
+ options: UseLayoutRoutePageOptions<TModel>,
28
+ ) {
29
+ const { flowEngine, pageUid, active, refreshDesktopRoutes, layoutContentRef, getLayoutModel } = options;
30
+ const layoutModel = getLayoutModel(flowEngine);
31
+ const activeRef = useRef(active);
32
+ const refreshRef = useRef(refreshDesktopRoutes);
33
+
34
+ if (!layoutModel) {
35
+ throw new Error('[NocoBase] FlowRoute requires layout model. Please render FlowRoute under Layout.');
36
+ }
37
+
38
+ activeRef.current = active;
39
+ refreshRef.current = refreshDesktopRoutes;
40
+
41
+ useEffect(() => {
42
+ layoutModel.registerRoutePage(pageUid, {
43
+ active: activeRef.current ?? false,
44
+ refreshDesktopRoutes: refreshRef.current,
45
+ layoutContentElement: layoutContentRef.current,
46
+ });
47
+ return () => {
48
+ layoutModel.unregisterRoutePage(pageUid);
49
+ };
50
+ }, [layoutModel, pageUid, layoutContentRef]);
51
+
52
+ useEffect(() => {
53
+ layoutModel.updateRoutePage(pageUid, {
54
+ active,
55
+ refreshDesktopRoutes,
56
+ layoutContentElement: layoutContentRef.current,
57
+ });
58
+ }, [active, layoutModel, pageUid, refreshDesktopRoutes, layoutContentRef]);
59
+
60
+ return layoutModel;
61
+ }
@@ -34,13 +34,41 @@ const defaultToolbar = [
34
34
  'code',
35
35
  'inline-code',
36
36
  'upload',
37
- 'fullscreen',
38
37
  ];
39
38
 
40
39
  const NAMESPACE = 'block-markdown';
41
40
 
42
41
  const locales = ['en_US', 'fr_FR', 'pt_BR', 'ja_JP', 'ko_KR', 'ru_RU', 'sv_SE', 'zh_CN', 'zh_TW'];
43
42
 
43
+ type MarkdownToolbarItem =
44
+ | string
45
+ | {
46
+ name: string;
47
+ tipPosition?: string;
48
+ toolbar?: MarkdownToolbarItem[];
49
+ [key: string]: unknown;
50
+ };
51
+
52
+ function placeToolbarTooltipsBelow(toolbar: MarkdownToolbarItem[]) {
53
+ return toolbar.map((item) => {
54
+ if (typeof item === 'string') {
55
+ if (item === '|' || item === 'br') {
56
+ return item;
57
+ }
58
+ return {
59
+ name: item,
60
+ tipPosition: 's',
61
+ };
62
+ }
63
+
64
+ return {
65
+ ...item,
66
+ tipPosition: 's',
67
+ toolbar: item.toolbar ? placeToolbarTooltipsBelow(item.toolbar) : item.toolbar,
68
+ };
69
+ });
70
+ }
71
+
44
72
  const Edit = (props) => {
45
73
  const { disabled, onChange, value, fileCollection, toolbar, vditorRef } = props;
46
74
  const flowCtx = useFlowContext();
@@ -73,7 +101,7 @@ const Edit = (props) => {
73
101
  useEffect(() => {
74
102
  if (!containerRef.current) return;
75
103
 
76
- const toolbarConfig = toolbar ?? defaultToolbar;
104
+ const toolbarConfig = placeToolbarTooltipsBelow(toolbar ?? defaultToolbar);
77
105
  const vditor = new Vditor(containerRef.current, {
78
106
  value: value ?? '',
79
107
  lang,
@@ -321,7 +349,7 @@ export const MarkdownWithContextSelector: React.FC<MarkdownWithContextSelectorPr
321
349
  if (quoteFlag !== false) {
322
350
  setInnerValue(value);
323
351
  }
324
- }, [value]);
352
+ }, [quoteFlag, value]);
325
353
  const handleTextChange = useCallback(
326
354
  (e) => {
327
355
  const val = e ?? '';
@@ -375,7 +403,7 @@ export const MarkdownWithContextSelector: React.FC<MarkdownWithContextSelectorPr
375
403
  editor.focus();
376
404
  });
377
405
  },
378
- [innerValue, onChange],
406
+ [onChange],
379
407
  );
380
408
 
381
409
  const handleVariableSelected = useCallback(