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:
parent
8747aa59de
commit
eb56b2147b
19 changed files with 267 additions and 1 deletions
|
@ -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:
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue