@sendbird/uikit-react-native
Version:
Sendbird UIKit for React Native: A feature-rich and customizable chat UI kit with messaging, channel management, and user authentication.
228 lines • 9.68 kB
JavaScript
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
import React from 'react';
import { Text, createStyleSheet } from '@sendbird/uikit-react-native-foundation';
import { createMentionTemplateRegex, isEndsWithRTL, replaceWithRegex } from '@sendbird/uikit-utils';
const SPAN_DIRECTION = {
LRM: '\u200E',
RLM: '\u200F'
};
class MentionManager {
constructor(config) {
this.config = config;
_defineProperty(this, "_invalidStartsKeywords", void 0);
_defineProperty(this, "_templateRegex", void 0);
// Note: When the input starts in LTR and the mentioned user's name is in RTL, it appears as "Hello @{cibarA}."
// If typing continues in RTL, the mention is rendered as: "Hello @{txeTlanoitiddA}{cibarA}."
//
// Conversely, if the input starts in RTL and the mentioned user's name is in LTR, it appears as "{Eng}@ cibarA."
// If typing continues, it is rendered as: "{Eng}{AdditionalText}@ cibarA."
//
// While this follows the natural text direction, it can make mentions harder to distinguish.
// To address this, we use the RLM or LRM Unicode characters to reset subsequent spans based on the last text string of the user's name.
// By applying this trick, the result will be displayed as "Hello @{cibarA} {txeTlanoitiddA}" or "{AdditionalText} {Eng}@ cibarA," ensuring the mention block remains clearly distinguishable.
_defineProperty(this, "getDirectionOfNextSpan", name => {
return isEndsWithRTL(name) ? SPAN_DIRECTION.LRM : SPAN_DIRECTION.RLM;
});
_defineProperty(this, "rangeHelpers", {
inRangeUnderOver(start, num, end) {
return start < num && num < end;
},
inRangeUnderMore(start, num, end) {
return start < num && num <= end;
},
inRangeLessOver(start, num, end) {
return start <= num && num < end;
},
inRangeLessMore(start, num, end) {
return start <= num && num <= end;
},
overlaps(a, b, compare = 'underOver') {
const inRange = {
underOver: this.inRangeUnderOver,
underMore: this.inRangeUnderMore,
lessOver: this.inRangeLessOver,
lessMore: this.inRangeLessMore
}[compare];
return inRange(a.start, b.start, a.end) || inRange(a.start, b.end, a.end);
}
});
_defineProperty(this, "getSearchString", (text, selectionIndex) => {
const lastSpan = text.slice(0, selectionIndex).split(this.config.delimiter).pop() ?? '';
const triggerIdx = lastSpan.indexOf(this.config.trigger);
const mentionSpan = triggerIdx === -1 ? lastSpan : lastSpan.slice(triggerIdx);
const searchString = mentionSpan.slice(this.config.trigger.length);
return {
searchString,
isTriggered: () => mentionSpan.startsWith(this.config.trigger),
isValidSearchString: () => this._invalidStartsKeywords.every(it => !searchString.startsWith(it))
};
});
/**
* @description Reconcile the range by offset in the mentioned users
* */
_defineProperty(this, "reconcileRangeOfMentionedUsers", (offset, selectionIndex, mentionedUsers) => {
return mentionedUsers.map(it => {
// Changes only on the right text of selection.
if (selectionIndex <= it.range.start) {
return {
...it,
range: {
start: it.range.start + offset,
end: it.range.end + offset
}
};
}
return it;
});
});
/**
* @description Remove users who in a range
* */
_defineProperty(this, "removeMentionedUsersInSelection", (selection, mentionedUsers) => {
let lastSelection = 0;
let removedOffset = 0;
const filtered = mentionedUsers.filter(it => {
const shouldRemove = this.rangeHelpers.overlaps(selection, it.range, 'lessMore');
if (shouldRemove) {
lastSelection = Math.max(lastSelection, it.range.end);
removedOffset -= it.range.end - it.range.start;
}
return !shouldRemove;
});
return {
filtered,
lastSelection,
removedOffset
};
});
_defineProperty(this, "getSearchStringRangeInText", (selectionIndex, searchString) => {
return {
start: selectionIndex - searchString.length - this.config.trigger.length,
end: selectionIndex
};
});
/**
* @description User to @{user.id} template format
* */
_defineProperty(this, "asMentionedMessageTemplate", (user, delimiter = false) => {
const prefix = ''; // Do not append anything to here in order to maintain backward compatibility.
const content = `${this.config.trigger}{${user.userId}}`;
const postfix = delimiter ? this.config.delimiter : '';
return prefix + content + postfix;
});
/**
* @description User to @user.nickname text format
* */
_defineProperty(this, "asMentionedMessageText", (user, delimiter = false) => {
const prefix = '';
const content = `${this.config.trigger}${user.nickname}`;
const postfix = this.getDirectionOfNextSpan(user.nickname) + (delimiter ? this.config.delimiter : '');
return prefix + content + postfix;
});
/**
* @description Bold @user.nickname
* */
_defineProperty(this, "textToMentionedComponents", (text, mentionedUsers, mentionEnabled) => {
if (!mentionEnabled || mentionedUsers.length === 0) return text;
const {
leftText,
components
} = mentionedUsers.sort((a, b) => b.range.start - a.range.start).reduce(({
leftText,
components
}, curr, currentIndex) => {
const leftSpan = leftText.slice(0, curr.range.start);
const mentionSpan = leftText.slice(curr.range.start, curr.range.end);
const rightSpan = leftText.slice(curr.range.end);
return {
leftText: leftSpan,
components: [/*#__PURE__*/React.createElement(Text, {
key: mentionSpan + currentIndex,
style: styles.mentionedText
}, mentionSpan), rightSpan, ...components]
};
}, {
leftText: text,
components: []
});
return [leftText, ...components];
});
_defineProperty(this, "textToMentionedMessageTemplate", (text, mentionedUsers, mentionEnabled) => {
if (!mentionEnabled) return text;
const {
leftText,
strings
} = mentionedUsers.sort((a, b) => b.range.start - a.range.start).reduce(({
leftText,
strings
}, curr) => {
const leftSpan = leftText.slice(0, curr.range.start);
const templateSpan = this.asMentionedMessageTemplate(curr.user);
const rightSpan = leftText.slice(curr.range.end);
return {
leftText: leftSpan,
strings: [templateSpan, rightSpan, ...strings]
};
}, {
leftText: text,
strings: []
});
return [leftText, ...strings].join('');
});
/**
* @description Convert @{user.id} template to @user.nickname text and MentionedUser[] array.
* */
_defineProperty(this, "templateToTextAndMentionedUsers", (template, mentionedUsers) => {
const actualMentionedUsers = [];
let offsetToMove = 0;
const mentionedText = replaceWithRegex(template, this.templateRegex, ({
match,
matchIndex,
groups
}) => {
const user = mentionedUsers.find(it => it.userId === groups[2]);
if (user && typeof matchIndex === 'number') {
const userIdSpan = match;
const userNicknameSpan = this.asMentionedMessageText(user);
const offsetAfterConverted = userNicknameSpan.length - userIdSpan.length;
const originalRange = {
start: matchIndex,
end: matchIndex + userIdSpan.length
};
const convertedRange = {
start: Math.max(0, originalRange.start + offsetToMove),
end: originalRange.end + offsetToMove + offsetAfterConverted
};
offsetToMove += offsetAfterConverted;
actualMentionedUsers.push({
range: convertedRange,
user
});
return userNicknameSpan;
}
return match;
}, '').join('');
return {
mentionedText,
mentionedUsers: actualMentionedUsers
};
});
_defineProperty(this, "shouldUseMentionedMessageTemplate", (message, mentionEnabled) => {
return Boolean(mentionEnabled && (message === null || message === void 0 ? void 0 : message.mentionedMessageTemplate) && (message === null || message === void 0 ? void 0 : message.mentionedUsers) && (message === null || message === void 0 ? void 0 : message.mentionedUsers.length) > 0);
});
this._invalidStartsKeywords = [this.config.trigger, this.config.delimiter];
this._templateRegex = createMentionTemplateRegex(this.config.trigger);
}
get templateRegex() {
return this._templateRegex;
}
}
const styles = createStyleSheet({
mentionedText: {
fontWeight: '700'
}
});
export default MentionManager;
//# sourceMappingURL=MentionManager.js.map