Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
af8573f
Adds settings for creating new cards at the top or bottom of stacks
theoholl Jul 19, 2026
2008bc2
Add a button for adding cards to the top or bottom of a stack
theoholl Jul 19, 2026
b9e23ed
Move 'Add card' button at bottom of stack right underneath last card …
theoholl Jul 19, 2026
e5d8636
Add fade-out gradient for overflowing stacks
theoholl Jul 19, 2026
2a9fbbc
Use a different approach for the overflow gradient because CSS `mask-…
theoholl Jul 19, 2026
d451037
Forms to add a new card can be close by pressing enter key
theoholl Jul 19, 2026
a81810f
Improve position of 'Add card' button for empty stacks when setting i…
theoholl Jul 19, 2026
4bfdc93
Keep the default setting adding cards to the bottom of a list
theoholl Jul 19, 2026
29c1df8
Use a real + icon instead of using text for 'Add card' button
theoholl Jul 19, 2026
0d5f45a
Removed duplicated user account check and improved method naming
theoholl Jul 19, 2026
f28ec96
Fix cypress tests
theoholl Jul 20, 2026
1f90fd2
Fix a cypress test
theoholl Jul 20, 2026
bd68c24
Simplify JS, use CSS for layout instead if possible
theoholl Jul 22, 2026
7d87dc3
Avoid layout shift when clicking the 'Add to card' button in add-to-t…
theoholl Jul 22, 2026
c8b5437
Use muted colors for 'Add card' button
theoholl Jul 22, 2026
8678032
Fix dropzone for empty add-to-bottom stacks
theoholl Jul 26, 2026
63f32bc
Remove all transitions and make remove padding from empty card lists …
theoholl Jul 28, 2026
e5fcdca
Clean up CSS selectors
theoholl Jul 28, 2026
60f0437
Improve drop behaviour for empty stacks
theoholl Jul 28, 2026
e68afa9
Simplify add-cards-to-bottom stacks
theoholl Jul 30, 2026
0e0f54b
Fix new card focus animation for add-to-top stacks
theoholl Jul 30, 2026
bf22997
Remove add button form duplication
theoholl Jul 30, 2026
69f7436
Extract add card form to keep tab order intact
theoholl Jul 30, 2026
e08c928
Fix card insertion position
theoholl Aug 8, 2026
d4d3c3e
fix(create): centralize insert-at-position handling and close add-car…
theoholl Aug 8, 2026
5e64a6c
Revert(create): restore explicit insertAtPosition opt-in
theoholl Aug 8, 2026
aba3ca4
Remove unused stack__cards-list class and reorderCard return value
theoholl Aug 8, 2026
ed63f0a
Clean up review findings
theoholl Aug 8, 2026
d92d52e
Simplify bottom add-card layout
theoholl Aug 8, 2026
236f7ee
Restore bottom add-card spacing
theoholl Aug 8, 2026
1012a40
Fix StackCardAdd CSS covering card content in bottom mode
Copilot Aug 9, 2026
cecd390
Add matching bottom list gradient
Copilot Aug 9, 2026
11ec824
Fix flaky Smart picker visibility assertion
Copilot Aug 9, 2026
7c64043
fix(l10n): Update translations from Transifex
nextcloud-bot Aug 10, 2026
9819378
Update Cardsidebar.vue fix: Prevent TypeError crash on undefined curr…
aquananu Jul 31, 2026
b03feb1
Chore(deps-dev): Bump undici from 6.27.0 to 6.28.0
dependabot[bot] Aug 5, 2026
0cc178e
Chore(deps): Bump dorny/paths-filter from 4.0.1 to 4.0.2
dependabot[bot] Jul 18, 2026
748924f
Fix Stack.vue conflict resolution
theoholl Aug 20, 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 .github/workflows/lint-eslint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
src: ${{ steps.changes.outputs.src}}

steps:
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: changes
continue-on-error: true
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/node.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
src: ${{ steps.changes.outputs.src}}

steps:
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: changes
continue-on-error: true
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/phpunit-mysql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ jobs:
src: ${{ steps.changes.outputs.src}}

steps:
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: changes
continue-on-error: true
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/phpunit-pgsql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
src: ${{ steps.changes.outputs.src }}

steps:
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: changes
continue-on-error: true
with:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/phpunit-sqlite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
src: ${{ steps.changes.outputs.src}}

steps:
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
- uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: changes
continue-on-error: true
with:
Expand Down
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
96 changes: 88 additions & 8 deletions cypress/e2e/cardFeatures.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,17 @@ const useModal = (useModal) => {
})
}

const addCardsAtTop = (enabled) => {
return cy.request({
method: 'POST',
url: `${Cypress.env('baseUrl')}/ocs/v2.php/apps/deck/api/v1.0/config/stackAddCardAtTop?format=json`,
auth,
body: { value: enabled },
}).then((response) => {
expect(response.status).to.eq(200)
})
}

describe('Card', function () {
let boardId
before(function () {
Expand Down Expand Up @@ -52,7 +63,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 @@ -63,13 +74,78 @@ describe('Card', function () {
})
})

describe('New card position', function() {
afterEach(function() {
addCardsAtTop(false)
})

it('Adds new cards to the configured side of the list', function() {
const bottomCardTitle = 'Card added at bottom'
const firstTopCardTitle = 'First card added at top'
const secondTopCardTitle = 'Second card added at top'
const finalBottomCardTitle = 'Card added at bottom again'

addCardsAtTop(false)
cy.intercept({ method: 'POST', url: '**/ocs/v2.php/apps/deck/api/v1.0/cards' }).as('createCard')
cy.intercept({ method: 'PUT', url: '**/ocs/v2.php/apps/deck/api/v1.0/cards/*/reorder' }).as('reorderCard')
cy.intercept({ method: 'POST', url: '**/ocs/v2.php/apps/deck/api/v1.0/config/stackAddCardAtTop' }).as('setCardPosition')
cy.visit(`/apps/deck/#/board/${boardId}`)

