1
0
Fork 0
mirror of https://github.com/plankanban/planka.git synced 2025-08-08 06:55:30 +02:00

feat: Filter cards by keyword with advanced capabilities (#713)

Closes #706
This commit is contained in:
Emmanuel Guyot 2024-04-22 23:15:31 +02:00 committed by GitHub
parent 8747aa59de
commit eb56b2147b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 267 additions and 1 deletions

View file

@ -3,6 +3,9 @@ import { attr, fk, many } from 'redux-orm';
import BaseModel from './BaseModel';
import ActionTypes from '../constants/ActionTypes';
import User from './User';
import Label from './Label';
export default class extends BaseModel {
static modelName = 'Board';
@ -25,6 +28,9 @@ export default class extends BaseModel {
}),
filterUsers: many('User', 'filterBoards'),
filterLabels: many('Label', 'filterBoards'),
filterText: attr({
getDefault: () => '',
}),
};
static reducer({ type, payload }, Board) {
@ -167,6 +173,47 @@ export default class extends BaseModel {
Board.withId(payload.boardId).filterLabels.remove(payload.id);
break;
case ActionTypes.TEXT_FILTER_IN_CURRENT_BOARD: {
const board = Board.withId(payload.boardId);
let filterText = payload.text;
const posSpace = filterText.indexOf(' ');
// Shortcut to user filters
const posAT = filterText.indexOf('@');
if (posAT >= 0 && posSpace > 0 && posAT < posSpace) {
const userId = User.findUsersFromText(
filterText.substring(posAT + 1, posSpace),
board.memberships.toModelArray().map((membership) => membership.user),
);
if (
userId &&
board.filterUsers.toModelArray().filter((user) => user.id === userId).length === 0
) {
board.filterUsers.add(userId);
filterText = filterText.substring(0, posAT);
}
}
// Shortcut to label filters
const posSharp = filterText.indexOf('#');
if (posSharp >= 0 && posSpace > 0 && posSharp < posSpace) {
const labelId = Label.findLabelsFromText(
filterText.substring(posSharp + 1, posSpace),
board.labels.toModelArray(),
);
if (
labelId &&
board.filterLabels.toModelArray().filter((label) => label.id === labelId).length === 0
) {
board.filterLabels.add(labelId);
filterText = filterText.substring(0, posSharp);
}
}
board.update({ filterText });
break;
}
default:
}
}

View file

@ -14,6 +14,11 @@ export default class extends BaseModel {
position: attr(),
name: attr(),
description: attr(),
creatorUserId: oneToOne({
to: 'User',
as: 'creatorUser',
relatedName: 'ownCards',
}),
dueDate: attr(),
stopwatch: attr(),
isSubscribed: attr({

View file

@ -80,4 +80,16 @@ export default class extends BaseModel {
default:
}
}
static findLabelsFromText(filterText, labels) {
const selectLabel = filterText.toLocaleLowerCase();
const matchingLabels = labels.filter((label) =>
label.name ? label.name.toLocaleLowerCase().startsWith(selectLabel) : false,
);
if (matchingLabels.length === 1) {
// Appens the user to the filter
return matchingLabels[0].id;
}
return null;
}
}

View file

@ -1,6 +1,7 @@
import { attr, fk } from 'redux-orm';
import BaseModel from './BaseModel';
import User from './User';
import ActionTypes from '../constants/ActionTypes';
export default class extends BaseModel {
@ -89,6 +90,37 @@ export default class extends BaseModel {
getFilteredOrderedCardsModelArray() {
let cardModels = this.getOrderedCardsQuerySet().toModelArray();
const { filterText } = this.board;
if (filterText !== '') {
let re = null;
const posSpace = filterText.indexOf(' ');
if (filterText.startsWith('/')) {
re = new RegExp(filterText.substring(1), 'i');
}
let doRegularSearch = true;
if (re) {
cardModels = cardModels.filter((cardModel) => re.test(cardModel.name));
doRegularSearch = false;
} else if (filterText.startsWith('!') && posSpace > 0) {
const creatorUserId = User.findUsersFromText(
filterText.substring(1, posSpace),
this.board.memberships.toModelArray().map((membership) => membership.user),
);
if (creatorUserId != null) {
doRegularSearch = false;
cardModels = cardModels.filter((cardModel) => cardModel.creatorUser.id === creatorUserId);
}
}
if (doRegularSearch) {
const lowerCasedFilter = filterText.toLocaleLowerCase();
cardModels = cardModels.filter(
(cardModel) => cardModel.name.toLocaleLowerCase().indexOf(lowerCasedFilter) >= 0,
);
}
}
const filterUserIds = this.board.filterUsers.toRefArray().map((user) => user.id);
const filterLabelIds = this.board.filterLabels.toRefArray().map((label) => label.id);

View file

@ -359,4 +359,18 @@ export default class extends BaseModel {
},
);
}
static findUsersFromText(filterText, users) {
const selectUser = filterText.toLocaleLowerCase();
const matchingUsers = users.filter(
(user) =>
user.name.toLocaleLowerCase().startsWith(selectUser) ||
user.username.toLocaleLowerCase().startsWith(selectUser),
);
if (matchingUsers.length === 1) {
// Appens the user to the filter
return matchingUsers[0].id;
}
return null;
}
}