@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.
- package/es/APIClient.d.ts +1 -3
- package/es/BaseApplication.d.ts +3 -0
- package/es/RouteRepository.d.ts +8 -0
- 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/ScanInput/useCodeScanner.d.ts +2 -1
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- 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/index.d.ts +1 -0
- package/es/entry-actions/EntryActionManager.d.ts +24 -0
- package/es/entry-actions/index.d.ts +9 -0
- package/es/flow/actions/index.d.ts +1 -1
- 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/AdminLayoutMenuModels.d.ts +1 -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/useApplications.d.ts +7 -2
- package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
- 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/models/base/ActionModelCore.d.ts +2 -0
- 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/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/routeTransientInputArgs.d.ts +14 -0
- package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +6 -0
- package/es/index.mjs +279 -158
- 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 +280 -159
- package/lib/locale/languageCodes.js +2 -1
- package/package.json +8 -7
- package/src/APIClient.ts +1 -10
- package/src/Application.tsx +4 -0
- package/src/BaseApplication.tsx +13 -6
- package/src/RouteRepository.ts +25 -0
- package/src/RouterManager.tsx +142 -1
- package/src/__tests__/RouteRepository.test.ts +23 -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__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +28 -0
- 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-validation.ts +1 -1
- 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 +4 -0
- package/src/components/form/ScanInput/CodeScanner.tsx +23 -6
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +180 -3
- package/src/components/form/ScanInput/useCodeScanner.ts +170 -5
- package/src/components/form/TypedVariableInput.tsx +452 -101
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
- 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/index.ts +1 -0
- package/src/entry-actions/EntryActionManager.ts +76 -0
- package/src/entry-actions/index.ts +10 -0
- package/src/flow/FlowPage.tsx +38 -3
- package/src/flow/__tests__/FlowPage.test.tsx +201 -27
- package/src/flow/__tests__/FlowRoute.test.tsx +549 -4
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +62 -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 +80 -20
- package/src/flow/actions/dataScopeFilter.ts +10 -1
- package/src/flow/actions/dateTimeFormat.tsx +2 -2
- package/src/flow/actions/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +86 -11
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/actions/openView.tsx +21 -1
- package/src/flow/admin-shell/BaseLayoutModel.tsx +124 -0
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +184 -42
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +1016 -119
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +41 -5
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +177 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +20 -0
- 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/__tests__/AdminLayoutComponent.test.tsx +253 -6
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutModel.test.tsx +436 -2
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +48 -0
- package/src/flow/admin-shell/admin-layout/index.ts +1 -0
- package/src/flow/admin-shell/admin-layout/useApplications.tsx +81 -12
- package/src/flow/common/Markdown/Display.tsx +14 -3
- package/src/flow/common/Markdown/Edit.tsx +19 -7
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignValueInput.tsx +91 -20
- package/src/flow/components/FlowRoute.tsx +134 -18
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
- 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/index.ts +1 -0
- package/src/flow/internal/components/Markdown/util.ts +2 -1
- package/src/flow/models/base/ActionModel.tsx +10 -8
- package/src/flow/models/base/ActionModelCore.tsx +5 -0
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
- package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
- package/src/flow/models/base/PageModel/PageTabModel.tsx +184 -3
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
- 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 +572 -1
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +1 -0
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- 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 +34 -0
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +1 -1
- package/src/flow/models/blocks/table/TableColumnModel.tsx +6 -2
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +51 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +2 -1
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +45 -13
- package/src/flow/models/fields/TextareaFieldModel.tsx +42 -19
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +32 -1
- 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 +78 -3
- package/src/flow/routeTransientInputArgs.ts +27 -0
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +42 -0
- package/src/flow-compat/fieldValidationConstants.ts +1 -1
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +6 -0
- package/src/locale/languageCodes.ts +2 -1
- package/src/nocobase-buildin-plugin/index.tsx +19 -1
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
- 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
|
-
<
|
|
315
|
-
{
|
|
316
|
-
|
|
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={
|
|
84
|
+
multiple={isMultiple}
|
|
75
85
|
value={Array.isArray(selected) ? selected : [selected]}
|
|
76
86
|
onChange={(val) => {
|
|
77
|
-
if (
|
|
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
|
-
{
|
|
95
|
-
<
|
|
96
|
-
{
|
|
97
|
-
|
|
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<
|
|
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(
|
|
141
|
-
return <Link to={
|
|
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: {
|
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: '
|
|
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 {
|
|
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({
|