From 31290f3377465b95aa46cf3a0025b40635325d55 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 17 Jul 2017 14:27:50 +0100 Subject: [PATCH 1/6] Remove redundant, unused decorators from RTE These have since been replaced by decorators that operator whether in MD mode or otherwise. This might not be optimal because LINK entities do not appear in MD mode at all at the moment, but instead you see the ()[] md notation version. --- src/RichText.js | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/src/RichText.js b/src/RichText.js index f2f2d533a8..3cd0f5ec59 100644 --- a/src/RichText.js +++ b/src/RichText.js @@ -113,31 +113,6 @@ let emojiDecorator = { * Returns a composite decorator which has access to provided scope. */ export function getScopedRTDecorators(scope: any): CompositeDecorator { - let MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); - - let usernameDecorator = { - strategy: (contentBlock, callback) => { - findWithRegex(USERNAME_REGEX, contentBlock, callback); - }, - component: (props) => { - let member = scope.room.getMember(props.children[0].props.text); - // unused until we make these decorators immutable (autocomplete needed) - let name = member ? member.name : null; - let avatar = member ? : null; - return {avatar}{props.children}; - } - }; - - let roomDecorator = { - strategy: (contentBlock, callback) => { - findWithRegex(ROOM_REGEX, contentBlock, callback); - }, - component: (props) => { - return {props.children}; - } - }; - - // TODO Re-enable usernameDecorator and roomDecorator return [emojiDecorator]; } From b1e3dc406c156717526a8e6629a20810d6532e5d Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 17 Jul 2017 14:50:45 +0100 Subject: [PATCH 2/6] Decorate matrix.to anchor tags as mx_UserPill and mx_RoomPill Requires https://github.com/vector-im/riot-web/pull/4597 to look OK --- src/components/views/messages/TextualBody.js | 53 ++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 2c50a94a6a..998818da20 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -30,6 +30,8 @@ import SdkConfig from '../../../SdkConfig'; import dis from '../../../dispatcher'; import { _t } from '../../../languageHandler'; import UserSettingsStore from "../../../UserSettingsStore"; +import MatrixClientPeg from '../../../MatrixClientPeg'; +import {RoomMember} from 'matrix-js-sdk'; linkifyMatrix(linkify); @@ -80,6 +82,10 @@ module.exports = React.createClass({ componentDidMount: function() { this._unmounted = false; + // pillifyLinks BEFORE linkifyElement because plain room/user URLs in the composer + // are still sent as plaintext URLs. If these are ever pillified in the composer, + // we should be pillify them here by doing the linkifying BEFORE the pillifying. + this.pillifyLinks(this.refs.content.children); linkifyElement(this.refs.content, linkifyMatrix.options); this.calculateUrlPreview(); @@ -162,6 +168,53 @@ module.exports = React.createClass({ } }, + pillifyLinks: function(nodes) { + const MemberAvatar = sdk.getComponent('avatars.MemberAvatar'); + const RoomAvatar = sdk.getComponent('avatars.RoomAvatar'); + for (let i = 0; i < nodes.length; i++) { + const node = nodes[i]; + if (node.tagName === "A" && node.getAttribute("href")) { + const href = node.getAttribute("href"); + // HtmlUtils transforms `matrix.to` links to local links, so match against + // user or room app links. + const match = /^#\/(user|room)\/(.*)$/.exec(href); + if (match) { + let avatar; + let roomId; + let room; + let member; + switch (match[1]) { + case "user": + roomId = this.props.mxEvent.getRoomId(); + room = MatrixClientPeg.get().getRoom(roomId); + member = room.getMember(match[2]) || + new RoomMember(null, match[2]); + avatar = ; + break; + case "room": + room = match[2][0] === '#' ? + MatrixClientPeg.get().getRooms().find((r) => { + return r.getCanonicalAlias() === match[2]; + }) : MatrixClientPeg.get().getRoom(match[2]); + if (room) { + avatar = ; + } + break; + } + if (avatar) { + const avatarContainer = document.createElement('span'); + node.className = "mx_MTextBody_pill " + + (match[1] === "user" ? "mx_UserPill" : "mx_RoomPill"); + ReactDOM.render(avatar, avatarContainer); + node.insertBefore(avatarContainer, node.firstChild); + } + } + } else if (node.children && node.children.length) { + this.pillifyLinks(node.children); + } + } + }, + findLinks: function(nodes) { var links = []; From d207ee5244dff40ee4f1f8cce140c7b1e1d55581 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Mon, 17 Jul 2017 19:31:36 +0100 Subject: [PATCH 3/6] Expand groups into variables with readable names --- src/components/views/messages/TextualBody.js | 22 +++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/views/messages/TextualBody.js b/src/components/views/messages/TextualBody.js index 998818da20..ab1d67a218 100644 --- a/src/components/views/messages/TextualBody.js +++ b/src/components/views/messages/TextualBody.js @@ -177,25 +177,27 @@ module.exports = React.createClass({ const href = node.getAttribute("href"); // HtmlUtils transforms `matrix.to` links to local links, so match against // user or room app links. - const match = /^#\/(user|room)\/(.*)$/.exec(href); - if (match) { + const match = /^#\/(user|room)\/(.*)$/.exec(href) || []; + const resourceType = match[1]; // "user" or "room" + const resourceId = match[2]; // user ID or room ID + if (match && resourceType && resourceId) { let avatar; let roomId; let room; let member; - switch (match[1]) { + switch (resourceType) { case "user": roomId = this.props.mxEvent.getRoomId(); room = MatrixClientPeg.get().getRoom(roomId); - member = room.getMember(match[2]) || - new RoomMember(null, match[2]); - avatar = ; + member = room.getMember(resourceId) || + new RoomMember(null, resourceId); + avatar = ; break; case "room": - room = match[2][0] === '#' ? + room = resourceId[0] === '#' ? MatrixClientPeg.get().getRooms().find((r) => { - return r.getCanonicalAlias() === match[2]; - }) : MatrixClientPeg.get().getRoom(match[2]); + return r.getCanonicalAlias() === resourceId; + }) : MatrixClientPeg.get().getRoom(resourceId); if (room) { avatar = ; } @@ -204,7 +206,7 @@ module.exports = React.createClass({ if (avatar) { const avatarContainer = document.createElement('span'); node.className = "mx_MTextBody_pill " + - (match[1] === "user" ? "mx_UserPill" : "mx_RoomPill"); + (resourceType === "user" ? "mx_UserPill" : "mx_RoomPill"); ReactDOM.render(avatar, avatarContainer); node.insertBefore(avatarContainer, node.firstChild); } From b185f43d3d364de1803b9722645ab66fbf16d763 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 18 Jul 2017 16:23:54 +0100 Subject: [PATCH 4/6] Use _uniq instead of _sortedUniq for return unique matched results _sortedUniq claims to be like _uniq but optimised for sorted arrays - https://lodash.com/docs/4.17.4#sortedUniqBy. But in practice this isn't quite true, so stick with the unoptimised version. --- src/autocomplete/QueryMatcher.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/autocomplete/QueryMatcher.js b/src/autocomplete/QueryMatcher.js index 07398e7a5f..762b285685 100644 --- a/src/autocomplete/QueryMatcher.js +++ b/src/autocomplete/QueryMatcher.js @@ -18,7 +18,7 @@ limitations under the License. import _at from 'lodash/at'; import _flatMap from 'lodash/flatMap'; import _sortBy from 'lodash/sortBy'; -import _sortedUniq from 'lodash/sortedUniq'; +import _uniq from 'lodash/uniq'; import _keys from 'lodash/keys'; class KeyMap { @@ -101,7 +101,7 @@ export default class QueryMatcher { } }); - return _sortedUniq(_flatMap(_sortBy(results, (candidate) => { + return _uniq(_flatMap(_sortBy(results, (candidate) => { return candidate.index; }).map((candidate) => { // return an array of objects (those given to setObjects) that have the given From 98ca937fef26a9650b81f16a2cd6f80f601ec6d2 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Tue, 18 Jul 2017 17:52:04 +0100 Subject: [PATCH 5/6] Interpret backspace at start of style block as block style toggle Part of fixing https://github.com/vector-im/riot-web/issues/4580 --- .../views/rooms/MessageComposerInput.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index d4ae55f03a..2f0901018d 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -528,16 +528,19 @@ export default class MessageComposerInput extends React.Component { if (this.state.isRichtextEnabled) { // These are block types, not handled by RichUtils by default. const blockCommands = ['code-block', 'blockquote', 'unordered-list-item', 'ordered-list-item']; + const currentBlockType = RichUtils.getCurrentBlockType(this.state.editorState); if (blockCommands.includes(command)) { - this.setState({ - editorState: RichUtils.toggleBlockType(this.state.editorState, command), - }); + newState = RichUtils.toggleBlockType(this.state.editorState, command); } else if (command === 'strike') { // this is the only inline style not handled by Draft by default - this.setState({ - editorState: RichUtils.toggleInlineStyle(this.state.editorState, 'STRIKETHROUGH'), - }); + newState = RichUtils.toggleInlineStyle(this.state.editorState, 'STRIKETHROUGH'); + } else if (command === 'backspace' && currentBlockType !== 'unstyled') { + const currentStartOffset = this.state.editorState.getSelection().getStartOffset(); + if (currentStartOffset === 0) { + // Toggle current block type (setting it to 'unstyled') + newState = RichUtils.toggleBlockType(this.state.editorState, currentBlockType); + } } } else { let contentState = this.state.editorState.getCurrentContent(); @@ -644,6 +647,7 @@ export default class MessageComposerInput extends React.Component { // By returning false, we allow the default draft-js key binding to occur, // which in this case invokes "split-block". This creates a new block of the // same type, allowing the user to delete it with backspace. + // See handleKeyCommand (when command === 'backspace') return false; } From c8722292e396f6caae1d0e86971926a787a467d4 Mon Sep 17 00:00:00 2001 From: Luke Barnard Date: Wed, 19 Jul 2017 16:20:57 +0100 Subject: [PATCH 6/6] Don't truncate EmojiProvider completion pills --- src/autocomplete/EmojiProvider.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autocomplete/EmojiProvider.js b/src/autocomplete/EmojiProvider.js index ef8a39edaf..cadbe44bf6 100644 --- a/src/autocomplete/EmojiProvider.js +++ b/src/autocomplete/EmojiProvider.js @@ -118,7 +118,7 @@ export default class EmojiProvider extends AutocompleteProvider { } renderCompletions(completions: [React.Component]): ?React.Component { - return
+ return
{completions}
; }