From 7d9c6a25e8396accef27079c950f1eef6ea9ad59 Mon Sep 17 00:00:00 2001 From: Julius Knorr Date: Mon, 31 Mar 2025 15:13:01 +0200 Subject: [PATCH 1/3] feat: Add row actions to delete or duplicate a row Signed-off-by: Julius Knorr --- src/modules/modals/EditRow.vue | 1 - .../components/ncTable/partials/TableRow.vue | 89 +++++++++++++++++-- .../ncTable/sections/CustomTable.vue | 2 + 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/src/modules/modals/EditRow.vue b/src/modules/modals/EditRow.vue index 25d27c0b7b..d34197d51c 100644 --- a/src/modules/modals/EditRow.vue +++ b/src/modules/modals/EditRow.vue @@ -238,7 +238,6 @@ export default { }, reset() { this.localRow = {} - this.dataLoaded = false this.prepareDeleteRow = false }, actionDeleteRow() { diff --git a/src/shared/components/ncTable/partials/TableRow.vue b/src/shared/components/ncTable/partials/TableRow.vue index 1412f92b93..f752238ce9 100644 --- a/src/shared/components/ncTable/partials/TableRow.vue +++ b/src/shared/components/ncTable/partials/TableRow.vue @@ -23,19 +23,45 @@ :is-view="isView" :can-edit="config.canEditRows" /> - - - - + +
+ + + + + + + {{ t('tables', 'Duplicate row') }} + + + + {{ t('tables', 'Delete row') }} + + +
@@ -222,5 +289,11 @@ td.fixed-width { overflow: hidden; white-space: normal; } +.row-actions-container { + display: flex; + align-items: center; + justify-content: center; + gap: var(--default-grid-baseline); +} diff --git a/src/shared/components/ncTable/sections/CustomTable.vue b/src/shared/components/ncTable/sections/CustomTable.vue index 2b3c58a417..2ef6f92f6e 100644 --- a/src/shared/components/ncTable/sections/CustomTable.vue +++ b/src/shared/components/ncTable/sections/CustomTable.vue @@ -598,6 +598,8 @@ export default { position: sticky; inset-inline-end: 0; width: 55px; + right: 0; + width: calc(var(--button-size) * 2); background-color: inherit; padding-inline-end: 16px; } From 6e6b320515048f5d1d1252299b5b26f2c9ef3726 Mon Sep 17 00:00:00 2001 From: Enjeck C Date: Tue, 18 Nov 2025 09:09:53 +0000 Subject: [PATCH 2/3] feat: Implement row duplication and deletion actions in the table component Signed-off-by: Enjeck C --- cypress/e2e/tables-rows.cy.js | 103 ++++++++++++++++++ .../components/ncTable/partials/TableRow.vue | 16 ++- 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/cypress/e2e/tables-rows.cy.js b/cypress/e2e/tables-rows.cy.js index 8ff86197e9..a251e26947 100644 --- a/cypress/e2e/tables-rows.cy.js +++ b/cypress/e2e/tables-rows.cy.js @@ -107,4 +107,107 @@ describe('Rows for a table', () => { cy.get('[data-cy="ncTable"] table').contains('Edited inline').should('exist') cy.get('[data-cy="ncTable"] table').contains('Test inline editing').should('not.exist') }) + + it('Duplicate row using action menu', () => { + cy.loadTable('Welcome to Nextcloud Tables!') + cy.get('[data-cy="createRowBtn"]').click({ force: true }) + cy.get('[data-cy="createRowModal"] .slot input').first().type('Original row') + cy.get('[data-cy="createRowModal"] .ProseMirror').first().click() + cy.get('[data-cy="createRowModal"] .ProseMirror').first().clear() + cy.get('[data-cy="createRowModal"] .ProseMirror').first().type('Original description') + cy.get('[data-cy="createRowModal"] [aria-label="Increase stars"]').click().click() + cy.get('[data-cy="createRowSaveButton"]').click() + + cy.get('[data-cy="createRowModal"]').should('not.exist') + cy.get('[data-cy="ncTable"] table').contains('Original row').should('exist') + + // Find the row and click duplicate action + cy.get('[data-cy="ncTable"] [data-cy="customTableRow"]').contains('Original row').closest('[data-cy="customTableRow"]').within(() => { + // Click the actions menu button (three dots) + cy.get('.action-item__menutoggle').click() + }) + + // Click duplicate action + cy.get('[data-cy="duplicateRowBtn"]').click() + + // Verify the row was duplicated + cy.get('.icon-loading').should('not.exist') + cy.get('[data-cy="ncTable"] table').contains('Original row').should('have.length.at.least', 2) + cy.get('[data-cy="ncTable"] [data-cy="customTableRow"]').should('have.length.at.least', 2) + }) + + it('Delete row using action menu', () => { + cy.loadTable('Welcome to Nextcloud Tables!') + cy.get('[data-cy="createRowBtn"]').click({ force: true }) + cy.get('[data-cy="createRowModal"] .slot input').first().type('Row to delete') + cy.get('[data-cy="createRowSaveButton"]').click() + + cy.get('[data-cy="createRowModal"]').should('not.exist') + cy.get('[data-cy="ncTable"] table').contains('Row to delete').should('exist') + + // Find the row and click delete action + cy.get('[data-cy="ncTable"] [data-cy="customTableRow"]').contains('Row to delete').closest('[data-cy="customTableRow"]').within(() => { + // Click the actions menu button (three dots) + cy.get('.action-item__menutoggle').click() + }) + + // Click delete action + cy.get('[data-cy="deleteRowBtn"]').click() + + // Confirm deletion in dialog + cy.get('.dialog__actions .error').contains('Delete').click() + + // Verify the row was deleted + cy.get('.icon-loading').should('not.exist') + cy.get('[data-cy="ncTable"] table').contains('Row to delete').should('not.exist') + }) + + it('Handle unique constraint when duplicating row', () => { + // Create a new table with a unique column + cy.get('.icon-loading').should('not.exist') + cy.get('[data-cy="navigationCreateTableIcon"]').click({ force: true }) + cy.get('[data-cy="createTableModal"] input[type="text"]').clear().type('Unique Test Table') + cy.get('.tile').contains('Custom').click({ force: true }) + cy.get('[data-cy="createTableModal"]').should('be.visible') + cy.get('[data-cy="createTableSubmitBtn"]').click() + + cy.loadTable('Unique Test Table') + + // Add a unique text column + cy.get('[data-cy="customTableAction"] button').click() + cy.get('.action-button').contains('Create column').click() + + // Configure the column + cy.get('[data-cy="createColumnModal"] input').first().type('Unique Field') + cy.get('[data-cy="createColumnModal"] .column-type').click() + cy.get('.nc-select__option').contains('Text line').click() + + // Enable unique constraint + cy.get('[data-cy="createColumnModal"]').contains('Unique value').parent().find('.checkbox-radio-switch__input').click() + + cy.get('[data-cy="createColumnModal"] .nc-modal__content [data-cy="createColumnSaveButton"]').click() + cy.get('[data-cy="createColumnModal"]').should('not.exist') + + // Create a row with unique data + cy.get('[data-cy="createRowBtn"]').click({ force: true }) + cy.get('[data-cy="createRowModal"] .slot input').first().type('unique-value-123') + cy.get('[data-cy="createRowSaveButton"]').click() + + cy.get('[data-cy="createRowModal"]').should('not.exist') + cy.get('[data-cy="ncTable"] table').contains('unique-value-123').should('exist') + + // Try to duplicate the row + cy.get('[data-cy="ncTable"] [data-cy="customTableRow"]').contains('unique-value-123').closest('[data-cy="customTableRow"]').within(() => { + cy.get('.action-item__menutoggle').click() + }) + + cy.get('[data-cy="duplicateRowBtn"]').click() + + // Verify that a new row was created but without the unique field value + cy.get('.icon-loading').should('not.exist') + cy.get('[data-cy="ncTable"] [data-cy="customTableRow"]').should('have.length', 2) + + // The new row should exist but should not have the unique value duplicated + cy.get('[data-cy="ncTable"] table').contains('unique-value-123').should('have.length', 1) + }) }) diff --git a/src/shared/components/ncTable/partials/TableRow.vue b/src/shared/components/ncTable/partials/TableRow.vue index f752238ce9..9f04f85390 100644 --- a/src/shared/components/ncTable/partials/TableRow.vue +++ b/src/shared/components/ncTable/partials/TableRow.vue @@ -33,6 +33,7 @@