diff --git a/src/autocomplete/Autocompleter.js b/src/autocomplete/Autocompleter.js
index 94d2ed28de..3d30363d9f 100644
--- a/src/autocomplete/Autocompleter.js
+++ b/src/autocomplete/Autocompleter.js
@@ -23,6 +23,7 @@ import DuckDuckGoProvider from './DuckDuckGoProvider';
import RoomProvider from './RoomProvider';
import UserProvider from './UserProvider';
import EmojiProvider from './EmojiProvider';
+import NotifProvider from './NotifProvider';
import Promise from 'bluebird';
export type SelectionRange = {
@@ -44,6 +45,7 @@ const PROVIDERS = [
UserProvider,
RoomProvider,
EmojiProvider,
+ NotifProvider,
CommandProvider,
DuckDuckGoProvider,
];
diff --git a/src/autocomplete/NotifProvider.js b/src/autocomplete/NotifProvider.js
new file mode 100644
index 0000000000..b7ac645525
--- /dev/null
+++ b/src/autocomplete/NotifProvider.js
@@ -0,0 +1,62 @@
+/*
+Copyright 2017 New Vector Ltd
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+import React from 'react';
+import AutocompleteProvider from './AutocompleteProvider';
+import { _t } from '../languageHandler';
+import MatrixClientPeg from '../MatrixClientPeg';
+import {PillCompletion} from './Components';
+import sdk from '../index';
+
+const AT_ROOM_REGEX = /@\S*/g;
+
+export default class NotifProvider extends AutocompleteProvider {
+ constructor(room) {
+ super(AT_ROOM_REGEX);
+ this.room = room;
+ }
+
+ async getCompletions(query: string, selection: {start: number, end: number}, force = false) {
+ const RoomAvatar = sdk.getComponent('views.avatars.RoomAvatar');
+
+ const client = MatrixClientPeg.get();
+
+ if (!this.room.currentState.mayTriggerNotifOfType('room', client.credentials.userId)) return [];
+
+ const {command, range} = this.getCurrentCommand(query, selection, force);
+ if (command && command[0] && '@room'.startsWith(command[0]) && command[0].length > 1) {
+ return [{
+ completion: '@room',
+ suffix: ' ',
+ component: (
+ } title="@room" description={_t("Notify the whole room")} />
+ ),
+ range,
+ }];
+ }
+ return [];
+ }
+
+ getName() {
+ return '❗️ ' + _t('Room Notification');
+ }
+
+ renderCompletions(completions: [React.Component]): ?React.Component {
+ return
+ { completions }
+
;
+ }
+}
diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js
index 43f3aa5d88..aa019de091 100644
--- a/src/components/views/rooms/MessageComposerInput.js
+++ b/src/components/views/rooms/MessageComposerInput.js
@@ -58,6 +58,11 @@ const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000;
const ZWS_CODE = 8203;
const ZWS = String.fromCharCode(ZWS_CODE); // zero width space
+
+const ENTITY_TYPES = {
+ AT_ROOM_PILL: 'ATROOMPILL',
+};
+
function stateToMarkdown(state) {
return __stateToMarkdown(state)
.replace(
@@ -188,13 +193,16 @@ export default class MessageComposerInput extends React.Component {
this.client = MatrixClientPeg.get();
}
- findLinkEntities(contentState: ContentState, contentBlock: ContentBlock, callback) {
+ findPillEntities(contentState: ContentState, contentBlock: ContentBlock, callback) {
contentBlock.findEntityRanges(
(character) => {
const entityKey = character.getEntity();
return (
entityKey !== null &&
- contentState.getEntity(entityKey).getType() === 'LINK'
+ (
+ contentState.getEntity(entityKey).getType() === 'LINK' ||
+ contentState.getEntity(entityKey).getType() === ENTITY_TYPES.AT_ROOM_PILL
+ )
);
}, callback,
);
@@ -210,11 +218,19 @@ export default class MessageComposerInput extends React.Component {
RichText.getScopedMDDecorators(this.props);
const shouldShowPillAvatar = !UserSettingsStore.getSyncedSetting("Pill.shouldHidePillAvatar", false);
decorators.push({
- strategy: this.findLinkEntities.bind(this),
+ strategy: this.findPillEntities.bind(this),
component: (entityProps) => {
const Pill = sdk.getComponent('elements.Pill');
+ const type = entityProps.contentState.getEntity(entityProps.entityKey).getType();
const {url} = entityProps.contentState.getEntity(entityProps.entityKey).getData();
- if (Pill.isPillUrl(url)) {
+ if (type === ENTITY_TYPES.AT_ROOM_PILL) {
+ return ;
+ } else if (Pill.isPillUrl(url)) {
return {
let blockText = block.getText();
let offset = 0;
- this.findLinkEntities(contentState, block, (start, end) => {
+ this.findPillEntities(contentState, block, (start, end) => {
const entity = contentState.getEntity(block.getEntityAt(start));
if (entity.getType() !== 'LINK') {
return;
@@ -989,6 +1005,11 @@ export default class MessageComposerInput extends React.Component {
isCompletion: true,
});
entityKey = contentState.getLastCreatedEntityKey();
+ } else if (completion === '@room') {
+ contentState = contentState.createEntity(ENTITY_TYPES.AT_ROOM_PILL, 'IMMUTABLE', {
+ isCompletion: true,
+ });
+ entityKey = contentState.getLastCreatedEntityKey();
}
let selection;
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json
index 83211f6e27..bff52e2e93 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -892,6 +892,8 @@
"Commands": "Commands",
"Results from DuckDuckGo": "Results from DuckDuckGo",
"Emoji": "Emoji",
+ "Notify the whole room": "Notify the whole room",
+ "Room Notification": "Room Notification",
"Users": "Users",
"unknown device": "unknown device",
"NOT verified": "NOT verified",