diff --git a/src/components/views/elements/Pill.js b/src/components/views/elements/Pill.js
index 51ae85ba5a..a85f83d78c 100644
--- a/src/components/views/elements/Pill.js
+++ b/src/components/views/elements/Pill.js
@@ -37,11 +37,20 @@ const Pill = React.createClass({
isMessagePillUrl: (url) => {
return !!REGEX_LOCAL_MATRIXTO.exec(url);
},
+ roomNotifPos: (text) => {
+ return text.indexOf("@room");
+ },
+ roomNotifLen: () => {
+ return "@room".length;
+ },
TYPE_USER_MENTION: 'TYPE_USER_MENTION',
TYPE_ROOM_MENTION: 'TYPE_ROOM_MENTION',
+ TYPE_AT_ROOM_MENTION: 'TYPE_AT_ROOM_MENTION', // '@room' mention
},
props: {
+ // The Type of this Pill. If url is given, this is auto-detected.
+ type: PropTypes.string,
// The URL to pillify (no validation is done, see isPillUrl and isMessagePillUrl)
url: PropTypes.string,
// Whether the pill is in a message
@@ -72,14 +81,20 @@ const Pill = React.createClass({
regex = REGEX_LOCAL_MATRIXTO;
}
- // Default to the empty array if no match for simplicity
- // resource and prefix will be undefined instead of throwing
- const matrixToMatch = regex.exec(nextProps.url) || [];
+ let matrixToMatch;
+ let resourceId;
+ let prefix;
- const resourceId = matrixToMatch[1]; // The room/user ID
- const prefix = matrixToMatch[2]; // The first character of prefix
+ if (nextProps.url) {
+ // Default to the empty array if no match for simplicity
+ // resource and prefix will be undefined instead of throwing
+ matrixToMatch = regex.exec(nextProps.url) || [];
- const pillType = {
+ resourceId = matrixToMatch[1]; // The room/user ID
+ prefix = matrixToMatch[2]; // The first character of prefix
+ }
+
+ const pillType = this.props.type || {
'@': Pill.TYPE_USER_MENTION,
'#': Pill.TYPE_ROOM_MENTION,
'!': Pill.TYPE_ROOM_MENTION,
@@ -88,6 +103,10 @@ const Pill = React.createClass({
let member;
let room;
switch (pillType) {
+ case Pill.TYPE_AT_ROOM_MENTION: {
+ room = nextProps.room;
+ }
+ break;
case Pill.TYPE_USER_MENTION: {
const localMember = nextProps.room.getMember(resourceId);
member = localMember;
@@ -160,6 +179,17 @@ const Pill = React.createClass({
let href = this.props.url;
let onClick;
switch (this.state.pillType) {
+ case Pill.TYPE_AT_ROOM_MENTION: {
+ const room = this.props.room;
+ if (room) {
+ linkText = "@room";
+ if (this.props.shouldShowPillAvatar) {
+ avatar = ;
+ }
+ pillClass = 'mx_AtRoomPill';
+ }
+ }
+ break;
case Pill.TYPE_USER_MENTION: {
// If this user is not a member of this room, default to the empty member
const member = this.state.member;
diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js
index 64b23238e5..faa4d6cf77 100644
--- a/src/components/views/messages/TextualBody.js
+++ b/src/components/views/messages/TextualBody.js
@@ -34,6 +34,7 @@ import MatrixClientPeg from '../../../MatrixClientPeg';
import ContextualMenu from '../../structures/ContextualMenu';
import {RoomMember} from 'matrix-js-sdk';
import classNames from 'classnames';
+import PushProcessor from 'matrix-js-sdk/lib/pushprocessor';
linkifyMatrix(linkify);
@@ -169,8 +170,10 @@ module.exports = React.createClass({
pillifyLinks: function(nodes) {
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
- for (let i = 0; i < nodes.length; i++) {
- const node = nodes[i];
+ let node = nodes[0];
+ while (node) {
+ let pillified = false;
+
if (node.tagName === "A" && node.getAttribute("href")) {
const href = node.getAttribute("href");
@@ -189,10 +192,68 @@ module.exports = React.createClass({
ReactDOM.render(pill, pillContainer);
node.parentNode.replaceChild(pillContainer, node);
+ // Pills within pills aren't going to go well, so move on
+ pillified = true;
+ }
+ } else if (node.nodeType == Node.TEXT_NODE) {
+ const Pill = sdk.getComponent('elements.Pill');
+
+ let currentTextNode = node;
+ const roomNotifTextNodes = [];
+
+ // Take a textNode and break it up to make all the instances of @room their
+ // own textNode, adding those nodes to roomNotifTextNodes
+ while (currentTextNode !== null) {
+ const roomNotifPos = Pill.roomNotifPos(currentTextNode.textContent);
+ let nextTextNode = null;
+ if (roomNotifPos > -1) {
+ let roomTextNode = currentTextNode;
+
+ if (roomNotifPos > 0) roomTextNode = roomTextNode.splitText(roomNotifPos);
+ if (roomTextNode.textContent.length > Pill.roomNotifLen()) {
+ nextTextNode = roomTextNode.splitText(Pill.roomNotifLen());
+ }
+ roomNotifTextNodes.push(roomTextNode);
+ }
+ currentTextNode = nextTextNode;
+ }
+
+ if (roomNotifTextNodes.length > 0) {
+ const pushProcessor = new PushProcessor(MatrixClientPeg.get());
+ const atRoomRule = pushProcessor.getPushRuleById(".m.rule.roomnotif");
+ if (pushProcessor.ruleMatchesEvent(atRoomRule, this.props.mxEvent)) {
+ // Now replace all those nodes with Pills
+ for (const roomNotifTextNode of roomNotifTextNodes) {
+ const pillContainer = document.createElement('span');
+ const room = MatrixClientPeg.get().getRoom(this.props.mxEvent.getRoomId());
+ const pill = ;
+
+ ReactDOM.render(pill, pillContainer);
+ roomNotifTextNode.parentNode.replaceChild(pillContainer, roomNotifTextNode);
+
+ // Set the next node to be processed to the one after the node
+ // we're adding now, since we've just inserted nodes into the structure
+ // we're iterating over.
+ // Note we've checked roomNotifTextNodes.length > 0 so we'll do this at least once
+ node = roomNotifTextNode.nextSibling;
+ }
+ // Nothing else to do for a text node (and we don't need to advance
+ // the loop pointer because we did it above)
+ continue;
+ }
}
- } else if (node.children && node.children.length) {
- this.pillifyLinks(node.children);
}
+
+ if (node.childNodes && node.childNodes.length && !pillified) {
+ this.pillifyLinks(node.childNodes);
+ }
+
+ node = node.nextSibling;
}
},