Browse Source

fix: undo redo mm cell copy paste

pull/7558/head
Ramesh Mane 10 months ago
parent
commit
4434da23e9
  1. 3
      packages/nc-gui/components/smartsheet/Row.vue
  2. 42
      packages/nc-gui/components/smartsheet/grid/Table.vue
  3. 2
      packages/nc-gui/composables/useData.ts
  4. 11
      packages/nc-gui/composables/useLTARStore.ts
  5. 8
      packages/nc-gui/composables/useMultiSelect/convertCellData.ts
  6. 74
      packages/nc-gui/composables/useMultiSelect/index.ts
  7. 40
      packages/nc-gui/composables/useSmartsheetRowStore.ts
  8. 6
      packages/nocodb/src/controllers/data-table.controller.ts
  9. 24
      packages/nocodb/src/schema/swagger.json
  10. 178
      packages/nocodb/src/services/data-table.service.ts
  11. 4
      packages/nocodb/src/utils/acl.ts

3
packages/nc-gui/components/smartsheet/Row.vue

@ -21,7 +21,7 @@ const currentRow = toRef(props, 'row')
const { meta } = useSmartsheetStoreOrThrow()
const { isNew, state, syncLTARRefs, clearLTARCell, addLTARRef } = useProvideSmartsheetRowStore(meta as Ref<TableType>, currentRow)
const { isNew, state, syncLTARRefs, clearLTARCell, addLTARRef, cleaMMCell } = useProvideSmartsheetRowStore(meta as Ref<TableType>, currentRow)
const reloadViewDataTrigger = inject(ReloadViewDataHookInj)!
@ -41,6 +41,7 @@ defineExpose({
syncLTARRefs,
clearLTARCell,
addLTARRef,
cleaMMCell
})
</script>

42
packages/nc-gui/components/smartsheet/grid/Table.vue

@ -50,6 +50,7 @@ import {
useViewColumnsOrThrow,
useViewsStore,
watch,
useApi
} from '#imports'
import type { CellRange, Row } from '#imports'
@ -98,6 +99,8 @@ const dataRef = toRef(props, 'data')
const paginationStyleRef = toRef(props, 'pagination')
const { api } = useApi()
const {
loadData,
changePage,
@ -263,17 +266,19 @@ provide(JsonExpandInj, isJsonExpand)
async function clearCell(ctx: { row: number; col: number } | null, skipUpdate = false) {
if (!ctx || !hasEditPermission.value || (!isLinksOrLTAR(fields.value[ctx.col]) && isVirtualCol(fields.value[ctx.col]))) return
if (fields.value[ctx.col]?.uidt === UITypes.Links) {
return message.info(t('msg.linkColumnClearNotSupportedYet'))
}
const rowObj = dataRef.value[ctx.row]
const columnObj = fields.value[ctx.col]
if (isVirtualCol(columnObj)) {
let mmClearResult
if (isMm(columnObj) && rowRefs.value) {
mmClearResult = await rowRefs.value[ctx.row]!.cleaMMCell(columnObj)
}
addUndo({
undo: {
fn: async (ctx: { row: number; col: number }, col: ColumnType, row: Row, pg: PaginatedType) => {
fn: async (ctx: { row: number; col: number }, col: ColumnType, row: Row, pg: PaginatedType, mmClearResult: any[]) => {
if (paginationDataRef.value?.pageSize === pg.pageSize) {
if (paginationDataRef.value?.page !== pg.page) {
await changePage?.(pg.page!)
@ -286,11 +291,17 @@ async function clearCell(ctx: { row: number; col: number } | null, skipUpdate =
rowId === extractPkFromRow(rowObj.row, meta.value?.columns as ColumnType[]) &&
columnObj.id === col.id
) {
rowObj.row[columnObj.title] = row.row[columnObj.title]
if (rowRefs.value) {
await rowRefs.value[ctx.row]!.addLTARRef(rowObj.row[columnObj.title], columnObj)
await rowRefs.value[ctx.row]!.syncLTARRefs(rowObj.row)
if (isBt(columnObj)) {
rowObj.row[columnObj.title] = row.row[columnObj.title]
await rowRefs.value[ctx.row]!.addLTARRef(rowObj.row[columnObj.title], columnObj)
await rowRefs.value[ctx.row]!.syncLTARRefs(rowObj.row)
} else if (isMm(columnObj)) {
await api.dbDataTableRow.nestedLink(meta.value?.id as string, columnObj.id as string, encodeURIComponent(rowId as string), mmClearResult)
rowObj.row[columnObj.title] = mmClearResult?.length ? mmClearResult?.length : null
}
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
@ -305,10 +316,10 @@ async function clearCell(ctx: { row: number; col: number } | null, skipUpdate =
throw new Error(t('msg.pageSizeChanged'))
}
},
args: [clone(ctx), clone(columnObj), clone(rowObj), clone(paginationDataRef.value)],
args: [clone(ctx), clone(columnObj), clone(rowObj), clone(paginationDataRef.value), mmClearResult],
},
redo: {
fn: async (ctx: { row: number; col: number }, col: ColumnType, row: Row, pg: PaginatedType) => {
fn: async (ctx: { row: number; col: number }, col: ColumnType, row: Row, pg: PaginatedType, mmClearResult:any[]) => {
if (paginationDataRef.value?.pageSize === pg.pageSize) {
if (paginationDataRef.value?.page !== pg.page) {
await changePage?.(pg.page!)
@ -318,7 +329,11 @@ async function clearCell(ctx: { row: number; col: number } | null, skipUpdate =
const columnObj = fields.value[ctx.col]
if (rowId === extractPkFromRow(rowObj.row, meta.value?.columns as ColumnType[]) && columnObj.id === col.id) {
if (rowRefs.value) {
await rowRefs.value[ctx.row]!.clearLTARCell(columnObj)
if (isBt(columnObj)) {
await rowRefs.value[ctx.row]!.clearLTARCell(columnObj)
} else if (isMm(columnObj)){
await rowRefs.value[ctx.row]!.cleaMMCell(columnObj)
}
}
// eslint-disable-next-line @typescript-eslint/no-use-before-define
activeCell.col = ctx.col
@ -332,11 +347,12 @@ async function clearCell(ctx: { row: number; col: number } | null, skipUpdate =
throw new Error(t('msg.pageSizeChanged'))
}
},
args: [clone(ctx), clone(columnObj), clone(rowObj), clone(paginationDataRef.value)],
args: [clone(ctx), clone(columnObj), clone(rowObj), clone(paginationDataRef.value), mmClearResult],
},
scope: defineViewScope({ view: view.value }),
})
if (rowRefs.value) await rowRefs.value[ctx.row]!.clearLTARCell(columnObj)
if (isBt(columnObj) && rowRefs.value) await rowRefs.value[ctx.row]!.clearLTARCell(columnObj)
return
}

2
packages/nc-gui/composables/useData.ts

@ -480,7 +480,7 @@ export function useData(args: {
base.value.title as string,
metaValue?.title as string,
encodeURIComponent(rowId),
type as 'mm' | 'hm',
type as RelationTypes,
column.title as string,
encodeURIComponent(relatedRowId),
)

11
packages/nc-gui/composables/useLTARStore.ts

@ -8,6 +8,7 @@ import {
dateFormats,
parseStringDateTime,
timeFormats,
RelationTypes,
} from 'nocodb-sdk'
import type { ComputedRef, Ref } from 'vue'
import {
@ -230,7 +231,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
baseId,
meta.value.id,
encodeURIComponent(rowId.value),
colOptions.value.type as 'mm' | 'hm',
colOptions.value.type as RelationTypes,
column?.value?.id,
{
limit: String(childrenExcludedListPagination.size),
@ -284,7 +285,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
childrenList.value = await $api.public.dataNestedList(
sharedView.value?.uuid as string,
encodeURIComponent(rowId.value),
colOptions.value.type as 'mm' | 'hm',
colOptions.value.type as RelationTypes,
column.value.id,
{
limit: String(childrenListPagination.size),
@ -304,7 +305,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
(base?.value?.id || (sharedView.value?.view as any)?.base_id) as string,
meta.value.id,
encodeURIComponent(rowId.value),
colOptions.value.type as 'mm' | 'hm',
colOptions.value.type as RelationTypes,
column?.value?.id,
{
limit: String(childrenListPagination.size),
@ -395,7 +396,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
base.value.id as string,
metaValue.id!,
encodeURIComponent(rowId.value),
colOptions.value.type as 'mm' | 'hm',
colOptions.value.type as RelationTypes,
column?.value?.id,
encodeURIComponent(getRelatedTableRowId(row) as string),
)
@ -461,7 +462,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
base.value.id as string,
metaValue.id as string,
encodeURIComponent(rowId.value),
colOptions.value.type as 'mm' | 'hm',
colOptions.value.type as RelationTypes,
column?.value?.id,
encodeURIComponent(getRelatedTableRowId(row) as string) as string,
)

8
packages/nc-gui/composables/useMultiSelect/convertCellData.ts

@ -1,8 +1,8 @@
import dayjs from 'dayjs'
import type { ColumnType, LinkToAnotherRecordType, SelectOptionsType } from 'nocodb-sdk'
import { RelationTypes, UITypes } from 'nocodb-sdk'
import { UITypes } from 'nocodb-sdk'
import type { AppInfo } from '~/composables/useGlobal'
import { parseProp } from '#imports'
import { isBt, isMm, parseProp } from '#imports'
export default function convertCellData(
args: { to: UITypes; value: string; column: ColumnType; appInfo: AppInfo; files?: FileList | File[]; oldValue?: unknown },
@ -254,7 +254,7 @@ export default function convertCellData(
return undefined
}
if ((column.colOptions as LinkToAnotherRecordType)?.type === RelationTypes.BELONGS_TO) {
if (isBt(column)) {
const parsedVal = typeof value === 'string' ? JSON.parse(value) : value
if (
@ -274,7 +274,7 @@ export default function convertCellData(
return undefined
}
if ((column.colOptions as LinkToAnotherRecordType)?.type === RelationTypes.MANY_TO_MANY) {
if (isMm(column)) {
const parsedVal = typeof value === 'string' ? JSON.parse(value) : value
if (

74
packages/nc-gui/composables/useMultiSelect/index.ts

@ -3,7 +3,7 @@ import { computed } from 'vue'
import dayjs from 'dayjs'
import type { MaybeRef } from '@vueuse/core'
import type { ColumnType, LinkToAnotherRecordType, TableType, UserFieldRecordType, ViewType } from 'nocodb-sdk'
import { RelationTypes, UITypes, dateFormats, isDateMonthFormat, isSystemColumn, isVirtualCol, timeFormats } from 'nocodb-sdk'
import { UITypes, dateFormats, isDateMonthFormat, isSystemColumn, isVirtualCol, timeFormats } from 'nocodb-sdk'
import { parse } from 'papaparse'
import type { Cell } from './cellRange'
import { CellRange } from './cellRange'
@ -12,9 +12,11 @@ import type { Nullable, Row } from '#imports'
import {
extractPkFromRow,
extractSdkResponseErrorMsg,
isBt,
isDrawerOrModalExist,
isExpandedCellInputExist,
isMac,
isMm,
isTypableInputColumn,
message,
parseProp,
@ -136,10 +138,7 @@ export function useMultiSelect(
}
}
if (
columnObj.uidt === UITypes.LinkToAnotherRecord &&
(columnObj.colOptions as LinkToAnotherRecordType).type === RelationTypes.BELONGS_TO
) {
if (isBt(columnObj)) {
// fk_related_model_id is used to prevent paste operation in different fk_related_model_id cell
textToCopy = {
fk_related_model_id: (columnObj.colOptions as LinkToAnotherRecordType).fk_related_model_id,
@ -147,10 +146,7 @@ export function useMultiSelect(
}
}
if (
columnObj.uidt === UITypes.Links &&
(columnObj.colOptions as LinkToAnotherRecordType).type === RelationTypes.MANY_TO_MANY
) {
if (isMm(columnObj)) {
textToCopy = {
rowId: extractPkFromRow(rowObj.row, meta.value?.columns as ColumnType[]),
columnId: columnObj.id,
@ -878,10 +874,7 @@ export function useMultiSelect(
const columnObj = unref(fields)[activeCell.col]
// handle belongs to column
if (
columnObj.uidt === UITypes.LinkToAnotherRecord &&
(columnObj.colOptions as LinkToAnotherRecordType)?.type === RelationTypes.BELONGS_TO
) {
if (isBt(columnObj)) {
const pasteVal = convertCellData(
{
value: clipboardData,
@ -911,10 +904,7 @@ export function useMultiSelect(
return await syncCellData?.({ ...activeCell, updatedColumnTitle: foreignKeyColumn.title })
}
if (
columnObj.uidt === UITypes.Links &&
(columnObj.colOptions as LinkToAnotherRecordType)?.type === RelationTypes.MANY_TO_MANY
) {
if (isMm(columnObj)) {
const pasteVal = convertCellData(
{
value: clipboardData,
@ -937,21 +927,26 @@ export function useMultiSelect(
let result
try {
result = await api.dbDataTableRow.nestedListCopyPaste(meta.value?.id as string, columnObj.id as string, [
{
operation: 'copy',
rowId: pasteVal.rowId,
columnId: pasteVal.columnId,
fk_related_model_id: pasteVal.fk_related_model_id,
},
{
operation: 'paste',
rowId: pasteRowPk,
columnId: pasteVal.columnId,
fk_related_model_id:
(columnObj.colOptions as LinkToAnotherRecordType).fk_related_model_id || pasteVal.fk_related_model_id,
},
])
result = await api.dbDataTableRow.nestedListCopyPasteOrDeleteAll(
meta.value?.id as string,
columnObj.id as string,
[
{
operation: 'copy',
rowId: pasteVal.rowId,
columnId: pasteVal.columnId,
fk_related_model_id: pasteVal.fk_related_model_id,
},
{
operation: 'paste',
rowId: pasteRowPk,
columnId: columnObj.id as string,
fk_related_model_id:
(columnObj.colOptions as LinkToAnotherRecordType).fk_related_model_id || pasteVal.fk_related_model_id,
},
],
{ viewId: activeView?.value?.id },
)
} catch {
rowObj.row[columnObj.title!] = oldCellValue
return
@ -970,17 +965,23 @@ export function useMultiSelect(
pasteRowPk: string,
result: { link: any[]; unlink: any[] },
value: number,
activeCell: Nullable<Cell>,
activeCell: Cell,
) => {
const rowObj = unref(data)[activeCell.row]
const columnObj = unref(fields)[activeCell.col]
await Promise.all([
result.link.length &&
api.dbDataTableRow.nestedLink(tableId, columnId, encodeURIComponent(pasteRowPk), result.link),
api.dbDataTableRow.nestedLink(tableId, columnId, encodeURIComponent(pasteRowPk), result.link, {
viewId: activeView?.value?.id,
}),
result.unlink.length &&
api.dbDataTableRow.nestedUnlink(
meta.value?.id as string,
columnObj.id as string,
encodeURIComponent(pasteRowPk),
result.unlink,
{ viewId: activeView?.value?.id },
),
])
@ -997,8 +998,11 @@ export function useMultiSelect(
pasteRowPk: string,
result: { link: any[]; unlink: any[] },
value: number,
activeCell: Nullable<Cell>,
activeCell: Cell,
) => {
const rowObj = unref(data)[activeCell.row]
const columnObj = unref(fields)[activeCell.col]
await Promise.all([
result.unlink.length &&
api.dbDataTableRow.nestedLink(tableId, columnId, encodeURIComponent(pasteRowPk), result.unlink),

40
packages/nc-gui/composables/useSmartsheetRowStore.ts

@ -83,7 +83,7 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
base.value.id as string,
metaValue?.id as string,
encodeURIComponent(rowId),
type as 'mm' | 'hm',
type,
column.id as string,
encodeURIComponent(relatedRowId),
)
@ -185,6 +185,43 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
})
}
// clear MM cell
const cleaMMCell = async (column: ColumnType) => {
try {
if (!column || !isLinksOrLTAR(column)) return
const relatedTableMeta = metas.value?.[(<LinkToAnotherRecordType>column?.colOptions)?.fk_related_model_id as string]
if (isNew.value) {
state.value[column.title!] = null
} else if (currentRow.value) {
if ((<LinkToAnotherRecordType>column.colOptions)?.type === RelationTypes.MANY_TO_MANY) {
if (!currentRow.value.row[column.title!]) return
console.log('currentRow.value.row, meta.value?.columns', currentRow.value.row, meta.value?.columns)
const result = await $api.dbDataTableRow.nestedListCopyPasteOrDeleteAll(
meta.value?.id as string,
column.id as string,
[
{
operation: 'deleteAll',
rowId: extractPkFromRow(currentRow.value.row, meta.value?.columns as ColumnType[]) as string,
columnId: column.id as string,
fk_related_model_id: (column.colOptions as LinkToAnotherRecordType)?.fk_related_model_id as string,
},
],
)
currentRow.value.row[column.title!] = null
return Array.isArray(result.unlink) ? result.unlink : []
}
}
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
return {
row,
state,
@ -196,6 +233,7 @@ const [useProvideSmartsheetRowStore, useSmartsheetRowStore] = useInjectionState(
loadRow,
currentRow,
clearLTARCell,
cleaMMCell,
}
},
'smartsheet-row-store',

6
packages/nocodb/src/controllers/data-table.controller.ts

@ -196,8 +196,8 @@ export class DataTableController {
// todo: naming
@Post(['/api/v2/tables/:modelId/links/:columnId/records'])
@Acl('nestedDataListCopyPaste')
async nestedListCopyPaste(
@Acl('nestedDataListCopyPasteOrDeleteAll')
async nestedListCopyPasteOrDeleteAll(
@Req() req: Request,
@Param('modelId') modelId: string,
@Query('viewId') viewId: string,
@ -210,7 +210,7 @@ export class DataTableController {
fk_related_model_id: string;
}[],
) {
return await this.dataTableService.nestedListCopyPaste({
return await this.dataTableService.nestedListCopyPasteOrDeleteAll({
modelId,
query: req.query,
viewId,

24
packages/nocodb/src/schema/swagger.json

@ -16351,8 +16351,8 @@
}
],
"post": {
"summary": "Copy paste nested link",
"operationId": "db-data-table-row-nested-list-copy-paste",
"summary": "Copy paste or deleteAll nested link",
"operationId": "db-data-table-row-nested-list-copy-paste-or-deleteAll",
"responses": {
"200": {
"description": "OK",
@ -16388,7 +16388,7 @@
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/nestedListCopyPasteReq"
"$ref": "#/components/schemas/nestedListCopyPasteOrDeleteAllReq"
},
"examples": {
"Example 1": {
@ -16406,12 +16406,22 @@
"fk_related_model_id": "m797s6vbrqvo2pv"
}
]
},
"Example 2": {
"value": [
{
"operation": "deleteAll",
"rowId": "1",
"columnId": "ca2ppiy8raidc09",
"fk_related_model_id": "m797s6vbrqvo2pv"
}
]
}
}
}
}
},
"description": "Copy links from the one cell and paste them into another cell",
"description": "Copy links from the one cell and paste them into another cell or delete all records from cell",
"parameters": [
{
"$ref": "#/components/parameters/xc-auth"
@ -24035,16 +24045,16 @@
}
}
},
"nestedListCopyPasteReq": {
"nestedListCopyPasteOrDeleteAllReq": {
"type": "array",
"minItems": 2,
"minItems": 1,
"maxItems": 2,
"items": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["copy", "paste"]
"enum": ["copy", "paste", "deleteAll"]
},
"rowId": {
"type": "string"

178
packages/nocodb/src/services/data-table.service.ts

@ -445,41 +445,33 @@ export class DataTableService {
}
// todo: naming & optimizing
async nestedListCopyPaste(param: {
async nestedListCopyPasteOrDeleteAll(param: {
cookie: any;
viewId: string;
modelId: string;
columnId: string;
query: any;
data: {
operation: 'copy' | 'paste';
operation: 'copy' | 'paste' | 'deleteAll';
rowId: string;
columnId: string;
fk_related_model_id: string;
}[];
}) {
validatePayload(
'swagger.json#/components/schemas/nestedListCopyPasteReq',
'swagger.json#/components/schemas/nestedListCopyPasteOrDeleteAllReq',
param.data,
);
if (
param.data[0]?.fk_related_model_id !== param.data[1]?.fk_related_model_id
) {
throw new Error(
'The operation is not supported on different fk_related_model_id',
);
}
const operationMap = param.data.reduce(
(map, p) => {
map[p.operation] = p;
return map;
},
{} as Record<
'copy' | 'paste',
'copy' | 'paste' | 'deleteAll',
{
operation: 'copy' | 'paste';
operation: 'copy' | 'paste' | 'deleteAll';
rowId: string;
columnId: string;
fk_related_model_id: string;
@ -487,6 +479,16 @@ export class DataTableService {
>,
);
if (
!operationMap.deleteAll &&
operationMap.copy.fk_related_model_id !==
operationMap.paste.fk_related_model_id
) {
throw new Error(
'The operation is not supported on different fk_related_model_id',
);
}
const { model, view } = await this.getModelAndView(param);
const source = await Source.get(model.source_id);
@ -497,21 +499,32 @@ export class DataTableService {
dbDriver: await NcConnectionMgrv2.get(source),
});
const [existsCopyRow, existsPasteRow] = await Promise.all([
baseModel.exist(operationMap.copy.rowId),
baseModel.exist(operationMap.paste.rowId),
]);
if (!existsCopyRow && !existsPasteRow) {
NcError.notFound(
`Record with id '${operationMap.copy.rowId}' and '${operationMap.paste.rowId}' not found`,
);
} else if (!existsCopyRow) {
NcError.notFound(`Record with id '${operationMap.copy.rowId}' not found`);
} else if (!existsPasteRow) {
if (
operationMap.deleteAll &&
!(await baseModel.exist(operationMap.deleteAll.rowId))
) {
NcError.notFound(
`Record with id '${operationMap.paste.rowId}' not found`,
`Record with id '${operationMap.deleteAll.rowId}' not found`,
);
} else if (operationMap.copy && operationMap.paste) {
const [existsCopyRow, existsPasteRow] = await Promise.all([
baseModel.exist(operationMap.copy.rowId),
baseModel.exist(operationMap.paste.rowId),
]);
if (!existsCopyRow && !existsPasteRow) {
NcError.notFound(
`Record with id '${operationMap.copy.rowId}' and '${operationMap.paste.rowId}' not found`,
);
} else if (!existsCopyRow) {
NcError.notFound(
`Record with id '${operationMap.copy.rowId}' not found`,
);
} else if (!existsPasteRow) {
NcError.notFound(
`Record with id '${operationMap.paste.rowId}' not found`,
);
}
}
const column = await this.getColumn(param);
@ -537,55 +550,88 @@ export class DataTableService {
listArgs.sortArr = JSON.parse(listArgs.sortArrJson);
} catch (e) {}
const [copiedCellNestedList, pasteCellNestedList] = await Promise.all([
baseModel.mmList(
{
colId: operationMap.copy.columnId,
parentId: operationMap.copy.rowId,
},
listArgs as any,
true,
),
baseModel.mmList(
if (operationMap.deleteAll) {
let deleteCellNestedList = await baseModel.mmList(
{
colId: column.id,
parentId: operationMap.paste.rowId,
parentId: operationMap.deleteAll.rowId,
},
listArgs as any,
true,
),
]);
const filteredRowsToLink = this.filterAndMapRows(
copiedCellNestedList,
pasteCellNestedList,
relatedModel.primaryKeys,
);
const filteredRowsToUnlink = this.filterAndMapRows(
pasteCellNestedList,
copiedCellNestedList,
relatedModel.primaryKeys,
);
);
await Promise.all([
filteredRowsToLink.length &&
baseModel.addLinks({
if (deleteCellNestedList && Array.isArray(deleteCellNestedList)) {
await baseModel.removeLinks({
colId: column.id,
childIds: filteredRowsToLink,
rowId: operationMap.paste.rowId,
childIds: deleteCellNestedList,
rowId: operationMap.deleteAll.rowId,
cookie: param.cookie,
}),
filteredRowsToUnlink.length &&
baseModel.removeLinks({
colId: column.id,
childIds: filteredRowsToUnlink,
rowId: operationMap.paste.rowId,
cookie: param.cookie,
}),
]);
});
// extract only pk row data
deleteCellNestedList = deleteCellNestedList.map((nestedList) => {
return relatedModel.primaryKeys.reduce((acc, col) => {
acc[col.title || col.column_name] =
nestedList[col.title || col.column_name];
return acc;
}, {});
});
} else {
deleteCellNestedList = [];
}
return { link: filteredRowsToLink, unlink: filteredRowsToUnlink };
return { link: [], unlink: deleteCellNestedList };
} else if (operationMap.copy && operationMap.paste) {
const [copiedCellNestedList, pasteCellNestedList] = await Promise.all([
baseModel.mmList(
{
colId: operationMap.copy.columnId,
parentId: operationMap.copy.rowId,
},
listArgs as any,
true,
),
baseModel.mmList(
{
colId: column.id,
parentId: operationMap.paste.rowId,
},
listArgs as any,
true,
),
]);
const filteredRowsToLink = this.filterAndMapRows(
copiedCellNestedList,
pasteCellNestedList,
relatedModel.primaryKeys,
);
const filteredRowsToUnlink = this.filterAndMapRows(
pasteCellNestedList,
copiedCellNestedList,
relatedModel.primaryKeys,
);
await Promise.all([
filteredRowsToLink.length &&
baseModel.addLinks({
colId: column.id,
childIds: filteredRowsToLink,
rowId: operationMap.paste.rowId,
cookie: param.cookie,
}),
filteredRowsToUnlink.length &&
baseModel.removeLinks({
colId: column.id,
childIds: filteredRowsToUnlink,
rowId: operationMap.paste.rowId,
cookie: param.cookie,
}),
]);
return { link: filteredRowsToLink, unlink: filteredRowsToUnlink };
}
}
private validateIds(rowIds: any[] | any) {

4
packages/nocodb/src/utils/acl.ts

@ -120,7 +120,7 @@ const permissionScopes = {
'nestedDataList',
'nestedDataLink',
'nestedDataUnlink',
'nestedListCopyPaste',
'nestedListCopyPasteOrDeleteAll',
'baseUserList',
// Base API Tokens
@ -228,7 +228,7 @@ const rolePermissions:
nestedDataLink: true,
nestedDataUnlink: true,
nestedListCopyPaste: true,
nestedListCopyPasteOrDeleteAll: true,
// TODO add ACL with base scope
// upload: true,
// uploadViaURL: true,

Loading…
Cancel
Save