mirror of
https://github.com/wekan/wekan.git
synced 2026-01-23 09:46:09 +01:00
Fixed Add member and @mentions.
Thanks to xet7 ! Fixes #6076, fixes #6077
This commit is contained in:
parent
603a65ef17
commit
ad511bd137
14 changed files with 413 additions and 149 deletions
|
|
@ -909,10 +909,11 @@ template(name="cardMembersPopup")
|
|||
each members
|
||||
li.item(class="{{#if isCardMember}}active{{/if}}")
|
||||
a.name.js-select-member(href="#")
|
||||
+userAvatar(userId=user._id)
|
||||
+userAvatar(userId=userId)
|
||||
span.full-name
|
||||
= user.profile.fullname
|
||||
| (<span class="username">{{ user.username }}</span>)
|
||||
= userData.profile.fullname
|
||||
if userData.username
|
||||
| (#{userData.username})
|
||||
if isCardMember
|
||||
| ✅
|
||||
|
||||
|
|
@ -923,10 +924,11 @@ template(name="cardAssigneesPopup")
|
|||
each members
|
||||
li.item(class="{{#if isCardAssignee}}active{{/if}}")
|
||||
a.name.js-select-assignee(href="#")
|
||||
+userAvatar(userId=user._id)
|
||||
+userAvatar(userId=userId)
|
||||
span.full-name
|
||||
= user.profile.fullname
|
||||
| (<span class="username">{{ user.username }}</span>)
|
||||
= userData.profile.fullname
|
||||
if userData.username
|
||||
| (#{userData.username})
|
||||
if isCardAssignee
|
||||
| ✅
|
||||
if currentUser.isWorker
|
||||
|
|
@ -936,7 +938,8 @@ template(name="cardAssigneesPopup")
|
|||
+userAvatar(userId=currentUser._id)
|
||||
span.full-name
|
||||
= currentUser.profile.fullname
|
||||
| (<span class="username">{{ currentUser.username }}</span>)
|
||||
if currentUser.username
|
||||
| (#{currentUser.username})
|
||||
if currentUser.isCardAssignee
|
||||
| ✅
|
||||
|
||||
|
|
|
|||
|
|
@ -928,6 +928,12 @@ Template.cardMembersPopup.onCreated(function () {
|
|||
});
|
||||
|
||||
Template.cardMembersPopup.events({
|
||||
'click .js-select-member'(event) {
|
||||
const card = Utils.getCurrentCard();
|
||||
const memberId = this.userId;
|
||||
card.toggleMember(memberId);
|
||||
event.preventDefault();
|
||||
},
|
||||
'keyup .card-members-filter'(event) {
|
||||
const members = filterMembers(event.target.value);
|
||||
Template.instance().members.set(members);
|
||||
|
|
@ -935,8 +941,23 @@ Template.cardMembersPopup.events({
|
|||
});
|
||||
|
||||
Template.cardMembersPopup.helpers({
|
||||
isCardMember() {
|
||||
const card = Template.parentData();
|
||||
const cardMembers = card.getMembers();
|
||||
|
||||
return _.contains(cardMembers, this.userId);
|
||||
},
|
||||
|
||||
members() {
|
||||
return _.sortBy(Template.instance().members.get(),'fullname');
|
||||
const members = Template.instance().members.get();
|
||||
const uniqueMembers = _.uniq(members, 'userId');
|
||||
return _.sortBy(uniqueMembers, member => {
|
||||
const user = ReactiveCache.getUser(member.userId);
|
||||
return user ? user.profile.fullname : '';
|
||||
});
|
||||
},
|
||||
userData() {
|
||||
return ReactiveCache.getUser(this.userId);
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -1910,10 +1931,15 @@ Template.cardAssigneesPopup.helpers({
|
|||
},
|
||||
|
||||
members() {
|
||||
return _.sortBy(Template.instance().members.get(),'fullname');
|
||||
const members = Template.instance().members.get();
|
||||
const uniqueMembers = _.uniq(members, 'userId');
|
||||
return _.sortBy(uniqueMembers, member => {
|
||||
const user = ReactiveCache.getUser(member.userId);
|
||||
return user ? user.profile.fullname : '';
|
||||
});
|
||||
},
|
||||
|
||||
user() {
|
||||
userData() {
|
||||
return ReactiveCache.getUser(this.userId);
|
||||
},
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,17 @@ var converter = require('@wekanteam/html-to-markdown');
|
|||
|
||||
const specialHandles = [
|
||||
{userId: 'board_members', username: 'board_members'},
|
||||
{userId: 'card_members', username: 'card_members'}
|
||||
{userId: 'card_members', username: 'card_members'},
|
||||
{userId: 'board_assignees', username: 'board_assignees'},
|
||||
{userId: 'card_assignees', username: 'card_assignees'}
|
||||
];
|
||||
const cardSpecialHandles = [
|
||||
{userId: 'card_members', username: 'card_members'},
|
||||
{userId: 'card_assignees', username: 'card_assignees'}
|
||||
];
|
||||
const boardSpecialHandles = [
|
||||
{userId: 'board_members', username: 'board_members'},
|
||||
{userId: 'board_assignees', username: 'board_assignees'}
|
||||
];
|
||||
const specialHandleNames = specialHandles.map(m => m.username);
|
||||
|
||||
|
|
@ -46,23 +56,26 @@ BlazeComponent.extendComponent({
|
|||
search(term, callback) {
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
const searchTerm = term.toLowerCase();
|
||||
callback(
|
||||
_.union(
|
||||
currentBoard
|
||||
.activeMembers()
|
||||
.map(member => {
|
||||
const user = ReactiveCache.getUser(member.userId);
|
||||
const username = user.username.toLowerCase();
|
||||
const fullName = user.profile && user.profile !== undefined && user.profile.fullname ? user.profile.fullname.toLowerCase() : "";
|
||||
return username.includes(searchTerm) || fullName.includes(searchTerm) ? user : null;
|
||||
})
|
||||
.filter(Boolean), [...specialHandles])
|
||||
);
|
||||
const users = currentBoard
|
||||
.activeMembers()
|
||||
.map(member => {
|
||||
const user = ReactiveCache.getUser(member.userId);
|
||||
const username = user.username.toLowerCase();
|
||||
const fullName = user.profile && user.profile !== undefined && user.profile.fullname ? user.profile.fullname.toLowerCase() : "";
|
||||
return username.includes(searchTerm) || fullName.includes(searchTerm) ? user : null;
|
||||
})
|
||||
.filter(Boolean);
|
||||
// Order: 1. Users, 2. Card-specific options, 3. Board-wide options
|
||||
callback(_.union(users, cardSpecialHandles, boardSpecialHandles));
|
||||
},
|
||||
template(user) {
|
||||
if (user.profile && user.profile.fullname) {
|
||||
return (user.profile.fullname + " (" + user.username + ")");
|
||||
}
|
||||
// Translate special group mentions
|
||||
if (specialHandleNames.includes(user.username)) {
|
||||
return TAPi18n.__(user.username);
|
||||
}
|
||||
return user.username;
|
||||
},
|
||||
replace(user) {
|
||||
|
|
@ -397,6 +410,14 @@ Blaze.Template.registerHelper(
|
|||
if (knowedUser.userId === Meteor.userId()) {
|
||||
linkClass += ' me';
|
||||
}
|
||||
|
||||
// For special group mentions, display translated text
|
||||
let displayText = knowedUser.username;
|
||||
if (specialHandleNames.includes(knowedUser.username)) {
|
||||
displayText = TAPi18n.__(knowedUser.username);
|
||||
linkClass = 'atMention'; // Remove js-open-member for special handles
|
||||
}
|
||||
|
||||
// This @user mention link generation did open same Wekan
|
||||
// window in new tab, so now A is changed to U so it's
|
||||
// underlined and there is no link popup. This way also
|
||||
|
|
@ -411,7 +432,7 @@ Blaze.Template.registerHelper(
|
|||
// using a data attribute.
|
||||
'data-userId': knowedUser.userId,
|
||||
},
|
||||
linkValue,
|
||||
[' ', at, displayText],
|
||||
);
|
||||
|
||||
content = content.replace(fullMention, Blaze.toHTML(link));
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
|
||||
|
||||
Template.settingHeaderBar.helpers({
|
||||
isSettingsActive() {
|
||||
return FlowRouter.getRouteName() === 'setting' ? 'active' : '';
|
||||
|
|
|
|||
|
|
@ -754,7 +754,6 @@ template(name="removeBoardTeamPopup")
|
|||
template(name="addMemberPopup")
|
||||
.js-search-member
|
||||
input.js-search-member-input(type="text" placeholder="{{_ 'email-address'}}")
|
||||
|
||||
if loading.get
|
||||
+spinner
|
||||
else if error.get
|
||||
|
|
@ -762,21 +761,12 @@ template(name="addMemberPopup")
|
|||
else
|
||||
ul.pop-over-list
|
||||
each searchResults
|
||||
li.item.js-member-item(class="{{#if isBoardMember}}disabled{{/if}}")
|
||||
li.item.js-member-item
|
||||
a.name.js-select-member(title="{{profile.fullname}} ({{username}})")
|
||||
+userAvatar(userId=_id)
|
||||
span.full-name
|
||||
= profile.fullname
|
||||
| (<span class="username">{{username}}</span>)
|
||||
if isBoardMember
|
||||
.quiet ({{_ 'joined'}})
|
||||
|
||||
if searching.get
|
||||
+spinner
|
||||
|
||||
if noResults.get
|
||||
.manage-member-section
|
||||
p.quiet {{_ 'no-results'}}
|
||||
button.js-email-invite.primary.full {{_ 'email-invite'}}
|
||||
|
||||
|
||||
|
|
@ -831,6 +821,12 @@ template(name="changePermissionsPopup")
|
|||
if isCommentAssignedOnly
|
||||
| ✅
|
||||
span.sub-name {{_ 'comment-assigned-only-desc'}}
|
||||
li
|
||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
|
||||
| {{_ 'worker'}}
|
||||
if isWorker
|
||||
| ✅
|
||||
span.sub-name {{_ 'worker-desc'}}
|
||||
li
|
||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-read-only{{/if}}")
|
||||
| {{_ 'read-only'}}
|
||||
|
|
@ -843,12 +839,6 @@ template(name="changePermissionsPopup")
|
|||
if isReadAssignedOnly
|
||||
| ✅
|
||||
span.sub-name {{_ 'read-assigned-only-desc'}}
|
||||
li
|
||||
a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
|
||||
| {{_ 'worker'}}
|
||||
if isWorker
|
||||
| ✅
|
||||
span.sub-name {{_ 'worker-desc'}}
|
||||
if isLastAdmin
|
||||
hr
|
||||
p.quiet.bottom {{_ 'last-admin-desc'}}
|
||||
|
|
|
|||
|
|
@ -1474,8 +1474,9 @@ BlazeComponent.extendComponent({
|
|||
|
||||
isBoardMember() {
|
||||
const userId = this.currentData()._id;
|
||||
const user = ReactiveCache.getUser(userId);
|
||||
return user && user.isBoardMember();
|
||||
const boardId = Session.get('currentBoard');
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
return board && board.hasMember(userId);
|
||||
},
|
||||
|
||||
isValidEmail(email) {
|
||||
|
|
@ -1504,14 +1505,20 @@ BlazeComponent.extendComponent({
|
|||
Session.set('addMemberPopup.searching', true);
|
||||
Session.set('addMemberPopup.noResults', false);
|
||||
|
||||
// Use the fallback search
|
||||
const results = UserSearchIndex.search(query, { limit: 20 }).fetch();
|
||||
Session.set('addMemberPopup.searchResults', results);
|
||||
Session.set('addMemberPopup.searching', false);
|
||||
|
||||
if (results.length === 0) {
|
||||
Session.set('addMemberPopup.noResults', true);
|
||||
}
|
||||
const boardId = Session.get('currentBoard');
|
||||
Meteor.call('searchUsers', query, boardId, (error, results) => {
|
||||
Session.set('addMemberPopup.searching', false);
|
||||
if (error) {
|
||||
console.error('Search error:', error);
|
||||
Session.set('addMemberPopup.searchResults', []);
|
||||
Session.set('addMemberPopup.noResults', true);
|
||||
} else {
|
||||
Session.set('addMemberPopup.searchResults', results);
|
||||
if (results.length === 0) {
|
||||
Session.set('addMemberPopup.noResults', true);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
inviteUser(idNameEmail) {
|
||||
|
|
@ -1520,9 +1527,11 @@ BlazeComponent.extendComponent({
|
|||
const self = this;
|
||||
Meteor.call('inviteUserToBoard', idNameEmail, boardId, (err, ret) => {
|
||||
self.setLoading(false);
|
||||
if (err) self.setError(err.error);
|
||||
else if (ret.email) self.setError('email-sent');
|
||||
else Popup.back();
|
||||
if (err) {
|
||||
self.setError(err.error);
|
||||
} else {
|
||||
Popup.back();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -1530,9 +1539,8 @@ BlazeComponent.extendComponent({
|
|||
return [
|
||||
{
|
||||
'keyup .js-search-member-input'(event) {
|
||||
this.setError('');
|
||||
Session.set('addMemberPopup.error', '');
|
||||
const query = event.target.value.trim();
|
||||
this.searchQuery.set(query);
|
||||
|
||||
// Clear previous timeout
|
||||
if (this.searchTimeout) {
|
||||
|
|
@ -1546,16 +1554,13 @@ BlazeComponent.extendComponent({
|
|||
},
|
||||
'click .js-select-member'() {
|
||||
const userId = this.currentData()._id;
|
||||
const currentBoard = Utils.getCurrentBoard();
|
||||
if (!currentBoard.hasMember(userId)) {
|
||||
this.inviteUser(userId);
|
||||
}
|
||||
this.inviteUser(userId);
|
||||
},
|
||||
'click .js-email-invite'() {
|
||||
const idNameEmail = $('.js-search-member-input').val();
|
||||
if (idNameEmail.indexOf('@') < 0 || this.isValidEmail(idNameEmail)) {
|
||||
this.inviteUser(idNameEmail);
|
||||
} else this.setError('email-invalid');
|
||||
} else Session.set('addMemberPopup.error', 'email-invalid');
|
||||
},
|
||||
},
|
||||
];
|
||||
|
|
@ -1565,7 +1570,6 @@ BlazeComponent.extendComponent({
|
|||
Template.addMemberPopup.helpers({
|
||||
searchResults() {
|
||||
const results = Session.get('addMemberPopup.searchResults');
|
||||
console.log('searchResults helper called, returning:', results);
|
||||
return results;
|
||||
},
|
||||
searching() {
|
||||
|
|
@ -1582,14 +1586,14 @@ Template.addMemberPopup.helpers({
|
|||
},
|
||||
isBoardMember() {
|
||||
const userId = this._id;
|
||||
const user = ReactiveCache.getUser(userId);
|
||||
return user && user.isBoardMember();
|
||||
const boardId = Session.get('currentBoard');
|
||||
const board = ReactiveCache.getBoard(boardId);
|
||||
return board && board.hasMember(userId);
|
||||
}
|
||||
})
|
||||
|
||||
Template.addMemberPopupTest.helpers({
|
||||
searchResults() {
|
||||
console.log('addMemberPopupTest searchResults helper called');
|
||||
return Session.get('addMemberPopup.searchResults') || [];
|
||||
}
|
||||
})
|
||||
|
|
@ -1675,8 +1679,15 @@ BlazeComponent.extendComponent({
|
|||
|
||||
this.page = new ReactiveVar(1);
|
||||
this.autorun(() => {
|
||||
const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER;
|
||||
this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {});
|
||||
const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
|
||||
this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
|
||||
});
|
||||
|
||||
this.findUsersOptions = new ReactiveVar({});
|
||||
this.userPage = new ReactiveVar(1);
|
||||
this.autorun(() => {
|
||||
const limitUsers = this.userPage.get() * Number.MAX_SAFE_INTEGER;
|
||||
this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
template(name="userAvatar")
|
||||
a.member(class="js-{{#if assignee}}assignee{{else}}member{{/if}}" title="{{userData.profile.fullname}} ({{userData.username}}) {{_ memberType}}")
|
||||
if userData.profile.avatarUrl
|
||||
img.avatar.avatar-image(src="{{avatarUrl}}")
|
||||
a.member(class="js-{{#if assignee}}assignee{{else}}member{{/if}}" title="{{#if userData}}{{userData.profile.fullname}} ({{userData.username}}) {{_ memberType}}{{/if}}")
|
||||
if userData
|
||||
if userData.profile.avatarUrl
|
||||
img.avatar.avatar-image(src="{{avatarUrl}}")
|
||||
else
|
||||
+userAvatarInitials(userId=userData._id)
|
||||
else
|
||||
+userAvatarInitials(userId=userData._id)
|
||||
+userAvatarInitials(userId=this.userId)
|
||||
|
||||
if showStatus
|
||||
span.member-presence-status(class=presenceStatusClassName)
|
||||
|
|
|
|||
|
|
@ -7,12 +7,13 @@ import Team from '/models/team';
|
|||
|
||||
Template.userAvatar.helpers({
|
||||
userData() {
|
||||
return ReactiveCache.getUser(this.userId, {
|
||||
const user = ReactiveCache.getUser(this.userId, {
|
||||
fields: {
|
||||
profile: 1,
|
||||
username: 1,
|
||||
},
|
||||
});
|
||||
return user;
|
||||
},
|
||||
|
||||
avatarUrl() {
|
||||
|
|
@ -32,7 +33,21 @@ Template.userAvatar.helpers({
|
|||
|
||||
memberType() {
|
||||
const user = ReactiveCache.getUser(this.userId);
|
||||
return user && user.isBoardAdmin() ? 'admin' : 'normal';
|
||||
if (!user) return '';
|
||||
|
||||
const board = Utils.getCurrentBoard();
|
||||
if (!board) return '';
|
||||
|
||||
// Return role in priority order: Admin, Normal, NormalAssignedOnly, NoComments, CommentOnly, CommentAssignedOnly, Worker, ReadOnly, ReadAssignedOnly
|
||||
if (user.isBoardAdmin()) return 'admin';
|
||||
if (board.hasReadAssignedOnly(user._id)) return 'read-assigned-only';
|
||||
if (board.hasReadOnly(user._id)) return 'read-only';
|
||||
if (board.hasWorker(user._id)) return 'worker';
|
||||
if (board.hasCommentAssignedOnly(user._id)) return 'comment-assigned-only';
|
||||
if (board.hasCommentOnly(user._id)) return 'comment-only';
|
||||
if (board.hasNoComments(user._id)) return 'no-comments';
|
||||
if (board.hasNormalAssignedOnly(user._id)) return 'normal-assigned-only';
|
||||
return 'normal';
|
||||
},
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue