Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
e444694
Adds settings for creating new cards at the top or bottom of stacks
theoholl Jul 19, 2026
e2b6939
Add a button for adding cards to the top or bottom of a stack
theoholl Jul 19, 2026
8e62890
Move 'Add card' button at bottom of stack right underneath last card …
theoholl Jul 19, 2026
8912d67
Add fade-out gradient for overflowing stacks
theoholl Jul 19, 2026
ef6b136
Use a different approach for the overflow gradient because CSS `mask-…
theoholl Jul 19, 2026
60aa2d0
Forms to add a new card can be close by pressing enter key
theoholl Jul 19, 2026
0487058
Merge remote-tracking branch 'origin/main' into add-new-cards-to-top-…
theoholl Jul 19, 2026
6e41520
Improve position of 'Add card' button for empty stacks when setting i…
theoholl Jul 19, 2026
8988831
Keep the default setting adding cards to the bottom of a list
theoholl Jul 19, 2026
5a12a1a
Use a real + icon instead of using text for 'Add card' button
theoholl Jul 19, 2026
5744226
Removed duplicated user account check and improved method naming
theoholl Jul 19, 2026
050a23d
Fix cypress tests
theoholl Jul 20, 2026
fb0be1c
Merge branch 'main' into add-new-cards-to-top-or-bottom
theoholl Jul 20, 2026
bcaea9d
Fix a cypress test
theoholl Jul 20, 2026
1971a97
Simplify JS, use CSS for layout instead if possible
theoholl Jul 22, 2026
405dc44
Avoid layout shift when clicking the 'Add to card' button in add-to-t…
theoholl Jul 22, 2026
08aaf0f
Use muted colors for 'Add card' button
theoholl Jul 22, 2026
d98a261
Fix dropzone for empty add-to-bottom stacks
theoholl Jul 26, 2026
95dd1c0
Remove all transitions and make remove padding from empty card lists …
theoholl Jul 28, 2026
fc05c33
Clean up CSS selectors
theoholl Jul 28, 2026
edfa253
Improve drop behaviour for empty stacks
theoholl Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cypress/e2e/cardColor.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('Card color', function () {

const newCardTitle = 'Card with color'

cy.get('.button-vue[aria-label*="Add card"]')
cy.get('[data-cy="action:add-card"]')
.first().click()
cy.get('.stack__card-add form input#new-stack-input-main')
.type(newCardTitle)
Expand Down
25 changes: 17 additions & 8 deletions cypress/e2e/cardFeatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ describe('Card', function () {
cy.get('.board .stack').eq(0).within(() => {
cy.get('.card:contains("Hello world")').should('be.visible')

cy.get('.button-vue[aria-label*="Add card"]')
cy.get('[data-cy="action:add-card"]')
.first().click()

cy.get('.stack__card-add form input#new-stack-input-main')
Expand All @@ -69,7 +69,7 @@ describe('Card', function () {
cy.intercept({ method: 'POST', url: '**/ocs/v2.php/apps/deck/api/v1.0/cards' }).as('save')
cy.intercept({ method: 'GET', url: '**/apps/deck/boards/*' }).as('getBoard')

cy.get('.button-vue[aria-label*="Add card"]')
cy.get('[data-cy="action:add-card"]')
.first().click()

// Somehow this avoids the electron crash
Expand Down Expand Up @@ -98,7 +98,7 @@ describe('Card', function () {
cy.visit(`/apps/deck/#/board/${boardId}`)
const absoluteUrl = `https://example.com`
cy.get('.board .stack').eq(0).within(() => {
cy.get('.button-vue[aria-label*="Add card"]')
cy.get('[data-cy="action:add-card"]')
.first().click()

cy.get('.stack__card-add form input#new-stack-input-main')
Expand All @@ -120,7 +120,7 @@ describe('Card', function () {
const absoluteUrl = `https://example.com`
const plainTitle = 'New title'
cy.get('.board .stack').eq(0).within(() => {
cy.get('.button-vue[aria-label*="Add card"]')
cy.get('[data-cy="action:add-card"]')
.first().click()

cy.get('.stack__card-add form input#new-stack-input-main')
Expand Down Expand Up @@ -260,7 +260,7 @@ describe('Card', function () {
it('Set a due date', function () {
const newCardTitle = 'Card with a due date'

cy.get('.button-vue[aria-label*="Add card"]')
cy.get('[data-cy="action:add-card"]')
.first().click()
cy.get('.stack__card-add form input#new-stack-input-main')
.type(newCardTitle)
Expand Down Expand Up @@ -296,13 +296,20 @@ describe('Card', function () {
it('Add a label', function () {
const newCardTitle = 'Card with labels'

cy.get('.button-vue[aria-label*="Add card"]')
cy.get('[data-cy="action:add-card"]')
.first().click()
cy.get('.stack__card-add form input#new-stack-input-main')
.type(newCardTitle)
cy.get('.stack__card-add form input[type=submit]')
.first().click()
cy.get(`.card:contains("${newCardTitle}")`).should('be.visible').click()
cy.get('body').then(($body) => {
const addCardInput = $body.find('.stack__card-add form input#new-stack-input-main')
if (addCardInput.length) {
cy.wrap(addCardInput.first()).type('{esc}')
}
})
cy.get('.stack__card-add form').should('not.exist')

// Add delay to ensure the events are bound
cy.wait(1000)
Expand All @@ -316,8 +323,10 @@ describe('Card', function () {
cy.get('.vs__selected .tag:contains("Action needed")')
.parent().find('button').click()

cy.get(`.card:contains("${newCardTitle}")`).find('.labels li:contains("Later")')
.should('be.visible')
cy.get(`.card:contains("${newCardTitle}")`)
.scrollIntoView({ block: 'center' })
.find('.labels li:contains("Later")')
.should('exist')
cy.get(`.card:contains("${newCardTitle}")`).find('.labels li:contains("Action needed")')
.should('not.exist')
})
Expand Down
6 changes: 3 additions & 3 deletions cypress/e2e/sharingFeatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ describe('Board', function() {
cy.login(recipient)
cy.visit(`/apps/deck/#/board/${boardId}`)
cy.get('.board-title').contains(board.title)
cy.get('.button-vue[aria-label*="Add card"]')
cy.get('[data-cy="action:add-card"]')
.should('not.exist')
})
})
Expand All @@ -50,7 +50,7 @@ describe('Board', function() {
cy.login(recipient)
cy.visit(`/apps/deck/#/board/${boardId}`)
cy.get('.board-title').contains(board.title)
cy.get('.button-vue[aria-label*="Add card"]')
cy.get('[data-cy="action:add-card"]')
.should('not.exist')
})
})
Expand All @@ -71,7 +71,7 @@ describe('Board', function() {
cy.login(recipient)
cy.visit(`/apps/deck/#/board/${boardId}`)
cy.get('.board-title').contains(board.title)
cy.get('.button-vue[aria-label*="Add card"]')
cy.get('[data-cy="action:add-card"]')
.first().click()
})
})
Expand Down
18 changes: 17 additions & 1 deletion lib/Service/ConfigService.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ public function getAll(): array {
$data = [
'calendar' => $this->isCalendarEnabled(),
'cardDetailsInModal' => $this->isCardDetailsInModal(),
'cardIdBadge' => $this->isCardIdBadgeEnabled()
'cardIdBadge' => $this->isCardIdBadgeEnabled(),
'stackAddCardAtTop' => $this->isStackAddCardAtTopEnabled()
];
if ($this->groupManager->isAdmin($userId)) {
$data['groupLimit'] = $this->get('groupLimit');
Expand Down Expand Up @@ -90,6 +91,8 @@ public function get(string $key) {
return false;
}
return (bool)$this->config->getUserValue($this->getUserId(), Application::APP_ID, 'cardIdBadge', false);
case 'stackAddCardAtTop':
return $this->isStackAddCardAtTopEnabled();
}
return false;
}
Expand Down Expand Up @@ -134,6 +137,15 @@ public function isCardIdBadgeEnabled(): bool {
return (bool)$this->config->getUserValue($userId, Application::APP_ID, 'cardIdBadge', $defaultState);
}

public function isStackAddCardAtTopEnabled(): bool {
$userId = $this->getUserId();
if ($userId === null) {
return false;
}

return (bool)$this->config->getUserValue($userId, Application::APP_ID, 'stackAddCardAtTop', false);
}

public function ensureFederationEnabled() {
if (!$this->get('federationEnabled')) {
throw new FederationDisabledException();
Expand Down Expand Up @@ -181,6 +193,10 @@ public function set($key, $value) {
$this->config->setUserValue($userId, Application::APP_ID, 'cardIdBadge', (string)$value);
$result = $value;
break;
case 'stackAddCardAtTop':
$this->config->setUserValue($userId, Application::APP_ID, 'stackAddCardAtTop', (string)$value);
$result = $value;
break;
case 'board':
// extra check that user only send one of the allowed board settings and not something random
$parts = explode(':', $key, 3);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Controls.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
{{ overviewName }}
</h2>
<NcActions>
<NcActionButton icon="icon-add" @click="clickShowAddCardModel">
<NcActionButton data-cy="action:add-card" icon="icon-add" @click="clickShowAddCardModel">
{{ t('deck', 'Add card') }}
</NcActionButton>
</NcActions>
Expand Down
10 changes: 10 additions & 0 deletions src/components/DeckAppSettings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
<NcFormBox>
<NcFormBoxSwitch v-model="cardDetailsInModal"
:label="t('deck', 'Use bigger card view')" />
<NcFormBoxSwitch v-model="stackAddCardAtTop"
:label="t('deck', 'Add new cards at the top of a list')" />
</NcFormBox>
</NcAppSettingsSection>

Expand Down Expand Up @@ -119,6 +121,14 @@ export default {
this.$store.dispatch('setConfig', { cardDetailsInModal: newValue })
},
},
stackAddCardAtTop: {
get() {
return this.$store.getters.config('stackAddCardAtTop') === true
},
set(newValue) {
this.$store.dispatch('setConfig', { stackAddCardAtTop: newValue })
},
},
cardIdBadge: {
get() {
return this.$store.getters.config('cardIdBadge')
Expand Down
3 changes: 1 addition & 2 deletions src/components/board/Board.vue
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,11 @@ export default {
position: relative;

.smooth-dnd-container.vertical {
$margin-x: calc($stack-gap * -1);
display: flex;
flex-direction: column;
gap: $stack-gap;
padding: $stack-gap;
margin: 0 $margin-x;
margin: 0 calc(#{$stack-gap} * -1);
overflow-y: auto;
scrollbar-gutter: stable;
}
Expand Down
Loading
Loading