@nocobase/client-v2 2.1.0-beta.33 → 2.1.0-beta.35
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 +16 -0
- package/es/Application.d.ts +2 -1
- package/es/BaseApplication.d.ts +6 -0
- package/es/PluginManager.d.ts +2 -0
- package/es/authRedirect.d.ts +9 -16
- package/es/components/form/EnvVariableInput.d.ts +8 -6
- package/es/components/form/VariableInput.d.ts +73 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/form/table/RowOverlayPreview.d.ts +27 -0
- package/es/components/form/table/SelectionCell.d.ts +36 -0
- package/es/components/form/table/Table.d.ts +82 -0
- package/es/components/form/table/constants.d.ts +15 -0
- package/es/components/form/table/dnd/SortableRow.d.ts +40 -0
- package/es/components/form/table/dnd/index.d.ts +9 -0
- package/es/components/form/table/index.d.ts +9 -0
- package/es/components/form/table/styles.d.ts +41 -0
- package/es/components/form/table/utils.d.ts +44 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/components/TextAreaWithContextSelector.d.ts +15 -0
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +9 -1
- package/es/flow/models/blocks/table/dragSort/dragSortComponents.d.ts +1 -6
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +5 -1
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.d.ts +1 -0
- package/es/index.mjs +166 -99
- package/es/json-logic/globalOperators.d.ts +11 -0
- package/es/theme/globalStyles.d.ts +9 -0
- package/es/theme/index.d.ts +1 -0
- package/es/utils/globalDeps.d.ts +7 -0
- package/lib/index.js +173 -106
- package/package.json +9 -6
- package/src/APIClient.ts +68 -0
- package/src/Application.tsx +6 -2
- package/src/BaseApplication.tsx +8 -0
- package/src/PluginManager.ts +2 -0
- package/src/__tests__/app.test.tsx +8 -0
- package/src/__tests__/authRedirect.test.ts +170 -64
- package/src/__tests__/globalDeps.test.ts +2 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +6 -6
- package/src/__tests__/remotePlugins.test.ts +148 -0
- package/src/authRedirect.ts +23 -84
- package/src/components/form/EnvVariableInput.tsx +11 -46
- package/src/components/form/VariableInput.tsx +177 -0
- package/src/components/form/__tests__/EnvVariableInput.test.tsx +175 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/form/table/RowOverlayPreview.tsx +51 -0
- package/src/components/form/table/SelectionCell.tsx +72 -0
- package/src/components/form/table/Table.tsx +279 -0
- package/src/components/form/table/__tests__/Table.pagination.test.tsx +80 -0
- package/src/components/form/table/constants.ts +16 -0
- package/src/components/form/table/dnd/SortableRow.tsx +106 -0
- package/src/components/form/table/dnd/index.ts +10 -0
- package/src/components/form/table/index.tsx +13 -0
- package/src/components/form/table/styles.ts +110 -0
- package/src/components/form/table/utils.ts +75 -0
- package/src/components/index.ts +2 -0
- package/src/css-variable/CSSVariableProvider.tsx +1 -1
- package/src/flow/actions/filterFormDefaultValues.tsx +1 -2
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +2 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +111 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +2 -1
- package/src/flow/components/TextAreaWithContextSelector.tsx +30 -6
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +81 -0
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +34 -2
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +329 -5
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +337 -0
- package/src/flow/models/blocks/table/dragSort/dragSortComponents.tsx +1 -81
- package/src/flow/models/fields/JSEditableFieldModel.tsx +107 -7
- package/src/flow/models/fields/__tests__/JSEditableFieldModel.test.tsx +97 -0
- package/src/index.ts +1 -0
- package/src/json-logic/globalOperators.js +731 -0
- package/src/nocobase-buildin-plugin/index.tsx +4 -4
- package/src/theme/globalStyles.ts +21 -0
- package/src/theme/index.tsx +1 -0
- package/src/utils/globalDeps.ts +50 -30
- package/src/utils/remotePlugins.ts +107 -6
|
@@ -0,0 +1,106 @@
|
|
|
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 { MenuOutlined } from '@ant-design/icons';
|
|
11
|
+
import { TinyColor } from '@ctrl/tinycolor';
|
|
12
|
+
import { useSortable } from '@dnd-kit/sortable';
|
|
13
|
+
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
|
|
14
|
+
import type { DraggableAttributes } from '@dnd-kit/core';
|
|
15
|
+
import { css } from '@emotion/css';
|
|
16
|
+
import { theme } from 'antd';
|
|
17
|
+
import classNames from 'classnames';
|
|
18
|
+
import React, { useMemo } from 'react';
|
|
19
|
+
|
|
20
|
+
type DragSortRowContextValue = {
|
|
21
|
+
attributes?: DraggableAttributes;
|
|
22
|
+
listeners?: SyntheticListenerMap;
|
|
23
|
+
setActivatorNodeRef?: (node: HTMLElement | null) => void;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const DragSortRowContext = React.createContext<DragSortRowContextValue | null>(null);
|
|
27
|
+
|
|
28
|
+
const sortHandleClass = css`
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
justify-content: center;
|
|
32
|
+
cursor: grab;
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Activator handle that initiates a row drag. Reads `attributes` / `listeners`
|
|
37
|
+
* / `setActivatorNodeRef` from the surrounding `DragSortRowContext` provided
|
|
38
|
+
* by `SortableRow`, so the handle can sit anywhere within the row's cells
|
|
39
|
+
* (typically a dedicated first column).
|
|
40
|
+
*/
|
|
41
|
+
export const SortHandle: React.FC<{ id?: string | number; style?: React.CSSProperties }> = (props) => {
|
|
42
|
+
const { id: _id, ...otherProps } = props;
|
|
43
|
+
const dragSortContext = React.useContext(DragSortRowContext);
|
|
44
|
+
return (
|
|
45
|
+
<span
|
|
46
|
+
ref={dragSortContext?.setActivatorNodeRef}
|
|
47
|
+
{...dragSortContext?.attributes}
|
|
48
|
+
{...dragSortContext?.listeners}
|
|
49
|
+
{...otherProps}
|
|
50
|
+
className={classNames(sortHandleClass)}
|
|
51
|
+
>
|
|
52
|
+
<MenuOutlined />
|
|
53
|
+
</span>
|
|
54
|
+
);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Drop-in replacement for antd Table's `<tr>` body row that wires `useSortable`
|
|
59
|
+
* keyed by the `data-row-key` attribute antd injects. Pass via
|
|
60
|
+
* `Table.components.body.row` and wrap the `<tbody>` with `DndContext` +
|
|
61
|
+
* `SortableContext`. The `DragSortRowContext` it provides lets `SortHandle`
|
|
62
|
+
* be placed anywhere inside the row, not just on the row itself.
|
|
63
|
+
*/
|
|
64
|
+
export const SortableRow: React.FC<{
|
|
65
|
+
rowIndex?: number;
|
|
66
|
+
className?: string;
|
|
67
|
+
[key: string]: any;
|
|
68
|
+
}> = (props) => {
|
|
69
|
+
const { token }: any = theme.useToken();
|
|
70
|
+
const id = props['data-row-key']?.toString();
|
|
71
|
+
const { setNodeRef, setActivatorNodeRef, attributes, listeners, active, over } = useSortable({
|
|
72
|
+
id,
|
|
73
|
+
});
|
|
74
|
+
const { rowIndex, ...others } = props;
|
|
75
|
+
const isOver = over?.id === id;
|
|
76
|
+
const classObj = useMemo(() => {
|
|
77
|
+
const borderColor = new TinyColor(token.colorPrimary).setAlpha(0.6).toHex8String();
|
|
78
|
+
return {
|
|
79
|
+
topActiveClass: css`
|
|
80
|
+
& > td {
|
|
81
|
+
border-top: 2px solid ${borderColor} !important;
|
|
82
|
+
}
|
|
83
|
+
`,
|
|
84
|
+
bottomActiveClass: css`
|
|
85
|
+
& > td {
|
|
86
|
+
border-bottom: 2px solid ${borderColor} !important;
|
|
87
|
+
}
|
|
88
|
+
`,
|
|
89
|
+
};
|
|
90
|
+
}, [token.colorPrimary]);
|
|
91
|
+
|
|
92
|
+
const className =
|
|
93
|
+
(active?.data.current?.sortable.index ?? -1) > rowIndex ? classObj.topActiveClass : classObj.bottomActiveClass;
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<DragSortRowContext.Provider value={{ listeners, attributes, setActivatorNodeRef }}>
|
|
97
|
+
<tr
|
|
98
|
+
ref={(node) => {
|
|
99
|
+
setNodeRef(node);
|
|
100
|
+
}}
|
|
101
|
+
{...others}
|
|
102
|
+
className={classNames(props.className, { [className]: active && isOver })}
|
|
103
|
+
/>
|
|
104
|
+
</DragSortRowContext.Provider>
|
|
105
|
+
);
|
|
106
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
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 * from './SortableRow';
|
|
@@ -0,0 +1,13 @@
|
|
|
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
|
+
// Public surface of the v2 table primitive. Internal helpers (utils, styles,
|
|
11
|
+
// SelectionCell, RowOverlayPreview, etc.) stay unexported — they're
|
|
12
|
+
// implementation details of `Table` and are not part of the package API.
|
|
13
|
+
export * from './Table';
|
|
@@ -0,0 +1,110 @@
|
|
|
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 { css } from '@emotion/css';
|
|
11
|
+
import { SORT_HANDLE_GUTTER } from './constants';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Reserve a `SORT_HANDLE_GUTTER`-wide gap on the left of the rowSelection
|
|
15
|
+
* column so the handle's `left:0` lands inside a `position:relative` cell.
|
|
16
|
+
* Padding is mirrored on both the header `<th>` and body `<td>` so the
|
|
17
|
+
* "select all" checkbox stays vertically aligned with body checkboxes.
|
|
18
|
+
*
|
|
19
|
+
* The class is a module-level constant — emotion's hash is stable across
|
|
20
|
+
* re-renders, so the caller doesn't need a `useMemo` to keep referential
|
|
21
|
+
* equality.
|
|
22
|
+
*/
|
|
23
|
+
export const selectionGutterClassName = css`
|
|
24
|
+
.ant-table-thead > tr > th.ant-table-selection-column,
|
|
25
|
+
.ant-table-tbody > tr > td.ant-table-selection-column {
|
|
26
|
+
padding-left: ${SORT_HANDLE_GUTTER}px !important;
|
|
27
|
+
position: relative;
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Index ↔ checkbox hover swap CSS. Both `.nb-table-index` and the antd
|
|
33
|
+
* checkbox (wrapped in `.nb-origin-node`) are absolutely positioned and
|
|
34
|
+
* centered inside `.nb-row-selection-cell`, so they share the same anchor
|
|
35
|
+
* without one displacing the other. `display: none/flex` flips so they never
|
|
36
|
+
* overlap mid-transition.
|
|
37
|
+
*/
|
|
38
|
+
export const indexSwapClassName = css`
|
|
39
|
+
.ant-table-tbody > tr > td.ant-table-selection-column {
|
|
40
|
+
.nb-row-selection-cell {
|
|
41
|
+
position: relative;
|
|
42
|
+
display: inline-block;
|
|
43
|
+
min-width: 22px;
|
|
44
|
+
min-height: 22px;
|
|
45
|
+
vertical-align: middle;
|
|
46
|
+
}
|
|
47
|
+
.nb-row-selection-cell .nb-table-index,
|
|
48
|
+
.nb-row-selection-cell .nb-origin-node {
|
|
49
|
+
position: absolute;
|
|
50
|
+
inset: 0;
|
|
51
|
+
display: flex;
|
|
52
|
+
align-items: center;
|
|
53
|
+
justify-content: center;
|
|
54
|
+
}
|
|
55
|
+
.nb-row-selection-cell .nb-origin-node {
|
|
56
|
+
display: none;
|
|
57
|
+
}
|
|
58
|
+
.nb-row-selection-cell.checked .nb-table-index {
|
|
59
|
+
display: none;
|
|
60
|
+
}
|
|
61
|
+
.nb-row-selection-cell.checked .nb-origin-node {
|
|
62
|
+
display: flex;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
.ant-table-tbody > tr:hover > td.ant-table-selection-column {
|
|
66
|
+
.nb-row-selection-cell .nb-table-index {
|
|
67
|
+
display: none;
|
|
68
|
+
}
|
|
69
|
+
.nb-row-selection-cell .nb-origin-node {
|
|
70
|
+
display: flex;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Self-contained styles for the drag-overlay clone. The runtime
|
|
77
|
+
* `selectionGutterClassName` + `indexSwapClassName` are scoped to AntdTable's
|
|
78
|
+
* emotion hash, so they don't reach the cloned `<tr>` injected via
|
|
79
|
+
* `dangerouslySetInnerHTML`. This class replays the minimal subset that the
|
|
80
|
+
* clone needs:
|
|
81
|
+
* - selection column gutter + `position: relative` so the absolute handle
|
|
82
|
+
* lands correctly
|
|
83
|
+
* - hide the row index inside the overlay (drag preview shouldn't carry
|
|
84
|
+
* a stale ordinal)
|
|
85
|
+
* - force the checkbox (`.nb-origin-node`) absolute-centered and visible
|
|
86
|
+
* regardless of hover/checked state, so the selection cell isn't empty
|
|
87
|
+
*/
|
|
88
|
+
export const overlayCellStylesClassName = css`
|
|
89
|
+
.ant-table-cell.ant-table-selection-column {
|
|
90
|
+
padding-left: ${SORT_HANDLE_GUTTER}px !important;
|
|
91
|
+
position: relative;
|
|
92
|
+
}
|
|
93
|
+
.nb-table-index {
|
|
94
|
+
display: none !important;
|
|
95
|
+
}
|
|
96
|
+
.nb-row-selection-cell {
|
|
97
|
+
position: relative;
|
|
98
|
+
display: inline-block;
|
|
99
|
+
min-width: 22px;
|
|
100
|
+
min-height: 22px;
|
|
101
|
+
vertical-align: middle;
|
|
102
|
+
}
|
|
103
|
+
.nb-row-selection-cell .nb-origin-node {
|
|
104
|
+
position: absolute;
|
|
105
|
+
inset: 0;
|
|
106
|
+
display: flex !important;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: center;
|
|
109
|
+
}
|
|
110
|
+
`;
|
|
@@ -0,0 +1,75 @@
|
|
|
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 React from 'react';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Same shape as antd Table's `rowKey` prop — either a record key name or a
|
|
14
|
+
* function. Hoisted here so utilities and `Table.tsx` agree on the contract.
|
|
15
|
+
*/
|
|
16
|
+
export type RowKey<RecordType extends object> =
|
|
17
|
+
| (keyof RecordType & (string | number))
|
|
18
|
+
| ((record: RecordType, index?: number) => React.Key);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Read a stable row id off a record. Mirrors antd Table's rowKey resolution
|
|
22
|
+
* but normalises non-primitive ids to strings so `data-row-key` attributes
|
|
23
|
+
* and `useSortable({ id })` agree on equality.
|
|
24
|
+
*/
|
|
25
|
+
export function readRowKey<RecordType extends object>(
|
|
26
|
+
record: RecordType,
|
|
27
|
+
rowKey: RowKey<RecordType>,
|
|
28
|
+
index?: number,
|
|
29
|
+
): React.Key | undefined {
|
|
30
|
+
if (typeof rowKey === 'function') {
|
|
31
|
+
return rowKey(record, index);
|
|
32
|
+
}
|
|
33
|
+
const value = record[rowKey] as unknown;
|
|
34
|
+
if (value == null) return undefined;
|
|
35
|
+
return typeof value === 'string' || typeof value === 'number' ? value : String(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Pixel-perfect snapshot of a rendered `<tr>` for the drag overlay clone.
|
|
40
|
+
* Contains everything needed to rebuild a visually identical floating row
|
|
41
|
+
* without re-running antd's column layout pass.
|
|
42
|
+
*/
|
|
43
|
+
export type RowSnapshot = {
|
|
44
|
+
/** outerHTML of the source `<tr>`, captured at drag start. */
|
|
45
|
+
html: string;
|
|
46
|
+
/** Per-cell pixel widths (in DOM order) so the clone can fix them via `<col>`. */
|
|
47
|
+
cellWidths: number[];
|
|
48
|
+
/** Total row width — used as the wrapper width so the clone matches source horizontally. */
|
|
49
|
+
totalWidth: number;
|
|
50
|
+
/** Total row height — applied to the clone so cell padding matches the source row exactly. */
|
|
51
|
+
totalHeight: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Snapshot the source `<tr>` so the drag overlay can render a pixel-accurate
|
|
56
|
+
* floating clone. We can't reliably recompute the layout from `columns` alone
|
|
57
|
+
* — antd auto-sizes columns at runtime based on content + the surrounding
|
|
58
|
+
* container — so we read the rendered widths off the DOM at drag start. The
|
|
59
|
+
* row height is captured too because antd's cell padding rules are scoped to
|
|
60
|
+
* a selector chain we strip in the clone.
|
|
61
|
+
*/
|
|
62
|
+
export function snapshotSourceRow(rowKey: string): RowSnapshot | null {
|
|
63
|
+
if (typeof document === 'undefined') return null;
|
|
64
|
+
// `CSS.escape` is in lib.dom and shipped in every browser we target; the
|
|
65
|
+
// guard is purely a belt-and-suspenders against exotic test environments
|
|
66
|
+
// where `window.CSS` may be absent.
|
|
67
|
+
const cssGlobal: { escape?: (value: string) => string } | undefined =
|
|
68
|
+
typeof window !== 'undefined' ? window.CSS : undefined;
|
|
69
|
+
const escaped = cssGlobal?.escape ? cssGlobal.escape(rowKey) : rowKey;
|
|
70
|
+
const sourceRow = document.querySelector(`tr[data-row-key="${escaped}"]`) as HTMLTableRowElement | null;
|
|
71
|
+
if (!sourceRow) return null;
|
|
72
|
+
const cellWidths = Array.from(sourceRow.cells).map((cell) => cell.getBoundingClientRect().width);
|
|
73
|
+
const rect = sourceRow.getBoundingClientRect();
|
|
74
|
+
return { html: sourceRow.outerHTML, cellWidths, totalWidth: rect.width, totalHeight: rect.height };
|
|
75
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
import { TinyColor } from '@ctrl/tinycolor';
|
|
11
11
|
import { useEffect } from 'react';
|
|
12
|
-
import { CustomToken, defaultTheme } from '@nocobase/client-v2';
|
|
13
12
|
import { theme } from 'antd';
|
|
13
|
+
import { type CustomToken, defaultTheme } from '../theme';
|
|
14
14
|
|
|
15
15
|
interface Result extends ReturnType<typeof theme.useToken> {
|
|
16
16
|
token: CustomToken;
|
|
@@ -122,12 +122,11 @@ const FilterFormDefaultValuesUI = observer(
|
|
|
122
122
|
rootCollection={getCollectionFromModel(ctx.model)}
|
|
123
123
|
value={value}
|
|
124
124
|
onChange={handleChange}
|
|
125
|
-
fixedMode="default"
|
|
126
|
-
showCondition={false}
|
|
127
125
|
showValueEditorWhenNoField
|
|
128
126
|
getValueInputProps={getValueInputProps}
|
|
129
127
|
isTitleFieldCandidate={isTitleFieldCandidate}
|
|
130
128
|
onSyncAssociationTitleField={onSyncAssociationTitleField}
|
|
129
|
+
enableDateVariableAsConstant
|
|
131
130
|
/>
|
|
132
131
|
);
|
|
133
132
|
},
|
|
@@ -681,6 +681,7 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
681
681
|
AdminLayoutMenuItemModel.registerFlow({
|
|
682
682
|
key: 'menuCreation',
|
|
683
683
|
title: 'Add menu item',
|
|
684
|
+
manual: true,
|
|
684
685
|
steps: {
|
|
685
686
|
basic: {
|
|
686
687
|
title: 'Add menu item',
|
|
@@ -697,6 +698,7 @@ AdminLayoutMenuItemModel.registerFlow({
|
|
|
697
698
|
AdminLayoutMenuItemModel.registerFlow({
|
|
698
699
|
key: 'menuSettings',
|
|
699
700
|
title: 'Menu settings',
|
|
701
|
+
manual: true,
|
|
700
702
|
steps: {
|
|
701
703
|
edit: {
|
|
702
704
|
title: 'Edit',
|
|
@@ -249,4 +249,115 @@ describe('resolveAdminRouteRuntimeTarget', () => {
|
|
|
249
249
|
expect(toRouterNavigationPath('/nocobase/v2/admin/page-1', '/nocobase/v2')).toBe('/admin/page-1');
|
|
250
250
|
expect(toRouterNavigationPath('/admin/page-1', '/nocobase/v2')).toBe('/admin/page-1');
|
|
251
251
|
});
|
|
252
|
+
|
|
253
|
+
describe('v2 sub-app context (router basename contains /apps/<id>/)', () => {
|
|
254
|
+
const subApp = {
|
|
255
|
+
getPublicPath: () => '/nocobase/v2/',
|
|
256
|
+
router: {
|
|
257
|
+
getBasename: () => '/nocobase/v2/apps/test-app/',
|
|
258
|
+
},
|
|
259
|
+
} as any;
|
|
260
|
+
|
|
261
|
+
it('should resolve flowPage runtime path under sub-app basename', () => {
|
|
262
|
+
expect(
|
|
263
|
+
resolveAdminRouteRuntimeTarget({
|
|
264
|
+
app: subApp,
|
|
265
|
+
route: {
|
|
266
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
267
|
+
schemaUid: 'fp1',
|
|
268
|
+
},
|
|
269
|
+
}),
|
|
270
|
+
).toEqual({
|
|
271
|
+
runtimePath: '/nocobase/v2/apps/test-app/admin/fp1',
|
|
272
|
+
navigationMode: 'spa',
|
|
273
|
+
isLegacy: false,
|
|
274
|
+
reason: 'ok',
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('should resolve group DFS to first flowPage under sub-app basename', () => {
|
|
279
|
+
const route: NocoBaseDesktopRoute = {
|
|
280
|
+
type: NocoBaseDesktopRouteType.group,
|
|
281
|
+
children: [
|
|
282
|
+
{
|
|
283
|
+
type: NocoBaseDesktopRouteType.tabs,
|
|
284
|
+
schemaUid: 'tabs-1',
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
type: NocoBaseDesktopRouteType.group,
|
|
288
|
+
children: [
|
|
289
|
+
{
|
|
290
|
+
type: NocoBaseDesktopRouteType.page,
|
|
291
|
+
schemaUid: 'legacy-2',
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
295
|
+
schemaUid: 'nested-fp',
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
],
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
expect(resolveAdminRouteRuntimeTarget({ app: subApp, route })).toEqual({
|
|
303
|
+
runtimePath: '/nocobase/v2/apps/test-app/admin/nested-fp',
|
|
304
|
+
navigationMode: 'spa',
|
|
305
|
+
isLegacy: false,
|
|
306
|
+
reason: 'ok',
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should strip sub-app basename when converting to router internal path', () => {
|
|
311
|
+
expect(toRouterNavigationPath('/nocobase/v2/apps/test-app/admin/page-1', '/nocobase/v2/apps/test-app')).toBe(
|
|
312
|
+
'/admin/page-1',
|
|
313
|
+
);
|
|
314
|
+
expect(toRouterNavigationPath('/nocobase/v2/apps/test-app/admin/page-1', '/nocobase/v2/apps/test-app/')).toBe(
|
|
315
|
+
'/admin/page-1',
|
|
316
|
+
);
|
|
317
|
+
});
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
describe('fallback when router basename is missing', () => {
|
|
321
|
+
it('should fall back to publicPath when router is undefined', () => {
|
|
322
|
+
const appNoRouter = {
|
|
323
|
+
getPublicPath: () => '/nocobase/v2/',
|
|
324
|
+
router: undefined,
|
|
325
|
+
} as any;
|
|
326
|
+
|
|
327
|
+
expect(
|
|
328
|
+
resolveAdminRouteRuntimeTarget({
|
|
329
|
+
app: appNoRouter,
|
|
330
|
+
route: {
|
|
331
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
332
|
+
schemaUid: 'fp1',
|
|
333
|
+
},
|
|
334
|
+
}),
|
|
335
|
+
).toMatchObject({
|
|
336
|
+
runtimePath: '/nocobase/v2/admin/fp1',
|
|
337
|
+
reason: 'ok',
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it('should fall back to publicPath when getBasename returns undefined', () => {
|
|
342
|
+
const appNoBasename = {
|
|
343
|
+
getPublicPath: () => '/nocobase/v2/',
|
|
344
|
+
router: {
|
|
345
|
+
getBasename: () => undefined,
|
|
346
|
+
},
|
|
347
|
+
} as any;
|
|
348
|
+
|
|
349
|
+
expect(
|
|
350
|
+
resolveAdminRouteRuntimeTarget({
|
|
351
|
+
app: appNoBasename,
|
|
352
|
+
route: {
|
|
353
|
+
type: NocoBaseDesktopRouteType.flowPage,
|
|
354
|
+
schemaUid: 'fp1',
|
|
355
|
+
},
|
|
356
|
+
}),
|
|
357
|
+
).toMatchObject({
|
|
358
|
+
runtimePath: '/nocobase/v2/admin/fp1',
|
|
359
|
+
reason: 'ok',
|
|
360
|
+
});
|
|
361
|
+
});
|
|
362
|
+
});
|
|
252
363
|
});
|
|
@@ -7,6 +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
11
|
import type { BaseApplication } from '../../../BaseApplication';
|
|
11
12
|
import { NocoBaseDesktopRouteType, type NocoBaseDesktopRoute } from '../../../flow-compat';
|
|
12
13
|
|
|
@@ -106,7 +107,7 @@ function joinRootRelativePath(basePath: string, pathname: string) {
|
|
|
106
107
|
}
|
|
107
108
|
|
|
108
109
|
function getV2AdminPath(app: ResolveAdminRouteRuntimeTargetOptions['app'], schemaUid: string) {
|
|
109
|
-
return joinRootRelativePath(app
|
|
110
|
+
return joinRootRelativePath(getV2EffectiveBasePath(app), `/admin/${schemaUid}`);
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
function appendLocationState(pathname: string, location?: LocationLike) {
|
|
@@ -20,7 +20,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
20
20
|
import { Button, Input } from 'antd';
|
|
21
21
|
import { css } from '@emotion/css';
|
|
22
22
|
import type { TextAreaRef } from 'antd/es/input/TextArea';
|
|
23
|
-
import { FlowContextSelector, useFlowContext } from '@nocobase/flow-engine';
|
|
23
|
+
import { FlowContextSelector, useFlowContext, type MetaTreeNode } from '@nocobase/flow-engine';
|
|
24
24
|
|
|
25
25
|
export interface TextAreaWithContextSelectorProps {
|
|
26
26
|
value?: string;
|
|
@@ -29,6 +29,20 @@ export interface TextAreaWithContextSelectorProps {
|
|
|
29
29
|
rows?: number;
|
|
30
30
|
maxRows?: number;
|
|
31
31
|
style?: React.CSSProperties;
|
|
32
|
+
disabled?: boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Custom meta tree for the variable picker. Accepts an array, a sync getter,
|
|
35
|
+
* or an async getter — same shape as `FlowContextSelector`'s `metaTree`. If
|
|
36
|
+
* omitted, the full `ctx.getPropertyMetaTree()` is used (legacy default).
|
|
37
|
+
*/
|
|
38
|
+
metaTree?: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
|
|
39
|
+
/**
|
|
40
|
+
* Format a picked meta node into the string inserted at the caret. When
|
|
41
|
+
* omitted, the FlowContextSelector default (`{{ ctx.X.Y }}`) is used.
|
|
42
|
+
* Override to match a different storage convention — e.g. NocoBase server
|
|
43
|
+
* templates use `{{$X.Y}}` without the `ctx.` prefix.
|
|
44
|
+
*/
|
|
45
|
+
formatPathToValue?: (meta: MetaTreeNode) => string;
|
|
32
46
|
}
|
|
33
47
|
|
|
34
48
|
/**
|
|
@@ -41,6 +55,9 @@ export const TextAreaWithContextSelector: React.FC<TextAreaWithContextSelectorPr
|
|
|
41
55
|
rows = 3,
|
|
42
56
|
maxRows = 24,
|
|
43
57
|
style,
|
|
58
|
+
disabled,
|
|
59
|
+
metaTree,
|
|
60
|
+
formatPathToValue,
|
|
44
61
|
}) => {
|
|
45
62
|
const flowCtx = useFlowContext();
|
|
46
63
|
const [innerValue, setInnerValue] = useState<string>(value || '');
|
|
@@ -76,10 +93,11 @@ export const TextAreaWithContextSelector: React.FC<TextAreaWithContextSelectorPr
|
|
|
76
93
|
const next = prev.slice(0, start) + toInsert + prev.slice(end);
|
|
77
94
|
setInnerValue(next);
|
|
78
95
|
onChange?.(next);
|
|
79
|
-
//
|
|
96
|
+
// 插入后选中刚插入的变量文本,与 v1 RawTextArea 行为一致:
|
|
97
|
+
// 用户可立即按删除键移除整段变量,或继续输入直接替换。
|
|
80
98
|
requestAnimationFrame(() => {
|
|
81
99
|
const pos = start + (toInsert?.length || 0);
|
|
82
|
-
el.setSelectionRange(
|
|
100
|
+
el.setSelectionRange(start, pos);
|
|
83
101
|
el.focus();
|
|
84
102
|
});
|
|
85
103
|
},
|
|
@@ -94,8 +112,8 @@ export const TextAreaWithContextSelector: React.FC<TextAreaWithContextSelectorPr
|
|
|
94
112
|
[insertAtCaret],
|
|
95
113
|
);
|
|
96
114
|
|
|
97
|
-
//
|
|
98
|
-
const
|
|
115
|
+
// 使用函数形式提供变量树,保证与运行时上下文一致;当外部传入则尊重外部值。
|
|
116
|
+
const resolvedMetaTree = useMemo(() => metaTree ?? (() => flowCtx.getPropertyMetaTree?.()), [flowCtx, metaTree]);
|
|
99
117
|
|
|
100
118
|
return (
|
|
101
119
|
<div style={{ position: 'relative', width: '100%', ...style }}>
|
|
@@ -105,6 +123,7 @@ export const TextAreaWithContextSelector: React.FC<TextAreaWithContextSelectorPr
|
|
|
105
123
|
onChange={handleTextChange}
|
|
106
124
|
autoSize={{ minRows: rows, maxRows }}
|
|
107
125
|
placeholder={placeholder}
|
|
126
|
+
disabled={disabled}
|
|
108
127
|
style={{ width: '100%' }}
|
|
109
128
|
/>
|
|
110
129
|
<div
|
|
@@ -116,7 +135,12 @@ export const TextAreaWithContextSelector: React.FC<TextAreaWithContextSelectorPr
|
|
|
116
135
|
lineHeight: 0,
|
|
117
136
|
}}
|
|
118
137
|
>
|
|
119
|
-
<FlowContextSelector
|
|
138
|
+
<FlowContextSelector
|
|
139
|
+
metaTree={resolvedMetaTree}
|
|
140
|
+
disabled={disabled}
|
|
141
|
+
formatPathToValue={formatPathToValue}
|
|
142
|
+
onChange={(val) => handleVariableSelected(val)}
|
|
143
|
+
>
|
|
120
144
|
<Button
|
|
121
145
|
type="default"
|
|
122
146
|
style={{ fontStyle: 'italic', fontFamily: 'New York, Times New Roman, Times, serif' }}
|