cy.get('.board .stack').eq(0).within(() => {
cy.get('[data-cy="action:add-card"]').click()
cy.get('.stack__card-add input[type="text"]').type(bottomCardTitle)
cy.get('.stack__card-add input[type="submit"]').click()
cy.wait('@createCard')
cy.get('.card').last().should('contain', bottomCardTitle)
})

cy.get('[data-cy="navigation:settings"]').click()
cy.get('[data-cy="setting:add-card-at-top"] input[role="switch"]').check({ force: true })
cy.wait('@setCardPosition')
cy.visit(`/apps/deck/#/board/${boardId}`)

for (const title of [firstTopCardTitle, secondTopCardTitle]) {
cy.get('.board .stack').eq(0).within(() => {
cy.get('[data-cy="action:add-card"]').click()
cy.get('.stack__card-add input[type="text"]').type(title)
cy.get('.stack__card-add input[type="submit"]').click()
cy.wait('@createCard')
})
}

cy.get('.board .stack').eq(0).within(() => {
cy.get('.card').eq(0).should('contain', secondTopCardTitle)
cy.get('.card').eq(1).should('contain', firstTopCardTitle)
})
cy.get('@reorderCard.all').should('have.length', 0)

cy.reload()
cy.get('.board .stack').eq(0).within(() => {
cy.get('.card').eq(0).should('contain', secondTopCardTitle)
cy.get('.card').eq(1).should('contain', firstTopCardTitle)
})

cy.get('[data-cy="navigation:settings"]').click()
cy.get('[data-cy="setting:add-card-at-top"] input[role="switch"]').uncheck({ force: true })
cy.wait('@setCardPosition')
cy.visit(`/apps/deck/#/board/${boardId}`)
cy.get('.board .stack').eq(0).within(() => {
cy.get('[data-cy="action:add-card"]').click()
cy.get('.stack__card-add input[type="text"]').type(finalBottomCardTitle)
cy.get('.stack__card-add input[type="submit"]').click()
cy.wait('@createCard')
cy.get('.card').last().should('contain', finalBottomCardTitle)
})
})
})

