1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-08-05 13:35:27 +02:00

feat: Add ability to create new card at top (#1261)

Closes #1260
This commit is contained in:
Mashood ur Rehman 2025-07-21 15:10:58 +05:00 committed by GitHub
parent fdac299fc7
commit 2e0483d9e9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 62 additions and 42 deletions

View file

@ -21,7 +21,7 @@ const FiniteContent = React.memo(() => {
const handleCardCreate = useCallback(
(data, autoOpen) => {
dispatch(entryActions.createCardInFirstFiniteList(data, autoOpen));
dispatch(entryActions.createCardInFirstFiniteList(data, undefined, autoOpen));
},
[dispatch],
);

View file

@ -30,6 +30,15 @@ import PlusMathIcon from '../../../assets/images/plus-math-icon.svg?react';
import styles from './List.module.scss';
import globalStyles from '../../../styles.module.scss';
const AddCardPositions = {
TOP: 'top',
BOTTOM: 'bottom',
};
const INDEX_BY_ADD_CARD_POSITION = {
[AddCardPositions.TOP]: 0,
};
const List = React.memo(({ id, index }) => {
const selectListById = useMemo(() => selectors.makeSelectListById(), []);
@ -59,16 +68,18 @@ const List = React.memo(({ id, index }) => {
const dispatch = useDispatch();
const [t] = useTranslation();
const [isEditNameOpened, setIsEditNameOpened] = useState(false);
const [isAddCardOpened, setIsAddCardOpened] = useState(false);
const [addCardPosition, setAddCardPosition] = useState(null);
const wrapperRef = useRef(null);
const cardsWrapperRef = useRef(null);
const handleCardCreate = useCallback(
(data, autoOpen) => {
dispatch(entryActions.createCard(id, data, autoOpen));
dispatch(
entryActions.createCard(id, data, INDEX_BY_ADD_CARD_POSITION[addCardPosition], autoOpen),
);
},
[id, dispatch],
[id, dispatch, addCardPosition],
);
const handleHeaderClick = useCallback(() => {
@ -78,15 +89,15 @@ const List = React.memo(({ id, index }) => {
}, [list.isPersisted, canEdit]);
const handleAddCardClick = useCallback(() => {
setIsAddCardOpened(true);
setAddCardPosition(AddCardPositions.BOTTOM);
}, []);
const handleAddCardClose = useCallback(() => {
setIsAddCardOpened(false);
setAddCardPosition(null);
}, []);
const handleCardAdd = useCallback(() => {
setIsAddCardOpened(true);
setAddCardPosition(AddCardPositions.TOP);
}, []);
const handleNameEdit = useCallback(() => {
@ -104,14 +115,26 @@ const List = React.memo(({ id, index }) => {
);
useDidUpdate(() => {
if (isAddCardOpened) {
cardsWrapperRef.current.scrollTop = cardsWrapperRef.current.scrollHeight;
if (!addCardPosition) {
return;
}
}, [cardIds, isAddCardOpened]);
cardsWrapperRef.current.scrollTop =
addCardPosition === AddCardPositions.TOP ? 0 : cardsWrapperRef.current.scrollHeight;
}, [cardIds, addCardPosition]);
const ActionsPopup = usePopup(ActionsStep);
const ArchiveCardsPopup = usePopup(ArchiveCardsStep);
const addCardNode = canAddCard && (
<AddCard
isOpened={!!addCardPosition}
className={styles.addCard}
onCreate={handleCardCreate}
onClose={handleAddCardClose}
/>
);
const cardsNode = (
<Droppable
droppableId={`list:${id}`}
@ -122,18 +145,12 @@ const List = React.memo(({ id, index }) => {
// eslint-disable-next-line react/jsx-props-no-spreading
<div {...droppableProps} ref={innerRef}>
<div className={styles.cards}>
{addCardPosition === AddCardPositions.TOP && addCardNode}
{cardIds.map((cardId, cardIndex) => (
<DraggableCard key={cardId} id={cardId} index={cardIndex} className={styles.card} />
))}
{placeholder}
{canAddCard && (
<AddCard
isOpened={isAddCardOpened}
className={styles.addCard}
onCreate={handleCardCreate}
onClose={handleAddCardClose}
/>
)}
{addCardPosition === AddCardPositions.BOTTOM && addCardNode}
</div>
</div>
)}
@ -213,7 +230,7 @@ const List = React.memo(({ id, index }) => {
<div ref={cardsWrapperRef} className={styles.cardsInnerWrapper}>
<div className={styles.cardsOuterWrapper}>{cardsNode}</div>
</div>
{!isAddCardOpened && canAddCard && (
{!addCardPosition && canAddCard && (
<button
type="button"
disabled={!list.isPersisted}

View file

@ -173,8 +173,8 @@ export default {
CARDS_IN_CURRENT_LIST_FETCH: `${PREFIX}/CARDS_IN_CURRENT_LIST_FETCH`,
CARDS_UPDATE_HANDLE: `${PREFIX}/CARDS_UPDATE_HANDLE`,
CARD_CREATE: `${PREFIX}/CARD_CREATE`,
CARD_IN_CURRENT_LIST_CREATE: `${PREFIX}/CARD_IN_CURRENT_LIST_CREATE`,
CARD_IN_FIRST_FINITE_LIST_CREATE: `${PREFIX}/CARD_IN_FIRST_FINITE_LIST_CREATE`,
CARD_IN_CURRENT_LIST_CREATE: `${PREFIX}/CARD_IN_CURRENT_LIST_CREATE`,
CARD_CREATE_HANDLE: `${PREFIX}/CARD_CREATE_HANDLE`,
CARD_UPDATE: `${PREFIX}/CARD_UPDATE`,
CURRENT_CARD_UPDATE: `${PREFIX}/CURRENT_CARD_UPDATE`,

View file

@ -18,25 +18,27 @@ const handleCardsUpdate = (cards, activities) => ({
},
});
const createCard = (listId, data, autoOpen) => ({
const createCard = (listId, data, index, autoOpen = false) => ({
type: EntryActionTypes.CARD_CREATE,
payload: {
listId,
data,
index,
autoOpen,
},
});
const createCardInCurrentList = (data, autoOpen) => ({
type: EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE,
const createCardInFirstFiniteList = (data, index = 0, autoOpen = false) => ({
type: EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE,
payload: {
data,
index,
autoOpen,
},
});
const createCardInFirstFiniteList = (data, autoOpen) => ({
type: EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE,
const createCardInCurrentList = (data, autoOpen = false) => ({
type: EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE,
payload: {
data,
autoOpen,
@ -178,8 +180,8 @@ export default {
fetchCardsInCurrentList,
handleCardsUpdate,
createCard,
createCardInCurrentList,
createCardInFirstFiniteList,
createCardInCurrentList,
handleCardCreate,
updateCard,
updateCurrentCard,

View file

@ -113,7 +113,7 @@ export function* handleCardsUpdate(cards, activities) {
yield put(actions.handleCardsUpdate(cards, activities));
}
export function* createCard(listId, data, autoOpen) {
export function* createCard(listId, data, index, autoOpen) {
const localId = yield call(createLocalId);
const list = yield select(selectors.selectListById, listId);
@ -127,7 +127,7 @@ export function* createCard(listId, data, autoOpen) {
};
if (isListFinite(list)) {
nextData.position = yield select(selectors.selectNextCardPosition, listId);
nextData.position = yield select(selectors.selectNextCardPosition, listId, index);
}
yield put(
@ -167,16 +167,16 @@ export function* createCard(listId, data, autoOpen) {
}
}
export function* createCardInFirstFiniteList(data, index, autoOpen) {
const firstFiniteListId = yield select(selectors.selectFirstFiniteListId);
yield call(createCard, firstFiniteListId, data, index, autoOpen);
}
export function* createCardInCurrentList(data, autoOpen) {
const currentListId = yield select(selectors.selectCurrentListId);
yield call(createCard, currentListId, data, autoOpen);
}
export function* createCardInFirstFiniteList(data, autoOpen) {
const firstFiniteListId = yield select(selectors.selectFirstFiniteListId);
yield call(createCard, firstFiniteListId, data, autoOpen);
yield call(createCard, currentListId, data, undefined, autoOpen);
}
export function* handleCardCreate(card) {
@ -596,9 +596,9 @@ export default {
fetchCardsInCurrentList,
handleCardsUpdate,
createCard,
createCardInFirstFiniteList,
createCardInCurrentList,
handleCardCreate,
createCardInFirstFiniteList,
updateCard,
updateCurrentCard,
handleCardUpdate,

View file

@ -16,15 +16,16 @@ export default function* cardsWatchers() {
takeEvery(EntryActionTypes.CARDS_UPDATE_HANDLE, ({ payload: { cards, activities } }) =>
services.handleCardsUpdate(cards, activities),
),
takeEvery(EntryActionTypes.CARD_CREATE, ({ payload: { listId, data, autoOpen } }) =>
services.createCard(listId, data, autoOpen),
),
takeEvery(EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, ({ payload: { data, autoOpen } }) =>
services.createCardInCurrentList(data, autoOpen),
takeEvery(EntryActionTypes.CARD_CREATE, ({ payload: { listId, data, index, autoOpen } }) =>
services.createCard(listId, data, index, autoOpen),
),
takeEvery(
EntryActionTypes.CARD_IN_FIRST_FINITE_LIST_CREATE,
({ payload: { data, autoOpen } }) => services.createCardInFirstFiniteList(data, autoOpen),
({ payload: { data, index, autoOpen } }) =>
services.createCardInFirstFiniteList(data, index, autoOpen),
),
takeEvery(EntryActionTypes.CARD_IN_CURRENT_LIST_CREATE, ({ payload: { data, autoOpen } }) =>
services.createCardInCurrentList(data, autoOpen),
),
takeEvery(EntryActionTypes.CARD_CREATE_HANDLE, ({ payload: { card } }) =>
services.handleCardCreate(card),