@nocobase/client-v2 2.2.0-beta.8 → 2.3.0-alpha.1

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 (164) hide show
  1. package/es/APIClient.d.ts +1 -3
  2. package/es/BaseApplication.d.ts +3 -0
  3. package/es/RouteRepository.d.ts +8 -0
  4. package/es/RouterManager.d.ts +15 -0
  5. package/es/authRedirect.d.ts +1 -0
  6. package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
  7. package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
  8. package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
  9. package/es/components/category-tabs/index.d.ts +9 -0
  10. package/es/components/form/ScanInput/useCodeScanner.d.ts +2 -1
  11. package/es/components/form/TypedVariableInput.d.ts +31 -5
  12. package/es/components/form/filter/CollectionFilter.d.ts +2 -0
  13. package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
  14. package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
  15. package/es/components/form/filter/index.d.ts +2 -0
  16. package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
  17. package/es/components/index.d.ts +1 -0
  18. package/es/entry-actions/EntryActionManager.d.ts +24 -0
  19. package/es/entry-actions/index.d.ts +9 -0
  20. package/es/flow/actions/index.d.ts +1 -1
  21. package/es/flow/actions/linkageRules.d.ts +2 -0
  22. package/es/flow/admin-shell/BaseLayoutModel.d.ts +6 -0
  23. package/es/flow/admin-shell/BaseLayoutRouteCoordinator.d.ts +7 -0
  24. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
  25. package/es/flow/admin-shell/admin-layout/AppListRender.d.ts +2 -1
  26. package/es/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.d.ts +24 -0
  27. package/es/flow/admin-shell/admin-layout/index.d.ts +1 -0
  28. package/es/flow/admin-shell/admin-layout/useApplications.d.ts +7 -2
  29. package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
  30. package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
  31. package/es/flow/components/RunJSValueEditor.d.ts +1 -0
  32. package/es/flow/components/filter/FilterGroup.d.ts +1 -0
  33. package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
  34. package/es/flow/index.d.ts +1 -0
  35. package/es/flow/models/base/ActionModelCore.d.ts +2 -0
  36. package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
  37. package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
  38. package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
  39. package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
  40. package/es/flow/routeTransientInputArgs.d.ts +14 -0
  41. package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
  42. package/es/flow-compat/routeTypes.d.ts +1 -0
  43. package/es/index.d.ts +6 -0
  44. package/es/index.mjs +279 -158
  45. package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
  46. package/es/utils/index.d.ts +1 -0
  47. package/es/utils/markdownSanitize.d.ts +11 -0
  48. package/lib/index.js +280 -159
  49. package/lib/locale/languageCodes.js +2 -1
  50. package/package.json +8 -7
  51. package/src/APIClient.ts +1 -10
  52. package/src/Application.tsx +4 -0
  53. package/src/BaseApplication.tsx +13 -6
  54. package/src/RouteRepository.ts +25 -0
  55. package/src/RouterManager.tsx +142 -1
  56. package/src/__tests__/RouteRepository.test.ts +23 -0
  57. package/src/__tests__/RouterManager.test.ts +125 -0
  58. package/src/__tests__/app.test.tsx +73 -0
  59. package/src/__tests__/authRedirect.test.ts +17 -0
  60. package/src/__tests__/browserChecker.test.ts +61 -0
  61. package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
  62. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +28 -0
  63. package/src/authRedirect.ts +38 -0
  64. package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
  65. package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
  66. package/src/collection-manager/field-validation.ts +1 -1
  67. package/src/components/README.md +7 -1
  68. package/src/components/README.zh-CN.md +6 -1
  69. package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
  70. package/src/components/category-tabs/index.ts +10 -0
  71. package/src/components/form/JsonTextArea.tsx +4 -0
  72. package/src/components/form/ScanInput/CodeScanner.tsx +23 -6
  73. package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +180 -3
  74. package/src/components/form/ScanInput/useCodeScanner.ts +170 -5
  75. package/src/components/form/TypedVariableInput.tsx +452 -101
  76. package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
  77. package/src/components/form/filter/CollectionFilter.tsx +4 -0
  78. package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
  79. package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
  80. package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
  81. package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
  82. package/src/components/form/filter/index.ts +2 -0
  83. package/src/components/form/filter/useFilterActionProps.ts +37 -6
  84. package/src/components/index.ts +1 -0
  85. package/src/entry-actions/EntryActionManager.ts +76 -0
  86. package/src/entry-actions/index.ts +10 -0
  87. package/src/flow/FlowPage.tsx +38 -3
  88. package/src/flow/__tests__/FlowPage.test.tsx +201 -27
  89. package/src/flow/__tests__/FlowRoute.test.tsx +549 -4
  90. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +62 -0
  91. package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
  92. package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
  93. package/src/flow/actions/__tests__/openView.defineProps.route.test.tsx +80 -20
  94. package/src/flow/actions/dataScopeFilter.ts +10 -1
  95. package/src/flow/actions/dateTimeFormat.tsx +2 -2
  96. package/src/flow/actions/index.ts +2 -0
  97. package/src/flow/actions/linkageRules.tsx +86 -11
  98. package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
  99. package/src/flow/actions/openView.tsx +21 -1
  100. package/src/flow/admin-shell/BaseLayoutModel.tsx +124 -0
  101. package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +184 -42
  102. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1016 -119
  103. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +41 -5
  104. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +177 -1
  105. package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +20 -0
  106. package/src/flow/admin-shell/admin-layout/AppListRender.tsx +13 -2
  107. package/src/flow/admin-shell/admin-layout/AppSwitcherActionPanelModel.tsx +289 -0
  108. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
  109. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +436 -2
  110. package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +48 -0
  111. package/src/flow/admin-shell/admin-layout/index.ts +1 -0
  112. package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
  113. package/src/flow/common/Markdown/Display.tsx +14 -3
  114. package/src/flow/common/Markdown/Edit.tsx +19 -7
  115. package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
  116. package/src/flow/components/FieldAssignValueInput.tsx +91 -20
  117. package/src/flow/components/FlowRoute.tsx +134 -18
  118. package/src/flow/components/RunJSValueEditor.tsx +9 -1
  119. package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
  120. package/src/flow/components/filter/FilterGroup.tsx +33 -5
  121. package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
  122. package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
  123. package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
  124. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
  125. package/src/flow/index.ts +1 -0
  126. package/src/flow/internal/components/Markdown/util.ts +2 -1
  127. package/src/flow/models/base/ActionModel.tsx +10 -8
  128. package/src/flow/models/base/ActionModelCore.tsx +5 -0
  129. package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
  130. package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
  131. package/src/flow/models/base/PageModel/PageTabModel.tsx +184 -3
  132. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
  133. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
  134. package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
  135. package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +572 -1
  136. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +1 -0
  137. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
  138. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
  139. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +34 -0
  140. package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
  141. package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
  142. package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
  143. package/src/flow/models/blocks/table/TableBlockModel.tsx +1 -1
  144. package/src/flow/models/blocks/table/TableColumnModel.tsx +6 -2
  145. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +51 -0
  146. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +2 -1
  147. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +45 -13
  148. package/src/flow/models/fields/TextareaFieldModel.tsx +42 -19
  149. package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
  150. package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +32 -1
  151. package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
  152. package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
  153. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
  154. package/src/flow/models/topbar/TopbarActionModel.tsx +78 -3
  155. package/src/flow/routeTransientInputArgs.ts +27 -0
  156. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +42 -0
  157. package/src/flow-compat/fieldValidationConstants.ts +1 -1
  158. package/src/flow-compat/routeTypes.ts +1 -0
  159. package/src/index.ts +6 -0
  160. package/src/locale/languageCodes.ts +2 -1
  161. package/src/nocobase-buildin-plugin/index.tsx +19 -1
  162. package/src/utils/getRouteRuntimeVersion.ts +185 -0
  163. package/src/utils/index.tsx +1 -0
  164. package/src/utils/markdownSanitize.ts +88 -0