it('Create card from overview', function () {
cy.visit(`/apps/deck/#/`)
const newCardTitle = 'Test create from overview'
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 +174,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 +196,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 @@ -238,7 +314,9 @@ describe('Card', function () {
cy.visit(`/apps/deck/board/${boardId}`)
cy.reload()
cy.get('.board .stack').eq(0).within(() => {
cy.get(`.card:contains("${newCardTitle}")`).should('be.visible')
cy.get(`.card:contains("${newCardTitle}")`)
.scrollIntoView({ block: 'center' })
.should('be.visible')
})
})
})
Expand All @@ -260,7 +338,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,7 +374,7 @@ 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)
Expand All @@ -316,7 +394,9 @@ 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")')
cy.get(`.card:contains("${newCardTitle}")`)
.scrollIntoView({ block: 'center' })
.find('.labels li:contains("Later")')
.should('be.visible')
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
2 changes: 2 additions & 0 deletions l10n/fr.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ OC.L10N.register(
"Mark card as completed/not completed" : "Marquer la carte (non) terminée",
"Open card menu" : "Ouvrir le menu de la carte",
"Currently present people" : "Personnes actuellement présentes",
"Search cards" : "Rechercher des cartes",
"Loading board" : "Chargement du tableau…",
"Board not found" : "Tableau non trouvé",
"Create a new list to add cards to this board" : "Créer une nouvelle liste pour ajouter des cartes à ce tableau",
Expand Down Expand Up @@ -258,6 +259,7 @@ OC.L10N.register(
"title and color value must be provided" : "Le titre et la couleur doivent être renseignés",
"Edit" : "Modifier",
"Add a new tag" : "Ajouter une nouvelle étiquette",
"Search boards" : "Rechercher des tableaux",
"Board name" : "Nom du tableau",
"Members" : "Membres",
"Assign to users/groups/team" : "Attribuer à des utilisateurs / groupes / équipes",
Expand Down
2 changes: 2 additions & 0 deletions l10n/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@
"Mark card as completed/not completed" : "Marquer la carte (non) terminée",
"Open card menu" : "Ouvrir le menu de la carte",
"Currently present people" : "Personnes actuellement présentes",
"Search cards" : "Rechercher des cartes",
"Loading board" : "Chargement du tableau…",
"Board not found" : "Tableau non trouvé",
"Create a new list to add cards to this board" : "Créer une nouvelle liste pour ajouter des cartes à ce tableau",
Expand Down Expand Up @@ -256,6 +257,7 @@
"title and color value must be provided" : "Le titre et la couleur doivent être renseignés",
"Edit" : "Modifier",
"Add a new tag" : "Ajouter une nouvelle étiquette",
"Search boards" : "Rechercher des tableaux",
"Board name" : "Nom du tableau",
"Members" : "Membres",
"Assign to users/groups/team" : "Attribuer à des utilisateurs / groupes / équipes",
Expand Down
8 changes: 4 additions & 4 deletions l10n/uk.js
Original file line number Diff line number Diff line change
Expand Up @@ -315,16 +315,16 @@ OC.L10N.register(
"Choose attachment" : "Вибрати вкладення",
"Select Date" : "Вкажіть дату",
"Later today – {timeLocale}" : "Пізніше сьогодні - {timeLocale}",
"Set due date for later today" : "Встановіть термін сплати на сьогоднішній день",
"Set due date for later today" : "Встановіть термін завершення на пізніше сьогодні",
"Tomorrow – {timeLocale}" : "Завтра - {timeLocale}",
"Set due date for tomorrow" : "Встановіть дедлайн на завтра",
"This weekend – {timeLocale}" : "На цих вихідних - {timeLocale}",
"Set due date for this weekend" : "Встановіть термін виконання на ці вихідні",
"Next week – {timeLocale}" : "Наступного тижня - {timeLocale}",
"Set due date for next week" : "Встановіть термін виконання на наступний тиждень",
"Assign a due date to this card…" : "Призначте цій картці термін оплати...",
"Assign a due date to this card…" : "Призначте цій картці термін завершення...",
"Set a due date" : "Встановити дату завершення",
"Add due date" : "Додати термін сплати",
"Add due date" : "Додати термін завершення",
"Choose a date" : "Виберіть дату",
"Remove due date" : "Вилучити кінцеву дату",
"Due at:" : "На часі:",
Expand Down Expand Up @@ -382,7 +382,7 @@ OC.L10N.register(
"Delete board" : "Вилучити дошку",
"Leave board" : "Залишити дошку",
"Importing board..." : "Імпортування дошки...",
"Board imported successfully" : "Плату успішно імпортовано",
"Board imported successfully" : "Дошку успішно імпортовано",
"Import board" : "Імпорт дошки",
"Clone {boardTitle}" : "Копіювати {boardTitle}",
"Clone cards" : "Копіювати картки",
Expand Down
8 changes: 4 additions & 4 deletions l10n/uk.json
Original file line number Diff line number Diff line change
Expand Up @@ -313,16 +313,16 @@
"Choose attachment" : "Вибрати вкладення",
"Select Date" : "Вкажіть дату",
"Later today – {timeLocale}" : "Пізніше сьогодні - {timeLocale}",
"Set due date for later today" : "Встановіть термін сплати на сьогоднішній день",
"Set due date for later today" : "Встановіть термін завершення на пізніше сьогодні",
"Tomorrow – {timeLocale}" : "Завтра - {timeLocale}",
"Set due date for tomorrow" : "Встановіть дедлайн на завтра",
"This weekend – {timeLocale}" : "На цих вихідних - {timeLocale}",
"Set due date for this weekend" : "Встановіть термін виконання на ці вихідні",
"Next week – {timeLocale}" : "Наступного тижня - {timeLocale}",
"Set due date for next week" : "Встановіть термін виконання на наступний тиждень",
"Assign a due date to this card…" : "Призначте цій картці термін оплати...",
"Assign a due date to this card…" : "Призначте цій картці термін завершення...",
"Set a due date" : "Встановити дату завершення",
"Add due date" : "Додати термін сплати",
"Add due date" : "Додати термін завершення",
"Choose a date" : "Виберіть дату",
"Remove due date" : "Вилучити кінцеву дату",
"Due at:" : "На часі:",
Expand Down Expand Up @@ -380,7 +380,7 @@
"Delete board" : "Вилучити дошку",
"Leave board" : "Залишити дошку",
"Importing board..." : "Імпортування дошки...",
"Board imported successfully" : "Плату успішно імпортовано",
"Board imported successfully" : "Дошку успішно імпортовано",
"Import board" : "Імпорт дошки",
"Clone {boardTitle}" : "Копіювати {boardTitle}",
"Clone cards" : "Копіювати картки",
Expand Down
11 changes: 9 additions & 2 deletions lib/Controller/CardOcsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
use OCP\IRequest;

class CardOcsController extends OCSController {
/**
* Sentinel order used when the client does not request a specific position.
* Cards created with this order are appended to the end of the stack.
*/
private const DEFAULT_ORDER = 999;

public function __construct(
string $appName,
IRequest $request,
Expand All @@ -36,7 +42,7 @@ public function __construct(

#[NoAdminRequired]
#[PublicPage]
public function create(string $title, int $stackId, ?int $boardId = null, ?string $type = 'plain', ?string $owner = null, ?int $order = 999, ?string $description = '', $duedate = null, $startdate = null, ?array $labels = [], ?array $users = [], ?string $color = null) {
public function create(string $title, int $stackId, ?int $boardId = null, ?string $type = 'plain', ?string $owner = null, ?int $order = self::DEFAULT_ORDER, ?string $description = '', $duedate = null, $startdate = null, ?array $labels = [], ?array $users = [], ?string $color = null) {
if ($boardId) {
$board = $this->boardService->find($boardId, false);
if ($board->getExternalId()) {
Expand All @@ -48,7 +54,8 @@ public function create(string $title, int $stackId, ?int $boardId = null, ?strin
if (!$owner) {
$owner = $this->userId;
}
$card = $this->cardService->create($title, $stackId, $type, $order, $owner, $description, $duedate, $startdate, $color);
// An explicit order means the client wants the card at that position, so shift the surrounding cards
$card = $this->cardService->create($title, $stackId, $type, $order, $owner, $description, $duedate, $startdate, $color, insertAtPosition: $order !== self::DEFAULT_ORDER);

// foreach ($labels as $label) {
// $this->assignLabel($card->getId(), $label);
Expand Down
Loading