mirror of https://github.com/vector-im/riot-web
Replace <p>s with <br/>s consistently
Also, allow newlines in /commands. Fixes vector-im/vector-web#2114, vector-im/vector-web#2165.pull/21833/head
parent
2b9258d377
commit
6befb09509
128
src/HtmlUtils.js
128
src/HtmlUtils.js
|
@ -55,7 +55,30 @@ export function unicodeToImage(str) {
|
||||||
});
|
});
|
||||||
|
|
||||||
return str;
|
return str;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
export function stripParagraphs(html: string): string {
|
||||||
|
const contentDiv = document.createElement('div');
|
||||||
|
contentDiv.innerHTML = html;
|
||||||
|
|
||||||
|
if (contentDiv.children.length === 0) {
|
||||||
|
return contentDiv.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
let contentHTML = "";
|
||||||
|
for (let i=0; i<contentDiv.children.length; i++) {
|
||||||
|
const element = contentDiv.children[i];
|
||||||
|
if (element.tagName.toLowerCase() === 'p') {
|
||||||
|
contentHTML += element.innerHTML + '<br />';
|
||||||
|
} else {
|
||||||
|
const temp = document.createElement('div');
|
||||||
|
temp.appendChild(element.cloneNode(true));
|
||||||
|
contentHTML += temp.innerHTML;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentHTML;
|
||||||
|
}
|
||||||
|
|
||||||
var sanitizeHtmlParams = {
|
var sanitizeHtmlParams = {
|
||||||
allowedTags: [
|
allowedTags: [
|
||||||
|
@ -153,8 +176,8 @@ class BaseHighlighter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle postamble
|
// handle postamble
|
||||||
if (lastOffset != safeSnippet.length) {
|
if (lastOffset !== safeSnippet.length) {
|
||||||
var subSnippet = safeSnippet.substring(lastOffset, undefined);
|
subSnippet = safeSnippet.substring(lastOffset, undefined);
|
||||||
nodes = nodes.concat(this._applySubHighlights(subSnippet, safeHighlights));
|
nodes = nodes.concat(this._applySubHighlights(subSnippet, safeHighlights));
|
||||||
}
|
}
|
||||||
return nodes;
|
return nodes;
|
||||||
|
@ -219,7 +242,7 @@ class TextHighlighter extends BaseHighlighter {
|
||||||
</span>;
|
</span>;
|
||||||
|
|
||||||
if (highlight && this.highlightLink) {
|
if (highlight && this.highlightLink) {
|
||||||
node = <a key={key} href={this.highlightLink}>{node}</a>
|
node = <a key={key} href={this.highlightLink}>{node}</a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
|
@ -227,7 +250,6 @@ class TextHighlighter extends BaseHighlighter {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/* turn a matrix event body into html
|
/* turn a matrix event body into html
|
||||||
*
|
*
|
||||||
* content: 'content' of the MatrixEvent
|
* content: 'content' of the MatrixEvent
|
||||||
|
@ -236,59 +258,57 @@ module.exports = {
|
||||||
*
|
*
|
||||||
* opts.highlightLink: optional href to add to highlighted words
|
* opts.highlightLink: optional href to add to highlighted words
|
||||||
*/
|
*/
|
||||||
bodyToHtml: function(content, highlights, opts) {
|
export function bodyToHtml(content, highlights, opts) {
|
||||||
opts = opts || {};
|
opts = opts || {};
|
||||||
|
|
||||||
var isHtml = (content.format === "org.matrix.custom.html");
|
var isHtml = (content.format === "org.matrix.custom.html");
|
||||||
let body = isHtml ? content.formatted_body : escape(content.body);
|
let body = isHtml ? content.formatted_body : escape(content.body);
|
||||||
|
|
||||||
var safeBody;
|
var safeBody;
|
||||||
// XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying
|
// XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying
|
||||||
// to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which
|
// to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which
|
||||||
// are interrupted by HTML tags (not that we did before) - e.g. foo<span/>bar won't get highlighted
|
// are interrupted by HTML tags (not that we did before) - e.g. foo<span/>bar won't get highlighted
|
||||||
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
|
// by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either
|
||||||
try {
|
try {
|
||||||
if (highlights && highlights.length > 0) {
|
if (highlights && highlights.length > 0) {
|
||||||
var highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
|
var highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink);
|
||||||
var safeHighlights = highlights.map(function(highlight) {
|
var safeHighlights = highlights.map(function(highlight) {
|
||||||
return sanitizeHtml(highlight, sanitizeHtmlParams);
|
return sanitizeHtml(highlight, sanitizeHtmlParams);
|
||||||
});
|
});
|
||||||
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeHtmlParams structure.
|
// XXX: hacky bodge to temporarily apply a textFilter to the sanitizeHtmlParams structure.
|
||||||
sanitizeHtmlParams.textFilter = function(safeText) {
|
sanitizeHtmlParams.textFilter = function(safeText) {
|
||||||
return highlighter.applyHighlights(safeText, safeHighlights).join('');
|
return highlighter.applyHighlights(safeText, safeHighlights).join('');
|
||||||
};
|
};
|
||||||
}
|
|
||||||
safeBody = sanitizeHtml(body, sanitizeHtmlParams);
|
|
||||||
safeBody = unicodeToImage(safeBody);
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
delete sanitizeHtmlParams.textFilter;
|
|
||||||
}
|
}
|
||||||
|
safeBody = sanitizeHtml(body, sanitizeHtmlParams);
|
||||||
|
safeBody = unicodeToImage(safeBody);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
delete sanitizeHtmlParams.textFilter;
|
||||||
|
}
|
||||||
|
|
||||||
EMOJI_REGEX.lastIndex = 0;
|
EMOJI_REGEX.lastIndex = 0;
|
||||||
let contentBodyTrimmed = content.body.trim();
|
let contentBodyTrimmed = content.body.trim();
|
||||||
let match = EMOJI_REGEX.exec(contentBodyTrimmed);
|
let match = EMOJI_REGEX.exec(contentBodyTrimmed);
|
||||||
let emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length;
|
let emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length;
|
||||||
|
|
||||||
const className = classNames({
|
const className = classNames({
|
||||||
'mx_EventTile_body': true,
|
'mx_EventTile_body': true,
|
||||||
'mx_EventTile_bigEmoji': emojiBody,
|
'mx_EventTile_bigEmoji': emojiBody,
|
||||||
'markdown-body': isHtml,
|
'markdown-body': isHtml,
|
||||||
});
|
});
|
||||||
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} />;
|
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} />;
|
||||||
},
|
}
|
||||||
|
|
||||||
highlightDom: function(element) {
|
export function highlightDom(element) {
|
||||||
var blocks = element.getElementsByTagName("code");
|
var blocks = element.getElementsByTagName("code");
|
||||||
for (var i = 0; i < blocks.length; i++) {
|
for (var i = 0; i < blocks.length; i++) {
|
||||||
highlight.highlightBlock(blocks[i]);
|
highlight.highlightBlock(blocks[i]);
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
|
|
||||||
emojifyText: function(text) {
|
|
||||||
return {
|
|
||||||
__html: unicodeToImage(escape(text)),
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
export function emojifyText(text) {
|
||||||
|
return {
|
||||||
|
__html: unicodeToImage(escape(text)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -304,7 +304,7 @@ module.exports = {
|
||||||
// IRC-style commands
|
// IRC-style commands
|
||||||
input = input.replace(/\s+$/, "");
|
input = input.replace(/\s+$/, "");
|
||||||
if (input[0] === "/" && input[1] !== "/") {
|
if (input[0] === "/" && input[1] !== "/") {
|
||||||
var bits = input.match(/^(\S+?)( +(.*))?$/);
|
var bits = input.match(/^(\S+?)( +((.|\n)*))?$/);
|
||||||
var cmd, args;
|
var cmd, args;
|
||||||
if (bits) {
|
if (bits) {
|
||||||
cmd = bits[1].substring(1).toLowerCase();
|
cmd = bits[1].substring(1).toLowerCase();
|
||||||
|
|
|
@ -47,6 +47,7 @@ import KeyCode from '../../../KeyCode';
|
||||||
import UserSettingsStore from '../../../UserSettingsStore';
|
import UserSettingsStore from '../../../UserSettingsStore';
|
||||||
|
|
||||||
import * as RichText from '../../../RichText';
|
import * as RichText from '../../../RichText';
|
||||||
|
import * as HtmlUtils from '../../../HtmlUtils';
|
||||||
import Autocomplete from './Autocomplete';
|
import Autocomplete from './Autocomplete';
|
||||||
import {Completion} from "../../../autocomplete/Autocompleter";
|
import {Completion} from "../../../autocomplete/Autocompleter";
|
||||||
|
|
||||||
|
@ -63,18 +64,9 @@ function stateToMarkdown(state) {
|
||||||
''); // this is *not* a zero width space, trust me :)
|
''); // this is *not* a zero width space, trust me :)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// FIXME Breaks markdown with multiple paragraphs, since it only strips first and last <p>
|
|
||||||
function mdownToHtml(mdown: string): string {
|
function mdownToHtml(mdown: string): string {
|
||||||
let html = marked(mdown) || "";
|
let html = marked(mdown) || "";
|
||||||
html = html.trim();
|
html = html.trim();
|
||||||
// strip start and end <p> tags else you get 'orrible spacing
|
|
||||||
if (html.indexOf("<p>") === 0) {
|
|
||||||
html = html.substring("<p>".length);
|
|
||||||
}
|
|
||||||
if (html.lastIndexOf("</p>") === (html.length - "</p>".length)) {
|
|
||||||
html = html.substring(0, html.length - "</p>".length);
|
|
||||||
}
|
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -547,6 +539,8 @@ export default class MessageComposerInput extends React.Component {
|
||||||
contentHTML = mdownToHtml(contentText);
|
contentHTML = mdownToHtml(contentText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contentHTML = HtmlUtils.stripParagraphs(contentHTML);
|
||||||
|
|
||||||
let sendFn = this.client.sendHtmlMessage;
|
let sendFn = this.client.sendHtmlMessage;
|
||||||
|
|
||||||
if (contentText.startsWith('/me')) {
|
if (contentText.startsWith('/me')) {
|
||||||
|
@ -561,16 +555,16 @@ export default class MessageComposerInput extends React.Component {
|
||||||
|
|
||||||
sendMessagePromise.then(() => {
|
sendMessagePromise.then(() => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'message_sent'
|
action: 'message_sent',
|
||||||
});
|
});
|
||||||
}, () => {
|
}, () => {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'message_send_failed'
|
action: 'message_send_failed',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
editorState: this.createEditorState()
|
editorState: this.createEditorState(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
Loading…
Reference in New Issue