From 112c74a255e9a74c8393dbc299b790e944f9482a Mon Sep 17 00:00:00 2001 From: David Baker Date: Thu, 2 Nov 2017 18:54:25 +0000 Subject: [PATCH 1/5] Add NotifProvider to offer @room as a completion --- src/autocomplete/Autocompleter.js | 2 + src/autocomplete/NotifProvider.js | 63 +++++++++++++++++++++++++++++++ src/i18n/strings/en_EN.json | 2 + 3 files changed, 67 insertions(+) create mode 100644 src/autocomplete/NotifProvider.js 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..fb33d0061b --- /dev/null +++ b/src/autocomplete/NotifProvider.js @@ -0,0 +1,63 @@ +/* +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 []; + + let completions = []; + 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/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index bc2f0754a7..c1dd089d10 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -891,6 +891,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", From adc42904516c1a5a5c84ee90666f8a48266750a0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 6 Nov 2017 15:11:42 +0000 Subject: [PATCH 2/5] Pillify room notif pills in composer --- .../views/rooms/MessageComposerInput.js | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 43f3aa5d88..42ab553b46 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -58,6 +58,9 @@ const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000; const ZWS_CODE = 8203; const ZWS = String.fromCharCode(ZWS_CODE); // zero width space + +const ATROOMPILL_ENTITY_TYPE = 'ATROOMPILL'; + function stateToMarkdown(state) { return __stateToMarkdown(state) .replace( @@ -188,13 +191,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() === ATROOMPILL_ENTITY_TYPE + ) ); }, callback, ); @@ -210,11 +216,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 === ATROOMPILL_ENTITY_TYPE) { + 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 +1003,11 @@ export default class MessageComposerInput extends React.Component { isCompletion: true, }); entityKey = contentState.getLastCreatedEntityKey(); + } else if (completion === '@room') { + contentState = contentState.createEntity(ATROOMPILL_ENTITY_TYPE, 'IMMUTABLE', { + isCompletion: true, + }); + entityKey = contentState.getLastCreatedEntityKey(); } let selection; From 41e7496ff1ead9faaa0fcee52c2e972d9f2984a0 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 6 Nov 2017 15:25:25 +0000 Subject: [PATCH 3/5] unused var --- src/autocomplete/NotifProvider.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/autocomplete/NotifProvider.js b/src/autocomplete/NotifProvider.js index fb33d0061b..b7ac645525 100644 --- a/src/autocomplete/NotifProvider.js +++ b/src/autocomplete/NotifProvider.js @@ -36,7 +36,6 @@ export default class NotifProvider extends AutocompleteProvider { if (!this.room.currentState.mayTriggerNotifOfType('room', client.credentials.userId)) return []; - let completions = []; const {command, range} = this.getCurrentCommand(query, selection, force); if (command && command[0] && '@room'.startsWith(command[0]) && command[0].length > 1) { return [{ From 08d006d1125f73cf96ad7bbbcb12cdb48e5af359 Mon Sep 17 00:00:00 2001 From: David Baker Date: Mon, 6 Nov 2017 17:15:09 +0000 Subject: [PATCH 4/5] PR feedback --- src/components/views/rooms/MessageComposerInput.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 42ab553b46..f6dfe90735 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -59,7 +59,9 @@ const TYPING_USER_TIMEOUT = 10000, TYPING_SERVER_TIMEOUT = 30000; const ZWS_CODE = 8203; const ZWS = String.fromCharCode(ZWS_CODE); // zero width space -const ATROOMPILL_ENTITY_TYPE = 'ATROOMPILL'; +const ENTITY_TYPES = { + AT_ROOM_PILL = 'ATROOMPILL', +}; function stateToMarkdown(state) { return __stateToMarkdown(state) @@ -199,7 +201,7 @@ export default class MessageComposerInput extends React.Component { entityKey !== null && ( contentState.getEntity(entityKey).getType() === 'LINK' || - contentState.getEntity(entityKey).getType() === ATROOMPILL_ENTITY_TYPE + contentState.getEntity(entityKey).getType() === ENTITY_TYPES.AT_ROOM_PILL ) ); }, callback, @@ -221,7 +223,7 @@ export default class MessageComposerInput extends React.Component { const Pill = sdk.getComponent('elements.Pill'); const type = entityProps.contentState.getEntity(entityProps.entityKey).getType(); const {url} = entityProps.contentState.getEntity(entityProps.entityKey).getData(); - if (type === ATROOMPILL_ENTITY_TYPE) { + if (type === ENTITY_TYPES.AT_ROOM_PILL) { return Date: Mon, 6 Nov 2017 22:01:23 +0000 Subject: [PATCH 5/5] get dict syntax right --- src/components/views/rooms/MessageComposerInput.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index f6dfe90735..aa019de091 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -60,7 +60,7 @@ const ZWS_CODE = 8203; const ZWS = String.fromCharCode(ZWS_CODE); // zero width space const ENTITY_TYPES = { - AT_ROOM_PILL = 'ATROOMPILL', + AT_ROOM_PILL: 'ATROOMPILL', }; function stateToMarkdown(state) {