PLT-1759 - Auto-complete for !channels when posting messages. (#3890)

* Auto-complete for !channels when posting messages.

This is part 1 of the fix for PLT-1759 to make channels linkable.

Still to do:
- Make the !channels clickable when they appear in messages. This is
  blocked until PR #3865 is resolved as it looks like that refactors
  some of the code that would be touched by making this change.

- Unit tests. Again, I think the above referenced PR should be merged
  before tackling this.

* Fix style problems.

* Highlighting of !channel-names in messages.

This only identifies the !channel-name (not the display name). The
implementation of the auto-complete on channel names now needs to be
modified to convert to the channel handle before sending the message.

* Display !channel-name as !Display Name.

When we encounter !channel-name in a message, display it as a link using
the channel's actual name rather than it's handle (name).

* Match on names and display name, and use name.

* Autocomplete channels matching on both the name and the the display
  name.
* Use the name as the text we fill in instead of the display name. It's
  potentially a bit ugly, but it minimises complexity for now as
  otherwise we'd have to do complicated things to the message box.

* Fix style issues.

* Load more channels everywhere.

Whenever we load the list of channels, we should also load the list of
more channels. This is to enable auto-completing and auto-linking of all
channels whether or not the user is in them currently.

* Include more channels in the map for linking.

* Listen for channel list updates for autolinking.

* Remove accidental console.log.

* Autocomplete on more channels too.

* i18n for channel autocomplete.

* Link directly to channels in !channel mentions.

This currently does not work if you aren't a member of that channel.
Need to decide what the correct behaviour is in that case.

* Fix style issues.

* Show channel name and handle in suggestion.

* Match channels only at start or after space.

* Better matching in text-formatting.

Only match channels after a space-type character or at the start in the
posts list too.

* Move the route construction to make tests work.

Moves route-construction out of text_formatting.jsx and into utils.jsx
so that the unit tests work once again.
Этот коммит содержится в:
George Goldberg
2016-09-19 13:21:22 +01:00
коммит произвёл Joram Wilander
родитель 781ff323db
Коммит 8443ca5828
11 изменённых файлов: 257 добавлений и 9 удалений

Просмотреть файл

@@ -835,6 +835,8 @@ export const Constants = {
PERMISSIONS_ALL: 'all',
PERMISSIONS_TEAM_ADMIN: 'team_admin',
PERMISSIONS_SYSTEM_ADMIN: 'system_admin',
MENTION_CHANNELS: 'mention.channels',
MENTION_MORE_CHANNELS: 'mention.morechannels',
MENTION_MEMBERS: 'mention.members',
MENTION_NONMEMBERS: 'mention.nonmembers',
MENTION_SPECIAL: 'mention.special',

Просмотреть файл

@@ -13,9 +13,9 @@ import XRegExp from 'xregexp';
// http://stackoverflow.com/questions/15033196/using-javascript-to-check-whether-a-string-contains-japanese-characters-includi
const cjkPattern = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-\u9faf\u3400-\u4dbf]/;
// Performs formatting of user posts including highlighting mentions and search terms and converting urls, hashtags, and
// @mentions to links by taking a user's message and returning a string of formatted html. Also takes a number of options
// as part of the second parameter:
// Performs formatting of user posts including highlighting mentions and search terms and converting urls, hashtags,
// @mentions and !channels to links by taking a user's message and returning a string of formatted html. Also takes
// a number of options as part of the second parameter:
// - searchTerm - If specified, this word is highlighted in the resulting html. Defaults to nothing.
// - mentionHighlight - Specifies whether or not to highlight mentions of the current user. Defaults to true.
// - mentionKeys - A list of mention keys for the current user to highlight.
@@ -26,6 +26,8 @@ const cjkPattern = /[\u3000-\u303f\u3040-\u309f\u30a0-\u30ff\uff00-\uff9f\u4e00-
// links that can be handled by a special click handler.
// - usernameMap - An object mapping usernames to users. If provided, at mentions will be replaced with internal links that can
// be handled by a special click handler (Utils.handleFormattedTextClick)
// - channelNamesMap - An object mapping channel display names to channels. If provided, !channel mentions will be replaced with
// links to the relevant channel.
export function formatText(text, inputOptions) {
let output = text;
@@ -61,6 +63,10 @@ export function doFormatText(text, options) {
output = autolinkAtMentions(output, tokens, options.usernameMap);
}
if (options.channelNamesMap) {
output = autolinkChannelMentions(output, tokens, options.channelNamesMap);
}
output = autolinkEmails(output, tokens);
output = autolinkHashtags(output, tokens);
@@ -198,6 +204,57 @@ function autolinkAtMentions(text, tokens, usernameMap) {
return output;
}
function autolinkChannelMentions(text, tokens, channelNamesMap) {
function channelMentionExists(c) {
return !!channelNamesMap[c];
}
function addToken(channelName, mention, displayName) {
const index = tokens.size;
const alias = `MM_CHANNELMENTION${index}`;
tokens.set(alias, {
value: `<a class='mention-link' href='#' data-channel-mention="${channelName}">${displayName}</a>`,
originalText: mention
});
return alias;
}
function replaceChannelMentionWithToken(fullMatch, spacer, mention, channelName) {
let channelNameLower = channelName.toLowerCase();
if (channelMentionExists(channelNameLower)) {
// Exact match
const alias = addToken(channelNameLower, mention, '!' + channelNamesMap[channelNameLower].display_name);
return spacer + alias;
}
// Not an exact match, attempt to truncate any punctuation to see if we can find a channel
const originalChannelName = channelNameLower;
for (let c = channelNameLower.length; c > 0; c--) {
if (punctuation.test(channelNameLower[c - 1])) {
channelNameLower = channelNameLower.substring(0, c - 1);
if (channelMentionExists(channelNameLower)) {
const suffix = originalChannelName.substr(c - 1);
const alias = addToken(channelNameLower, '!' + channelNameLower, '!' + channelNamesMap[channelNameLower].display_name);
return spacer + alias + suffix;
}
} else {
// If the last character is not punctuation, no point in going any further
break;
}
}
return fullMatch;
}
let output = text;
output = output.replace(/(^|\s)(!([a-z0-9.\-_]*))/gi, replaceChannelMentionWithToken);
return output;
}
export function escapeRegex(text) {
return text.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
}

Просмотреть файл

@@ -1355,6 +1355,7 @@ export function handleFormattedTextClick(e) {
const mentionAttribute = e.target.getAttributeNode('data-mention');
const hashtagAttribute = e.target.getAttributeNode('data-hashtag');
const linkAttribute = e.target.getAttributeNode('data-link');
const channelMentionAttribute = e.target.getAttributeNode('data-channel-mention');
if (mentionAttribute) {
e.preventDefault();
@@ -1372,5 +1373,7 @@ export function handleFormattedTextClick(e) {
browserHistory.push(linkAttribute.value);
}
} else if (channelMentionAttribute) {
browserHistory.push('/' + TeamStore.getCurrent().name + '/channels/' + channelMentionAttribute.value);
}
}