@@ -7,8 +7,9 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
- import { FlowEngine } from '@nocobase/flow-engine';
10
+ import { FieldModelRenderer, FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
11
11
  import { fireEvent, render, screen, waitFor } from '@testing-library/react';
12
+ import { Form } from 'antd';
12
13
  import React from 'react';
13
14
  import { describe, expect, it, vi } from 'vitest';
14
15
  import { TextareaFieldModel } from '../TextareaFieldModel';
@@ -23,6 +24,36 @@ function createTextareaFieldModel(props?: Record<string, unknown>) {
23
24
  }
24
25
 
25
26
  describe('TextareaFieldModel', () => {
27
+ it('syncs multiline values assigned through the form renderer', async () => {
28
+ const flowEngine = new FlowEngine();
29
+ const model = createTextareaFieldModel();
30
+ model.dispatchEvent = vi.fn().mockResolvedValue([]);
31
+
32
+ function FormHost() {
33
+ const [form] = Form.useForm();
34
+
35
+ React.useEffect(() => {
36
+ form.setFieldsValue({ address: 'aaaaaa\nbbbbbb' });
37
+ }, [form]);
38
+
39
+ return (
40
+ <FlowEngineProvider engine={flowEngine}>
41
+ <Form form={form}>
42
+ <Form.Item name="address">
43
+ <FieldModelRenderer model={model} />
44
+ </Form.Item>
45
+ </Form>
46
+ </FlowEngineProvider>
47
+ );
48
+ }
49
+
50
+ render(<FormHost />);
51
+
52
+ await waitFor(() => {
53
+ expect((screen.getByRole('textbox') as HTMLTextAreaElement).value).toBe('aaaaaa\nbbbbbb');
54
+ });
55
+ });
56
+
26
57
  it('keeps IME composition text visible before the parent value is committed', () => {
27
58
  const onChange = vi.fn();
28
59
  const onCompositionStart = vi.fn();
@@ -0,0 +1,51 @@
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 { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
11
+ import { render, screen, waitFor } from '@testing-library/react';
12
+ import React from 'react';
13
+ import { describe, expect, it } from 'vitest';
14
+ import { InputFieldModel } from '../InputFieldModel';
15
+ import { VariableFieldFormModel } from '../VariableFieldFormModel';
16
+
17
+ describe('VariableFieldFormModel', () => {
18
+ it('uses DOM-safe name and id for temporary controls created from reserved form property names', async () => {
19
+ const engine = new FlowEngine();
20
+ engine.registerModels({ InputFieldModel, VariableFieldFormModel });
21
+ const model = engine.createModel<VariableFieldFormModel>({
22
+ use: 'VariableFieldFormModel',
23
+ subModels: {
24
+ fields: [
25
+ {
26
+ use: 'InputFieldModel',
27
+ stepParams: {
28
+ fieldSettings: {
29
+ init: {
30
+ fieldPath: 'nodeName',
31
+ },
32
+ },
33
+ },
34
+ },
35
+ ],
36
+ },
37
+ });
38
+ const field = model.subModels.fields[0];
39
+
40
+ render(<FlowEngineProvider engine={engine}>{model.render()}</FlowEngineProvider>);
41
+
42
+ const input = await screen.findByRole('textbox');
43
+ await waitFor(() => {
44
+ expect(field.props.name).toEqual(['__nb_variable_field_nodeName']);
45
+ });
46
+ expect(input).toHaveAttribute('name', '__nb_variable_field_nodeName');
47
+ expect(input).toHaveAttribute('id', '__nb_variable_field_nodeName');
48
+ expect(field.props.id).toEqual(['__nb_variable_field_nodeName']);
49
+ expect(field.getStepParams('fieldSettings', 'init')).toEqual({ fieldPath: 'nodeName' });
50
+ });
51
+ });
@@ -23,6 +23,20 @@ import {
23
23
  } from '../AssociationFieldModel/recordSelectShared';
24
24
  import _ from 'lodash';
25
25
 
26
+ const mobileSelectSafeAreaPaddingBottom = 'calc(12px + env(safe-area-inset-bottom, 0px))';
27
+
28
+ const mobileSelectConfirmFooterStyle: CSSProperties = {
29
+ paddingBottom: mobileSelectSafeAreaPaddingBottom,
30
+ };
31
+
32
+ function getMobileSelectListStyle(hasConfirmFooter: boolean): CSSProperties {
33
+ return {
34
+ maxHeight: '60vh',
35
+ overflowY: 'auto',
36
+ paddingBottom: hasConfirmFooter ? undefined : mobileSelectSafeAreaPaddingBottom,
37
+ };
38
+ }
39
+
26
40
  const labelClassName = css`
27
41
  div {
28
42
  white-space: nowrap !important;
@@ -280,13 +294,7 @@ export function MobileLazySelect(props: Readonly<LazySelectProps>) {
280
294
  showCancelButton
281
295
  />
282
296
  </div>
283
- <div
284
- style={{
285
- maxHeight: '60vh',
286
- overflowY: 'auto',
287
- }}
288
- onScroll={handleScroll}
289
- >
297
+ <div style={getMobileSelectListStyle(isMultiple)} onScroll={handleScroll}>
290
298
  <CheckList multiple={isMultiple} value={selectedValueIds} onChange={handleListChange}>
291
299
  {realOptions.map((item) => {
292
300
  const optionValue = item?.[valueKey];
@@ -311,9 +319,11 @@ export function MobileLazySelect(props: Readonly<LazySelectProps>) {
311
319
  )}
312
320
  </div>
313
321
  {isMultiple && (
314
- <Button block color="primary" onClick={handleConfirm} style={{ marginTop: '16px' }}>
315
- {t('Confirm')}
316
- </Button>
322
+ <div style={mobileSelectConfirmFooterStyle}>
323
+ <Button block color="primary" onClick={handleConfirm} style={{ marginTop: '16px' }}>
324
+ {t('Confirm')}
325
+ </Button>
326
+ </div>
317
327
  )}
318
328
  </Popup>
319
329
  </>
@@ -12,8 +12,23 @@ import { Select } from 'antd';
12
12
  import { Button, CheckList, Popup, SearchBar } from 'antd-mobile';
13
13
  import React, { useEffect, useMemo, useState } from 'react';
14
14
 
15
+ const mobileSelectSafeAreaPaddingBottom = 'calc(12px + env(safe-area-inset-bottom, 0px))';
16
+
17
+ const mobileSelectConfirmFooterStyle: React.CSSProperties = {
18
+ paddingBottom: mobileSelectSafeAreaPaddingBottom,
19
+ };
20
+
21
+ function getMobileSelectListStyle(hasConfirmFooter: boolean): React.CSSProperties {
22
+ return {
23
+ maxHeight: '60vh',
24
+ overflowY: 'auto',
25
+ paddingBottom: hasConfirmFooter ? undefined : mobileSelectSafeAreaPaddingBottom,
26
+ };
27
+ }
28
+
15
29
  export function MobileSelect(props) {
16
30
  const { value, displayValue, onChange, onChangeComplete, disabled, options = [], mode } = props;
31
+ const isMultiple = ['multiple', 'tags'].includes(mode);
17
32
  const ctx = useFlowModelContext();
18
33
  const t = ctx.t;
19
34
  const [visible, setVisible] = useState(false);
@@ -64,17 +79,12 @@ export function MobileSelect(props) {
64
79
  <div style={{ margin: '10px' }}>
65
80
  <SearchBar placeholder={t('search')} value={searchText} onChange={(v) => setSearchText(v)} showCancelButton />
66
81
  </div>
67
- <div
68
- style={{
69
- maxHeight: '60vh',
70
- overflowY: 'auto',
71
- }}
72
- >
82
+ <div style={getMobileSelectListStyle(isMultiple)}>
73
83
  <CheckList
74
- multiple={['multiple', 'tags'].includes(mode)}
84
+ multiple={isMultiple}
75
85
  value={Array.isArray(selected) ? selected : [selected]}
76
86
  onChange={(val) => {
77
- if (['multiple', 'tags'].includes(mode)) {
87
+ if (isMultiple) {
78
88
  setSelected(val);
79
89
  } else {
80
90
  setSelected(val[0]);
@@ -91,10 +101,12 @@ export function MobileSelect(props) {
91
101
  ))}
92
102
  </CheckList>
93
103
  </div>
94
- {['multiple', 'tags'].includes(mode) && (
95
- <Button block color="primary" onClick={handleConfirm} style={{ marginTop: '16px' }}>
96
- {t('Confirm')}
97
- </Button>
104
+ {isMultiple && (
105
+ <div style={mobileSelectConfirmFooterStyle}>
106
+ <Button block color="primary" onClick={handleConfirm} style={{ marginTop: '16px' }}>
107
+ {t('Confirm')}
108
+ </Button>
109
+ </div>
98
110
  )}
99
111
  </Popup>
100
112
  </>
@@ -18,6 +18,7 @@ import { Link, useLocation } from 'react-router-dom';
18
18
  import { useACLRoleContext } from '../../../acl';
19
19
  import type { BaseApplication } from '../../../BaseApplication';
20
20
  import type { PluginSettingsPageType } from '../../../PluginSettingsManager';
21
+ import { shouldOpenAdminRouteInNewWindow } from '../../../RouterManager';
21
22
  import { useApp } from '../../../hooks/useApp';
22
23
  import {
23
24
  filterRenderableSettings,
@@ -65,7 +66,10 @@ const topbarActionTriggerClassName = css`
65
66
  height: 100%;
66
67
  `;
67
68
 
68
- type TopbarSettingsAppLike = Pick<BaseApplication<any>, 'router' | 'getPublicPath'>;
69
+ type TopbarSettingsAppLike = Pick<
70
+ BaseApplication<any>,
71
+ 'name' | 'router' | 'getPublicPath' | 'getHref' | 'layoutManager'
72
+ >;
69
73
 
70
74
  const normalizeTopbarPath = (pathname?: string) => {
71
75
  const trimmed = pathname?.trim();
@@ -103,10 +107,57 @@ const stripTopbarRouterBasePath = (pathname: string, basename?: string) => {
103
107
  return normalizedPath;
104
108
  };
105
109
 
110
+ const getTopbarAppPath = (pathname: string) => {
111
+ const normalizedPath = normalizeTopbarPath(pathname);
112
+ const match = /^(.*?\/(?:apps|_app)\/[^/]+)(?=\/|$)/.exec(normalizedPath);
113
+ const appPath = match?.[1] || '';
114
+
115
+ return {
116
+ appPath,
117
+ routePath: appPath ? normalizeTopbarPath(normalizedPath.slice(appPath.length)) : normalizedPath,
118
+ };
119
+ };
120
+
121
+ const getTopbarContextAppPath = (app: TopbarSettingsAppLike | undefined, basename?: string) => {
122
+ const basenameAppPath = getTopbarAppPath(basename || '').appPath;
123
+ if (basenameAppPath) {
124
+ return basenameAppPath;
125
+ }
126
+
127
+ const publicPathAppPath = getTopbarAppPath(app?.getPublicPath?.() || '').appPath;
128
+ if (publicPathAppPath) {
129
+ return publicPathAppPath;
130
+ }
131
+
132
+ if (app?.name && app.name !== 'main' && app.getHref) {
133
+ return normalizeTopbarBasePath(app.getHref('/'));
134
+ }
135
+
136
+ return '';
137
+ };
138
+
139
+ const prependTopbarAppPath = (pathname: string, appPath: string) => {
140
+ const normalizedPath = normalizeTopbarPath(pathname);
141
+
142
+ if (!appPath || normalizedPath === appPath || normalizedPath.startsWith(`${appPath}/`)) {
143
+ return normalizedPath;
144
+ }
145
+
146
+ return `${appPath}${normalizedPath}`;
147
+ };
148
+
106
149
  const isAdminRuntimePath = (pathname: string) => {
107
150
  return pathname === '/admin' || pathname.startsWith('/admin/');
108
151
  };
109
152
 
153
+ const getTopbarAdminRoutePath = (app: TopbarSettingsAppLike | undefined) => {
154
+ try {
155
+ return app?.layoutManager?.getLayout?.('admin')?.routePath;
156
+ } catch {
157
+ return undefined;
158
+ }
159
+ };
160
+
110
161
  const buildTopbarDocumentHref = (targetPath: string, basename?: string) => {
111
162
  const normalizedTarget = normalizeTopbarPath(targetPath);
112
163
  const normalizedBase = normalizeTopbarBasePath(basename);
@@ -136,9 +187,33 @@ function TopbarInternalSettingsLabel(props: { title: React.ReactNode; path?: str
136
187
  const targetPath = props.path || '/admin/settings';
137
188
  const basename = getTopbarRouterBasePath(app);
138
189
  const currentPath = stripTopbarRouterBasePath(location.pathname, basename);
190
+ const currentLocationAppPath = getTopbarAppPath(currentPath);
191
+ const currentAppPath = currentLocationAppPath.appPath || getTopbarContextAppPath(app, basename);
192
+ const currentRoutePath = currentLocationAppPath.appPath ? currentLocationAppPath.routePath : currentPath;
193
+ const targetPathInCurrentApp = prependTopbarAppPath(stripTopbarRouterBasePath(targetPath, basename), currentAppPath);
194
+
195
+ if (currentAppPath) {
196
+ const href = buildTopbarDocumentHref(targetPathInCurrentApp, basename);
197
+ const shouldOpenInNewWindow = shouldOpenAdminRouteInNewWindow({
198
+ currentPathname: currentPath,
199
+ targetPathname: targetPathInCurrentApp,
200
+ basePath: basename,
201
+ adminRoutePath: getTopbarAdminRoutePath(app),
202
+ });
203
+
204
+ return (
205
+ <a
206
+ href={href}
207
+ target={shouldOpenInNewWindow ? '_blank' : undefined}
208
+ rel={shouldOpenInNewWindow ? 'noopener noreferrer' : undefined}
209
+ >
210
+ {props.title}
211
+ </a>
212
+ );
213
+ }
139
214
 
140
- if (isAdminRuntimePath(currentPath)) {
141
- return <Link to={targetPath}>{props.title}</Link>;
215
+ if (isAdminRuntimePath(currentRoutePath)) {
216
+ return <Link to={targetPathInCurrentApp}>{props.title}</Link>;
142
217
  }
143
218
 
144
219
  return (
@@ -0,0 +1,27 @@
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
+ export const ROUTE_TRANSIENT_INPUT_ARGS_KEY = '__nocobaseOpenViewInputArgs';
11
+
12
+ export type RouteTransientInputArgsState = {
13
+ [ROUTE_TRANSIENT_INPUT_ARGS_KEY]?: Record<string, Record<string, unknown>>;
14
+ usr?: RouteTransientInputArgsState;
15
+ };
16
+
17
+ export const getRouteTransientInputArgs = (state: unknown, viewUid?: string) => {
18
+ if (!viewUid || !state || typeof state !== 'object') {
19
+ return {};
20
+ }
21
+
22
+ const stateValue = state as RouteTransientInputArgsState;
23
+ const values = (stateValue[ROUTE_TRANSIENT_INPUT_ARGS_KEY] || stateValue.usr?.[ROUTE_TRANSIENT_INPUT_ARGS_KEY])?.[
24
+ viewUid
25
+ ];
26
+ return values && typeof values === 'object' ? values : {};
27
+ };
@@ -207,6 +207,48 @@ describe('dateTimeFormat', () => {
207
207
  expect(save).toHaveBeenCalled();
208
208
  });
209
209
 
210
+ it('syncs ordinary table column props when date time format settings are saved', async () => {
211
+ const setProps = vi.fn();
212
+ const save = vi.fn();
213
+ const setParentProps = vi.fn();
214
+ const model = {
215
+ context: {
216
+ collectionField: {
217
+ type: 'datetime',
218
+ interface: 'datetime',
219
+ },
220
+ },
221
+ setProps,
222
+ save,
223
+ parent: {
224
+ use: 'TableColumnModel',
225
+ setProps: setParentProps,
226
+ },
227
+ };
228
+ model.parent['subModels'] = {
229
+ field: model,
230
+ };
231
+
232
+ await dateTimeFormat.beforeParamsSave(
233
+ { model },
234
+ {
235
+ picker: 'date',
236
+ dateFormat: 'YYYY-MM-DD',
237
+ showTime: true,
238
+ timeFormat: 'HH:mm:ss',
239
+ },
240
+ );
241
+
242
+ expect(setParentProps).toHaveBeenCalledWith({
243
+ picker: 'date',
244
+ dateFormat: 'YYYY-MM-DD',
245
+ showTime: true,
246
+ timeFormat: 'HH:mm:ss',
247
+ format: 'YYYY-MM-DD HH:mm:ss',
248
+ });
249
+ expect(save).toHaveBeenCalled();
250
+ });
251
+
210
252
  it('hides time format for association title date-only fields', () => {
211
253
  const ctx = {
212
254
  model: {
@@ -231,7 +231,7 @@ export const FIELDS_VALIDATION_OPTIONS = {
231
231
  },
232
232
  {
233
233
  key: 'multiple',
234
- label: 'Multiple',
234
+ label: 'Multiple of',
235
235
  hasValue: true,
236
236
  params: [{ key: 'base', label: 'Base', componentType: 'inputNumber', required: true }],
237
237
  },
@@ -18,6 +18,7 @@ export enum NocoBaseDesktopRouteType {
18
18
 
19
19
  export interface NocoBaseDesktopRouteOptions {
20
20
  hasPersistedMenuInstanceFlow?: boolean;
21
+ hasPersistedPageTabFlowModel?: boolean;
21
22
  [key: string]: any;
22
23
  }
23
24
 
package/src/index.ts CHANGED
@@ -30,6 +30,8 @@ export * from './layout-manager';
30
30
  export * from './hooks';
31
31
  export { default as languageCodes } from './locale/languageCodes';
32
32
  export * from './nocobase-buildin-plugin';
33
+ export { getRouteRuntimeVersion } from './utils/getRouteRuntimeVersion';
34
+ export type { RouteRuntimeVersion } from './utils/getRouteRuntimeVersion';
33
35
  export * from './collection-field-interface/CollectionFieldInterface';
34
36
  export * from './collection-field-interface/CollectionFieldInterfaceManager';
35
37
  export * from './collection-manager/field-configure';
@@ -38,7 +40,10 @@ export * from './collection-manager/filter-operators';
38
40
  export * from './collection-manager/interfaces';
39
41
  export * from './collection-manager/template-fields';
40
42
  export * from './data-source';
43
+ export * from './entry-actions';
41
44
  export * from './flow';
45
+ export { CodeEditorExtension } from './flow/components/code-editor/extension';
46
+ export type { CodeEditorExtra, CodeEditorExtraRegistry, EditorRef } from './flow/components/code-editor/types';
42
47
  export {
43
48
  DEFAULT_DATA_SOURCE_KEY,
44
49
  IconPicker,
@@ -47,4 +52,5 @@ export {
47
52
  NocoBaseDesktopRouteType,
48
53
  } from './flow-compat';
49
54
  export type { NocoBaseDesktopRoute } from './flow-compat';
55
+ export * from './utils/markdownSanitize';
50
56
  export { default as AntdAppProvider } from './theme/AntdAppProvider';
@@ -73,7 +73,8 @@ export const languageCodes: Record<string, LocaleOptions> = {
73
73
  'tk-TK': { label: 'Turkmen' },
74
74
  'tr-TR': { label: 'Türkçe' },
75
75
  'uk-UA': { label: 'Українська' },
76
- 'ur-PK': { label: 'Oʻzbekcha' },
76
+ 'ur-PK': { label: 'اردو' },
77
+ 'uz-UZ': { label: 'Oʻzbekcha' },
77
78
  'vi-VN': { label: 'Tiếng Việt' },
78
79
  'zh-CN': { label: '简体中文' },
79
80
  'zh-HK': { label: '繁體中文(香港)' },
@@ -15,7 +15,12 @@ import type { Application } from '../Application';
15
15
  import { getCurrentV2RedirectPath, getDefaultV2AdminRedirectPath } from '../authRedirect';
16
16
  import { AppNotFound } from '../components';
17
17
  import { PluginFlowEngine } from '../flow';
18
- import { ADMIN_LAYOUT_MODEL_UID, AdminLayoutMenuItemModel, AdminLayoutModel } from '../flow/admin-shell/admin-layout';
18
+ import {
19
+ ADMIN_LAYOUT_MODEL_UID,
20
+ AdminLayoutMenuItemModel,
21
+ AdminLayoutModel,
22
+ AppSwitcherActionPanelModel,
23
+ } from '../flow/admin-shell/admin-layout';
19
24
  import { useApp } from '../hooks/useApp';
20
25
  import { Plugin } from '../Plugin';
21
26
  import { AdminSettingsLayoutModel } from '../settings-center';
@@ -235,6 +240,16 @@ const CurrentUserProvider: FC = ({ children }) => {
235
240
  return;
236
241
  }
237
242
 
243
+ try {
244
+ await app.apiClient.auth.syncCookies();
245
+ } catch {
246
+ // Cookie bootstrap is best-effort; auth:check remains the source of truth for the current page load.
247
+ }
248
+
249
+ if (!mounted) {
250
+ return;
251
+ }
252
+
238
253
  const userMeta = createCollectionContextMeta(
239
254
  () => app.flowEngine.context.dataSourceManager?.getDataSource('main')?.getCollection('users') || null,
240
255
  app.flowEngine.translate('Current user'),
@@ -293,6 +308,8 @@ const CurrentUserProvider: FC = ({ children }) => {
293
308
  return <CurrentUserContext.Provider value={contextValue}>{children}</CurrentUserContext.Provider>;
294
309
  };
295
310
 
311
+ CurrentUserProvider.displayName = 'CurrentUserProvider';
312
+
296
313
  const RootRedirect: FC = () => {
297
314
  const app = useApp<Application>();
298
315
  const hasToken = !!app?.apiClient?.auth?.token;
@@ -326,6 +343,7 @@ export class NocoBaseBuildInPlugin extends Plugin<any, Application> {
326
343
  this.app.flowEngine.registerModels({
327
344
  AdminLayoutModel,
328
345
  AdminLayoutMenuItemModel,
346
+ AppSwitcherActionPanelModel,
329
347
  AdminSettingsLayoutModel,
330
348
  });
331
349
  this.app.layoutManager.registerLayout({