diff --git a/modules/planka/assets/planka.css b/modules/planka/assets/planka.css
index d768a12..5bf7bf7 100644
--- a/modules/planka/assets/planka.css
+++ b/modules/planka/assets/planka.css
@@ -514,3 +514,7 @@
border-radius: 6px;
margin: 2px 0;
}
+
+/* ── Indicateurs drop sur carte ────────────────────────────────────────────── */
+.pk-card.drop-top { border-top: 3px solid var(--primary, #4361ee) !important; }
+.pk-card.drop-bottom { border-bottom: 3px solid var(--primary, #4361ee) !important; }
diff --git a/planka.php b/planka.php
index 9162fe0..4d62142 100644
--- a/planka.php
+++ b/planka.php
@@ -150,7 +150,7 @@ function renderBoard() {
`;
- // Drag source — cards
+ // ── Drag source ──────────────────────────────────────────────────────────
area.querySelectorAll('.pk-card').forEach(el => {
el.addEventListener('click', () => openCardModal(el.dataset.id));
el.addEventListener('dragstart', e => {
@@ -160,39 +160,60 @@ function renderBoard() {
});
el.addEventListener('dragend', () => {
el.classList.remove('is-dragging');
- area.querySelectorAll('.drag-over,.drop-before-active').forEach(x => x.classList.remove('drag-over','drop-before-active'));
+ area.querySelectorAll('.drop-top,.drop-bottom,.drag-over').forEach(x =>
+ x.classList.remove('drop-top','drop-bottom','drag-over')
+ );
});
- });
- // Drop zone — before a specific card
- area.querySelectorAll('.pk-drop-before').forEach(el => {
- el.addEventListener('dragover', e => { e.preventDefault(); e.stopPropagation(); el.classList.add('drop-before-active'); });
- el.addEventListener('dragleave', () => el.classList.remove('drop-before-active'));
+ // ── Drop ON a card: top half = before, bottom half = after ──────────
+ el.addEventListener('dragover', e => {
+ e.preventDefault(); e.stopPropagation();
+ e.dataTransfer.dropEffect = 'move';
+ const mid = el.getBoundingClientRect().top + el.getBoundingClientRect().height / 2;
+ if (e.clientY < mid) { el.classList.add('drop-top'); el.classList.remove('drop-bottom'); }
+ else { el.classList.add('drop-bottom'); el.classList.remove('drop-top'); }
+ });
+ el.addEventListener('dragleave', () => el.classList.remove('drop-top','drop-bottom'));
el.addEventListener('drop', e => {
e.preventDefault(); e.stopPropagation();
- el.classList.remove('drop-before-active');
+ const isBefore = el.classList.contains('drop-top');
+ el.classList.remove('drop-top','drop-bottom');
const cardId = e.dataTransfer.getData('card-id');
- if (!cardId) return;
- const listId = el.dataset.listId;
- const prevPos = parseFloat(el.dataset.prevPos || 0);
- const curPos = parseFloat(el.dataset.curPos || 0);
- const newPos = prevPos === 0 ? curPos / 2 : (prevPos + curPos) / 2;
- moveCard(cardId, listId, newPos);
+ if (!cardId || cardId === el.dataset.id) return;
+ const listId = el.dataset.listId;
+ const sorted = state.cards.filter(c => c.listId === listId && c.id !== cardId)
+ .sort((a, b) => (a.position||0) - (b.position||0));
+ const idx = sorted.findIndex(c => c.id === el.dataset.id);
+ let pos;
+ if (isBefore) {
+ pos = idx === 0
+ ? (sorted[0].position || 65535) / 2
+ : ((sorted[idx-1].position||0) + (sorted[idx].position||0)) / 2;
+ } else {
+ pos = idx === sorted.length - 1
+ ? (sorted[idx].position || 0) + 65535
+ : ((sorted[idx].position||0) + (sorted[idx+1].position||0)) / 2;
+ }
+ moveCard(cardId, listId, pos);
});
});
- // Drop zone — end of column (whole .pk-cards)
+ // ── Drop at end of column (empty list or after all cards) ───────────────
area.querySelectorAll('.pk-cards').forEach(el => {
- el.addEventListener('dragover', e => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; el.classList.add('drag-over'); });
+ el.addEventListener('dragover', e => {
+ // Only highlight column when not over a card
+ if (!e.target.closest('.pk-card')) { e.preventDefault(); el.classList.add('drag-over'); }
+ });
el.addEventListener('dragleave', e => { if (!el.contains(e.relatedTarget)) el.classList.remove('drag-over'); });
el.addEventListener('drop', e => {
- e.preventDefault(); e.stopPropagation();
+ if (e.target.closest('.pk-card')) return; // handled by card listener
+ e.preventDefault();
el.classList.remove('drag-over');
const cardId = e.dataTransfer.getData('card-id');
if (!cardId) return;
- const listId = el.dataset.listId;
- const listCards = state.cards.filter(c => c.listId === listId && c.id !== cardId);
- const maxPos = listCards.reduce((m, c) => Math.max(m, c.position || 0), 0);
+ const listId = el.dataset.listId;
+ const others = state.cards.filter(c => c.listId === listId && c.id !== cardId);
+ const maxPos = others.reduce((m, c) => Math.max(m, c.position||0), 0);
moveCard(cardId, listId, maxPos + 65535);
});
});
@@ -206,15 +227,15 @@ function renderList(list) {
${esc(list.name)}
${cards.length}
-
+
${cards.length === 0 ? '
Déposer ici
' : ''}
- ${cards.map((c, i) => renderCard(c, cards[i-1] ?? null)).join('')}
+ ${cards.map(c => renderCard(c)).join('')}
`;
}
-function renderCard(card, prevCard) {
+function renderCard(card) {
const labels = (state.cardLabels || []).filter(cl => cl.cardId === card.id).map(cl => {
const lbl = (state.labels || []).find(l => l.id === cl.labelId);
if (!lbl) return '';
@@ -229,9 +250,7 @@ function renderCard(card, prevCard) {
return `📅 ${formatDate(card.dueDate)}`;
})() : '';
const taskHtml = cardTasks.length ? `☑ ${doneCount}/${cardTasks.length}` : '';
- const prevPos = prevCard ? (prevCard.position ?? 0) : 0;
return `
-
${labels ? `
${labels}
` : ''}
${esc(card.name)}