Merge branch 'develop' into luke/feature-async-pills
commit
d13c4b510c
|
@ -170,11 +170,11 @@ const Pill = React.createClass({
|
||||||
|
|
||||||
if (this.state.pillType) {
|
if (this.state.pillType) {
|
||||||
return this.props.inMessage ?
|
return this.props.inMessage ?
|
||||||
<a className={classes} href={this.props.url}>
|
<a className={classes} href={this.props.url} title={resource}>
|
||||||
{avatar}
|
{avatar}
|
||||||
{linkText}
|
{linkText}
|
||||||
</a> :
|
</a> :
|
||||||
<span className={classes}>
|
<span className={classes} title={resource}>
|
||||||
{avatar}
|
{avatar}
|
||||||
{linkText}
|
{linkText}
|
||||||
</span>;
|
</span>;
|
||||||
|
|
|
@ -43,6 +43,10 @@ import Markdown from '../../../Markdown';
|
||||||
import ComposerHistoryManager from '../../../ComposerHistoryManager';
|
import ComposerHistoryManager from '../../../ComposerHistoryManager';
|
||||||
import MessageComposerStore from '../../../stores/MessageComposerStore';
|
import MessageComposerStore from '../../../stores/MessageComposerStore';
|
||||||
|
|
||||||
|
import {MATRIXTO_URL_PATTERN, MATRIXTO_MD_LINK_PATTERN} from '../../../linkify-matrix';
|
||||||
|
const REGEX_MATRIXTO = new RegExp(MATRIXTO_URL_PATTERN);
|
||||||
|
const REGEX_MATRIXTO_MARKDOWN_GLOBAL = new RegExp(MATRIXTO_MD_LINK_PATTERN, 'g');
|
||||||
|
|
||||||
import {asciiRegexp, shortnameToUnicode, emojioneList, asciiList, mapUnicodeToShort} from 'emojione';
|
import {asciiRegexp, shortnameToUnicode, emojioneList, asciiList, mapUnicodeToShort} from 'emojione';
|
||||||
const EMOJI_SHORTNAMES = Object.keys(emojioneList);
|
const EMOJI_SHORTNAMES = Object.keys(emojioneList);
|
||||||
const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort();
|
const EMOJI_UNICODE_TO_SHORTNAME = mapUnicodeToShort();
|
||||||
|
@ -727,6 +731,35 @@ export default class MessageComposerInput extends React.Component {
|
||||||
sendTextFn = this.client.sendEmoteMessage;
|
sendTextFn = this.client.sendEmoteMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Strip MD user (tab-completed) mentions to preserve plaintext mention behaviour
|
||||||
|
contentText = contentText.replace(REGEX_MATRIXTO_MARKDOWN_GLOBAL,
|
||||||
|
(markdownLink, text, resource, prefix, offset) => {
|
||||||
|
// Calculate the offset relative to the current block that the offset is in
|
||||||
|
let sum = 0;
|
||||||
|
const blocks = contentState.getBlocksAsArray();
|
||||||
|
let block;
|
||||||
|
for (let i = 0; i < blocks.length; i++) {
|
||||||
|
block = blocks[i];
|
||||||
|
sum += block.getLength();
|
||||||
|
if (sum > offset) {
|
||||||
|
sum -= block.getLength();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
offset -= sum;
|
||||||
|
|
||||||
|
const entityKey = block.getEntityAt(offset);
|
||||||
|
const entity = entityKey ? Entity.get(entityKey) : null;
|
||||||
|
if (entity && entity.getData().isCompletion && prefix === '@') {
|
||||||
|
// This is a completed mention, so do not insert MD link, just text
|
||||||
|
return text;
|
||||||
|
} else {
|
||||||
|
// This is either a MD link that was typed into the composer or another
|
||||||
|
// type of pill (e.g. room pill)
|
||||||
|
return markdownLink;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let sendMessagePromise;
|
let sendMessagePromise;
|
||||||
if (contentHTML) {
|
if (contentHTML) {
|
||||||
sendMessagePromise = sendHtmlFn.call(
|
sendMessagePromise = sendHtmlFn.call(
|
||||||
|
@ -890,7 +923,10 @@ export default class MessageComposerInput extends React.Component {
|
||||||
let entityKey;
|
let entityKey;
|
||||||
let mdCompletion;
|
let mdCompletion;
|
||||||
if (href) {
|
if (href) {
|
||||||
entityKey = Entity.create('LINK', 'IMMUTABLE', {url: href});
|
entityKey = Entity.create('LINK', 'IMMUTABLE', {
|
||||||
|
url: href,
|
||||||
|
isCompletion: true,
|
||||||
|
});
|
||||||
if (!this.state.isRichtextEnabled) {
|
if (!this.state.isRichtextEnabled) {
|
||||||
mdCompletion = `[${completion}](${href})`;
|
mdCompletion = `[${completion}](${href})`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,8 @@ matrixLinkify.VECTOR_URL_PATTERN = "^(?:https?:\/\/)?(?:"
|
||||||
+ ")(#.*)";
|
+ ")(#.*)";
|
||||||
|
|
||||||
matrixLinkify.MATRIXTO_URL_PATTERN = "^(?:https?:\/\/)?(?:www\\.)?matrix\\.to/#/((#|@|!).*)";
|
matrixLinkify.MATRIXTO_URL_PATTERN = "^(?:https?:\/\/)?(?:www\\.)?matrix\\.to/#/((#|@|!).*)";
|
||||||
|
matrixLinkify.MATRIXTO_MD_LINK_PATTERN =
|
||||||
|
'\\[([^\\]]*)\\]\\((?:https?:\/\/)?(?:www\\.)?matrix\\.to/#/((#|@|!)[^\\)]*)\\)';
|
||||||
matrixLinkify.MATRIXTO_BASE_URL= "https://matrix.to";
|
matrixLinkify.MATRIXTO_BASE_URL= "https://matrix.to";
|
||||||
|
|
||||||
matrixLinkify.options = {
|
matrixLinkify.options = {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import sdk from 'matrix-react-sdk';
|
||||||
import UserSettingsStore from '../../../../src/UserSettingsStore';
|
import UserSettingsStore from '../../../../src/UserSettingsStore';
|
||||||
const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput');
|
const MessageComposerInput = sdk.getComponent('views.rooms.MessageComposerInput');
|
||||||
import MatrixClientPeg from '../../../../src/MatrixClientPeg';
|
import MatrixClientPeg from '../../../../src/MatrixClientPeg';
|
||||||
|
import RoomMember from 'matrix-js-sdk';
|
||||||
|
|
||||||
function addTextToDraft(text) {
|
function addTextToDraft(text) {
|
||||||
const components = document.getElementsByClassName('public-DraftEditor-content');
|
const components = document.getElementsByClassName('public-DraftEditor-content');
|
||||||
|
@ -31,6 +32,7 @@ describe('MessageComposerInput', () => {
|
||||||
testUtils.beforeEach(this);
|
testUtils.beforeEach(this);
|
||||||
sandbox = testUtils.stubClient(sandbox);
|
sandbox = testUtils.stubClient(sandbox);
|
||||||
client = MatrixClientPeg.get();
|
client = MatrixClientPeg.get();
|
||||||
|
client.credentials = {userId: '@me:domain.com'};
|
||||||
|
|
||||||
parentDiv = document.createElement('div');
|
parentDiv = document.createElement('div');
|
||||||
document.body.appendChild(parentDiv);
|
document.body.appendChild(parentDiv);
|
||||||
|
@ -236,4 +238,68 @@ describe('MessageComposerInput', () => {
|
||||||
expect(spy.calledOnce).toEqual(true);
|
expect(spy.calledOnce).toEqual(true);
|
||||||
expect(spy.args[0][1]).toEqual('Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n\nFusce congue sapien sed neque molestie volutpat.');
|
expect(spy.args[0][1]).toEqual('Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n\nFusce congue sapien sed neque molestie volutpat.');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should strip tab-completed mentions so that only the display name is sent in the plain body in Markdown mode', () => {
|
||||||
|
// Sending a HTML message because we have entities in the composer (because of completions)
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
mci.enableRichtext(false);
|
||||||
|
mci.setDisplayedCompletion({
|
||||||
|
completion: 'Some Member',
|
||||||
|
selection: mci.state.editorState.getSelection(),
|
||||||
|
href: `https://matrix.to/#/@some_member:domain.bla`,
|
||||||
|
});
|
||||||
|
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
|
||||||
|
expect(spy.args[0][1]).toEqual(
|
||||||
|
'Some Member',
|
||||||
|
'the plaintext body should only include the display name',
|
||||||
|
);
|
||||||
|
expect(spy.args[0][2]).toEqual(
|
||||||
|
'<a href="https://matrix.to/#/@some_member:domain.bla">Some Member</a>',
|
||||||
|
'the html body should contain an anchor tag with a matrix.to href and display name text',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should strip tab-completed mentions so that only the display name is sent in the plain body in RTE mode', () => {
|
||||||
|
// Sending a HTML message because we have entities in the composer (because of completions)
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
mci.enableRichtext(true);
|
||||||
|
mci.setDisplayedCompletion({
|
||||||
|
completion: 'Some Member',
|
||||||
|
selection: mci.state.editorState.getSelection(),
|
||||||
|
href: `https://matrix.to/#/@some_member:domain.bla`,
|
||||||
|
});
|
||||||
|
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
|
||||||
|
expect(spy.args[0][1]).toEqual('Some Member');
|
||||||
|
expect(spy.args[0][2]).toEqual('<a href="https://matrix.to/#/@some_member:domain.bla">Some Member</a>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not strip non-tab-completed mentions when manually typing MD', () => {
|
||||||
|
// Sending a HTML message because we have entities in the composer (because of completions)
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
// Markdown mode enabled
|
||||||
|
mci.enableRichtext(false);
|
||||||
|
addTextToDraft('[My Not-Tab-Completed Mention](https://matrix.to/#/@some_member:domain.bla)');
|
||||||
|
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
|
||||||
|
expect(spy.args[0][1]).toEqual('[My Not-Tab-Completed Mention](https://matrix.to/#/@some_member:domain.bla)');
|
||||||
|
expect(spy.args[0][2]).toEqual('<a href="https://matrix.to/#/@some_member:domain.bla">My Not-Tab-Completed Mention</a>');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not strip arbitrary typed (i.e. not tab-completed) MD links', () => {
|
||||||
|
// Sending a HTML message because we have entities in the composer (because of completions)
|
||||||
|
const spy = sinon.spy(client, 'sendHtmlMessage');
|
||||||
|
// Markdown mode enabled
|
||||||
|
mci.enableRichtext(false);
|
||||||
|
addTextToDraft('[Click here](https://some.lovely.url)');
|
||||||
|
|
||||||
|
mci.handleReturn(sinon.stub());
|
||||||
|
|
||||||
|
expect(spy.args[0][1]).toEqual('[Click here](https://some.lovely.url)');
|
||||||
|
expect(spy.args[0][2]).toEqual('<a href="https://some.lovely.url">Click here</a>');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -238,7 +238,12 @@ export function mkStubRoom(roomId = null) {
|
||||||
return {
|
return {
|
||||||
roomId,
|
roomId,
|
||||||
getReceiptsForEvent: sinon.stub().returns([]),
|
getReceiptsForEvent: sinon.stub().returns([]),
|
||||||
getMember: sinon.stub().returns({}),
|
getMember: sinon.stub().returns({
|
||||||
|
userId: '@member:domain.bla',
|
||||||
|
name: 'Member',
|
||||||
|
roomId: roomId,
|
||||||
|
getAvatarUrl: () => 'mxc://avatar.url/image.png',
|
||||||
|
}),
|
||||||
getJoinedMembers: sinon.stub().returns([]),
|
getJoinedMembers: sinon.stub().returns([]),
|
||||||
getPendingEvents: () => [],
|
getPendingEvents: () => [],
|
||||||
getLiveTimeline: () => stubTimeline,
|
getLiveTimeline: () => stubTimeline,
|
||||||
|
|
Loading…
Reference in New Issue