mirror of
https://github.com/flarum/core.git
synced 2025-07-30 21:20:24 +02:00
[mentions] feat: group mentions (#3658)
* wip: group mentions * Apply fixes from StyleCI * chore: format * group mention autocomplete * chore: format * remove console.log * implement notifications * prevent guest and member groups from being mentioned * Update extensions/mentions/less/forum.less Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * rename displayname to groupname * Update extensions/mentions/src/Formatter/FormatGroupMentions.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * remove redundant unparse * simplify migrations * add group deleted translation * Apply fixes from StyleCI * handle everything falsy * Include icon in group mention preview * remove box-shadow from autocomplete group results * Add color to preview * chore: format * Remove box shadow from group autocomplete results * Update extensions/mentions/migrations/2022_10_21_000000_create_post_mentions_groups_table.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * remove unneeded migration * prevent former group icon from beingdisplayed * add group searcher with permissions * Apply fixes from StyleCI * Search groups based on canSearchGroups permission * Don't include virtual groups in results * Add search groups translation * Revert "remove unneeded migration" This reverts commit9347665baa
. * Revert "Update extensions/mentions/migrations/2022_10_21_000000_create_post_mentions_groups_table.php" This reverts commit8406d51df2
. * add searchGroups permission to tests * Apply fixes from StyleCI * Add default searchGroups permission * Apply fixes from StyleCI * Update extensions/mentions/js/src/forum/addComposerAutocomplete.js Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update extensions/mentions/migrations/2022_10_21_000000_create_post_mentions_groups_table.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * remove unneeded migration, correct table table * correct table name in down migration * Remove group searcher * Apply fixes from StyleCI * Remove group searching from composer autocomplete * Add mentionGroups permission * Apply fixes from StyleCI * prevent post preview from rendering a group mention when user does not have permission * remove test changes * wip: expose ServerRequestInterface to textformatter parse() * Apply fixes from StyleCI * Set post content properly * php 7.x compatibility * begin adding groupmention tests * Apply fixes from StyleCI * test virtual groups don't mention * Apply fixes from StyleCI * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update extensions/mentions/extend.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update extensions/mentions/extend.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * requested changes * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/tests/integration/api/groups/ListTest.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/src/Search/SearchServiceProvider.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * Update framework/core/src/Extend/Formatter.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * remove default permission migration * try using datetime column instead of timestamp * Apply fixes from StyleCI * chore: remove commented code * add tests * Apply fixes from StyleCI * Pass actor to parser instead of ServerRequest * Allow for to be null * Update framework/core/src/Extend/Formatter.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * pass actor instead of request * Apply fixes from StyleCI * actor instead of request * remove serverrequest * Apply fixes from StyleCI * remove dupe actor * Update extensions/mentions/src/Formatter/CheckPermissions.php Co-authored-by: Sami Mazouz <sychocouldy@gmail.com> * fix type in comment * group does not have the relation, post does * test: invalid, deleted, fresh data mentions Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * Apply fixes from StyleCI * fix: group mentions don't work when editing posts Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> Co-authored-by: StyleCI Bot <bot@styleci.io> Co-authored-by: Sami Mazouz <sychocouldy@gmail.com>
This commit is contained in:
@@ -1,10 +1,20 @@
|
||||
import app from 'flarum/admin/app';
|
||||
|
||||
app.initializers.add('flarum-mentions', function () {
|
||||
app.extensionData.for('flarum-mentions').registerSetting({
|
||||
setting: 'flarum-mentions.allow_username_format',
|
||||
type: 'boolean',
|
||||
label: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_label'),
|
||||
help: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_text'),
|
||||
});
|
||||
app.extensionData
|
||||
.for('flarum-mentions')
|
||||
.registerSetting({
|
||||
setting: 'flarum-mentions.allow_username_format',
|
||||
type: 'boolean',
|
||||
label: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_label'),
|
||||
help: app.translator.trans('flarum-mentions.admin.settings.allow_username_format_text'),
|
||||
})
|
||||
.registerPermission(
|
||||
{
|
||||
permission: 'mentionGroups',
|
||||
label: app.translator.trans('flarum-mentions.admin.permissions.mention_groups_label'),
|
||||
icon: 'fas fa-at',
|
||||
},
|
||||
'start'
|
||||
);
|
||||
});
|
||||
|
@@ -10,6 +10,8 @@ import highlight from 'flarum/common/helpers/highlight';
|
||||
import KeyboardNavigatable from 'flarum/forum/utils/KeyboardNavigatable';
|
||||
import { truncate } from 'flarum/common/utils/string';
|
||||
import { throttle } from 'flarum/common/utils/throttleDebounce';
|
||||
import Badge from 'flarum/common/components/Badge';
|
||||
import Group from 'flarum/common/models/Group';
|
||||
|
||||
import AutocompleteDropdown from './fragments/AutocompleteDropdown';
|
||||
import getMentionText from './utils/getMentionText';
|
||||
@@ -29,6 +31,7 @@ const throttledSearch = throttle(
|
||||
|
||||
buildSuggestions();
|
||||
});
|
||||
|
||||
searched.push(typedLower);
|
||||
}
|
||||
}
|
||||
@@ -66,6 +69,13 @@ export default function addComposerAutocomplete() {
|
||||
const returnedUsers = Array.from(app.store.all('users'));
|
||||
const returnedUserIds = new Set(returnedUsers.map((u) => u.id()));
|
||||
|
||||
// Store groups, but exclude the two virtual groups - 'Guest' and 'Member'.
|
||||
const returnedGroups = Array.from(
|
||||
app.store.all('groups').filter((group) => {
|
||||
return group.id() != Group.GUEST_ID && group.id() != Group.MEMBER_ID;
|
||||
})
|
||||
);
|
||||
|
||||
const applySuggestion = (replacement) => {
|
||||
this.attrs.composer.editor.replaceBeforeCursor(absMentionStart - 1, replacement + ' ');
|
||||
|
||||
@@ -124,12 +134,41 @@ export default function addComposerAutocomplete() {
|
||||
);
|
||||
};
|
||||
|
||||
const makeGroupSuggestion = function (group, replacement, content, className = '') {
|
||||
let groupName = group.namePlural().toLowerCase();
|
||||
|
||||
if (typed) {
|
||||
groupName = highlight(groupName, typed);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
className={'PostPreview ' + className}
|
||||
onclick={() => applySuggestion(replacement)}
|
||||
onmouseenter={function () {
|
||||
dropdown.setIndex($(this).parent().index());
|
||||
}}
|
||||
>
|
||||
<span className="PostPreview-content">
|
||||
<Badge class={`Avatar Badge Badge--group--${group.id()} Badge-icon `} color={group.color()} type="group" icon={group.icon()} />
|
||||
<span className="username">{groupName}</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const userMatches = function (user) {
|
||||
const names = [user.username(), user.displayName()];
|
||||
|
||||
return names.some((name) => name.toLowerCase().substr(0, typed.length) === typed);
|
||||
};
|
||||
|
||||
const groupMatches = function (group) {
|
||||
const names = [group.nameSingular(), group.namePlural()];
|
||||
|
||||
return names.some((name) => name.toLowerCase().substr(0, typed.length) === typed);
|
||||
};
|
||||
|
||||
const buildSuggestions = () => {
|
||||
const suggestions = [];
|
||||
|
||||
@@ -141,6 +180,15 @@ export default function addComposerAutocomplete() {
|
||||
|
||||
suggestions.push(makeSuggestion(user, getMentionText(user), '', 'MentionsDropdown-user'));
|
||||
});
|
||||
|
||||
// ... or groups.
|
||||
if (app.session?.user?.canMentionGroups()) {
|
||||
returnedGroups.forEach((group) => {
|
||||
if (!groupMatches(group)) return;
|
||||
|
||||
suggestions.push(makeGroupSuggestion(group, getMentionText(undefined, undefined, group), '', 'MentionsDropdown-group'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// If the user is replying to a discussion, or if they are editing a
|
||||
|
@@ -1,3 +1,4 @@
|
||||
import GroupMentionedNotification from './components/GroupMentionedNotification';
|
||||
import MentionsUserPage from './components/MentionsUserPage';
|
||||
import PostMentionedNotification from './components/PostMentionedNotification';
|
||||
import UserMentionedNotification from './components/UserMentionedNotification';
|
||||
@@ -13,6 +14,7 @@ export default {
|
||||
'mentions/components/MentionsUserPage': MentionsUserPage,
|
||||
'mentions/components/PostMentionedNotification': PostMentionedNotification,
|
||||
'mentions/components/UserMentionedNotification': UserMentionedNotification,
|
||||
'mentions/components/GroupMentionedNotification': GroupMentionedNotification,
|
||||
'mentions/fragments/AutocompleteDropdown': AutocompleteDropdown,
|
||||
'mentions/fragments/PostQuoteButton': PostQuoteButton,
|
||||
'mentions/utils/getCleanDisplayName': getCleanDisplayName,
|
||||
|
@@ -0,0 +1,25 @@
|
||||
import app from 'flarum/forum/app';
|
||||
import Notification from 'flarum/forum/components/Notification';
|
||||
import { truncate } from 'flarum/common/utils/string';
|
||||
|
||||
export default class GroupMentionedNotification extends Notification {
|
||||
icon() {
|
||||
return 'fas fa-at';
|
||||
}
|
||||
|
||||
href() {
|
||||
const post = this.attrs.notification.subject();
|
||||
|
||||
return app.route.discussion(post.discussion(), post.number());
|
||||
}
|
||||
|
||||
content() {
|
||||
const user = this.attrs.notification.fromUser();
|
||||
|
||||
return app.translator.trans('flarum-mentions.forum.notifications.group_mentioned_text', { user });
|
||||
}
|
||||
|
||||
excerpt() {
|
||||
return truncate(this.attrs.notification.subject().contentPlain(), 200);
|
||||
}
|
||||
}
|
@@ -10,11 +10,16 @@ import addPostQuoteButton from './addPostQuoteButton';
|
||||
import addComposerAutocomplete from './addComposerAutocomplete';
|
||||
import PostMentionedNotification from './components/PostMentionedNotification';
|
||||
import UserMentionedNotification from './components/UserMentionedNotification';
|
||||
import GroupMentionedNotification from './components/GroupMentionedNotification';
|
||||
import UserPage from 'flarum/forum/components/UserPage';
|
||||
import LinkButton from 'flarum/common/components/LinkButton';
|
||||
import MentionsUserPage from './components/MentionsUserPage';
|
||||
import User from 'flarum/common/models/User';
|
||||
import Model from 'flarum/common/Model';
|
||||
|
||||
app.initializers.add('flarum-mentions', function () {
|
||||
User.prototype.canMentionGroups = Model.attribute('canMentionGroups');
|
||||
|
||||
// For every mention of a post inside a post's content, set up a hover handler
|
||||
// that shows a preview of the mentioned post.
|
||||
addPostMentionPreviews();
|
||||
@@ -36,6 +41,7 @@ app.initializers.add('flarum-mentions', function () {
|
||||
|
||||
app.notificationComponents.postMentioned = PostMentionedNotification;
|
||||
app.notificationComponents.userMentioned = UserMentionedNotification;
|
||||
app.notificationComponents.groupMentioned = GroupMentionedNotification;
|
||||
|
||||
// Add notification preferences.
|
||||
extend(NotificationGrid.prototype, 'notificationTypes', function (items) {
|
||||
@@ -50,6 +56,12 @@ app.initializers.add('flarum-mentions', function () {
|
||||
icon: 'fas fa-at',
|
||||
label: app.translator.trans('flarum-mentions.forum.settings.notify_user_mentioned_label'),
|
||||
});
|
||||
|
||||
items.add('groupMentioned', {
|
||||
name: 'groupMentioned',
|
||||
icon: 'fas fa-at',
|
||||
label: app.translator.trans('flarum-mentions.forum.settings.notify_group_mentioned_label'),
|
||||
});
|
||||
});
|
||||
|
||||
// Add mentions tab in user profile
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import getCleanDisplayName, { shouldUseOldFormat } from './getCleanDisplayName';
|
||||
|
||||
/**
|
||||
* Fetches the mention text for a specified user (and optionally a post ID for replies).
|
||||
* Fetches the mention text for a specified user (and optionally a post ID for replies, or group).
|
||||
*
|
||||
* Automatically determines which mention syntax to be used based on the option in the
|
||||
* admin dashboard. Also performs display name clean-up automatically.
|
||||
@@ -17,9 +17,13 @@ import getCleanDisplayName, { shouldUseOldFormat } from './getCleanDisplayName';
|
||||
* @example <caption>Using old syntax</caption>
|
||||
* // '@username'
|
||||
* getMentionText(User) // User's username is 'username'
|
||||
*
|
||||
* @example <caption>Group mention</caption>
|
||||
* // '@"Mods"#g4'
|
||||
* getMentionText(undefined, undefined, group) // Group display name is 'Mods', group ID is 4
|
||||
*/
|
||||
export default function getMentionText(user, postId) {
|
||||
if (postId === undefined) {
|
||||
export default function getMentionText(user, postId, group) {
|
||||
if (user !== undefined && postId === undefined) {
|
||||
if (shouldUseOldFormat()) {
|
||||
// Plain @username
|
||||
const cleanText = getCleanDisplayName(user, false);
|
||||
@@ -28,9 +32,14 @@ export default function getMentionText(user, postId) {
|
||||
// @"Display name"#UserID
|
||||
const cleanText = getCleanDisplayName(user);
|
||||
return `@"${cleanText}"#${user.id()}`;
|
||||
} else {
|
||||
} else if (user !== undefined && postId !== undefined) {
|
||||
// @"Display name"#pPostID
|
||||
const cleanText = getCleanDisplayName(user);
|
||||
return `@"${cleanText}"#p${postId}`;
|
||||
} else if (group !== undefined) {
|
||||
// @"Name Plural"#gGroupID
|
||||
return `@"${group.namePlural()}"#g${group.id()}`;
|
||||
} else {
|
||||
throw 'No parameters were passed';
|
||||
}
|
||||
}
|
||||
|
@@ -31,3 +31,19 @@ export function filterPostMentions(tag) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export function filterGroupMentions(tag) {
|
||||
if (app.session?.user?.canMentionGroups()) {
|
||||
const group = app.store.getById('groups', tag.getAttribute('id'));
|
||||
|
||||
if (group) {
|
||||
tag.setAttribute('groupname', extractText(group.namePlural()));
|
||||
tag.setAttribute('icon', group.icon());
|
||||
tag.setAttribute('color', group.color());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
tag.invalidate();
|
||||
}
|
||||
|
Reference in New Issue
Block a user