diff --git a/packages/nc-gui/components/smartsheet/Row.vue b/packages/nc-gui/components/smartsheet/Row.vue index 16e6ae4019..80a806bdfb 100644 --- a/packages/nc-gui/components/smartsheet/Row.vue +++ b/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, currentRow) +const { isNew, state, syncLTARRefs, clearLTARCell, addLTARRef, cleaMMCell } = useProvideSmartsheetRowStore(meta as Ref, currentRow) const reloadViewDataTrigger = inject(ReloadViewDataHookInj)! @@ -41,6 +41,7 @@ defineExpose({ syncLTARRefs, clearLTARCell, addLTARRef, + cleaMMCell }) diff --git a/packages/nc-gui/components/smartsheet/grid/Table.vue b/packages/nc-gui/components/smartsheet/grid/Table.vue index 9bc3f8896e..63378c34d4 100644 --- a/packages/nc-gui/components/smartsheet/grid/Table.vue +++ b/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 } diff --git a/packages/nc-gui/composables/useData.ts b/packages/nc-gui/composables/useData.ts index c588e3fc0f..33f3cfb57b 100644 --- a/packages/nc-gui/composables/useData.ts +++ b/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), ) diff --git a/packages/nc-gui/composables/useLTARStore.ts b/packages/nc-gui/composables/useLTARStore.ts index 37e154df0e..51882e0673 100644 --- a/packages/nc-gui/composables/useLTARStore.ts +++ b/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, ) diff --git a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts b/packages/nc-gui/composables/useMultiSelect/convertCellData.ts index 3973c75bf7..0325993020 100644 --- a/packages/nc-gui/composables/useMultiSelect/convertCellData.ts +++ b/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 ( diff --git a/packages/nc-gui/composables/useMultiSelect/index.ts b/packages/nc-gui/composables/useMultiSelect/index.ts index 72824d45e7..9de520ff92 100644 --- a/packages/nc-gui/composables/useMultiSelect/index.ts +++ b/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, + 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, + 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), diff --git a/packages/nc-gui/composables/useSmartsheetRowStore.ts b/packages/nc-gui/composables/useSmartsheetRowStore.ts index 74781244b6..0e68daada8 100644 --- a/packages/nc-gui/composables/useSmartsheetRowStore.ts +++ b/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?.[(column?.colOptions)?.fk_related_model_id as string] + + if (isNew.value) { + state.value[column.title!] = null + } else if (currentRow.value) { + if ((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', diff --git a/packages/nocodb/src/controllers/data-table.controller.ts b/packages/nocodb/src/controllers/data-table.controller.ts index a6c65b471b..04a8501661 100644 --- a/packages/nocodb/src/controllers/data-table.controller.ts +++ b/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, diff --git a/packages/nocodb/src/schema/swagger.json b/packages/nocodb/src/schema/swagger.json index 88180e1b23..ecccd9c63d 100644 --- a/packages/nocodb/src/schema/swagger.json +++ b/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" diff --git a/packages/nocodb/src/services/data-table.service.ts b/packages/nocodb/src/services/data-table.service.ts index 8b4d55f35d..3725b50b6f 100644 --- a/packages/nocodb/src/services/data-table.service.ts +++ b/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) { diff --git a/packages/nocodb/src/utils/acl.ts b/packages/nocodb/src/utils/acl.ts index 8173b464bf..b60c414e9b 100644 --- a/packages/nocodb/src/utils/acl.ts +++ b/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,