diff --git a/src/HtmlUtils.js b/src/HtmlUtils.js index 2bb0ef4c04..63445dcf30 100644 --- a/src/HtmlUtils.js +++ b/src/HtmlUtils.js @@ -55,7 +55,30 @@ export function unicodeToImage(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'; + } else { + const temp = document.createElement('div'); + temp.appendChild(element.cloneNode(true)); + contentHTML += temp.innerHTML; + } + } + + return contentHTML; +} var sanitizeHtmlParams = { allowedTags: [ @@ -153,8 +176,8 @@ class BaseHighlighter { } // handle postamble - if (lastOffset != safeSnippet.length) { - var subSnippet = safeSnippet.substring(lastOffset, undefined); + if (lastOffset !== safeSnippet.length) { + subSnippet = safeSnippet.substring(lastOffset, undefined); nodes = nodes.concat(this._applySubHighlights(subSnippet, safeHighlights)); } return nodes; @@ -219,7 +242,7 @@ class TextHighlighter extends BaseHighlighter { ; if (highlight && this.highlightLink) { - node = {node} + node = {node}; } return node; @@ -227,7 +250,6 @@ class TextHighlighter extends BaseHighlighter { } -module.exports = { /* turn a matrix event body into html * * content: 'content' of the MatrixEvent @@ -236,59 +258,57 @@ module.exports = { * * opts.highlightLink: optional href to add to highlighted words */ - bodyToHtml: function(content, highlights, opts) { - opts = opts || {}; +export function bodyToHtml(content, highlights, opts) { + opts = opts || {}; - var isHtml = (content.format === "org.matrix.custom.html"); - let body = isHtml ? content.formatted_body : escape(content.body); + var isHtml = (content.format === "org.matrix.custom.html"); + let body = isHtml ? content.formatted_body : escape(content.body); - var safeBody; - // 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 - // are interrupted by HTML tags (not that we did before) - e.g. foobar won't get highlighted - // by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either - try { - if (highlights && highlights.length > 0) { - var highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink); - var safeHighlights = highlights.map(function(highlight) { - return sanitizeHtml(highlight, sanitizeHtmlParams); - }); - // XXX: hacky bodge to temporarily apply a textFilter to the sanitizeHtmlParams structure. - sanitizeHtmlParams.textFilter = function(safeText) { - return highlighter.applyHighlights(safeText, safeHighlights).join(''); - }; - } - safeBody = sanitizeHtml(body, sanitizeHtmlParams); - safeBody = unicodeToImage(safeBody); - } - finally { - delete sanitizeHtmlParams.textFilter; + var safeBody; + // 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 + // are interrupted by HTML tags (not that we did before) - e.g. foobar won't get highlighted + // by an attempt to search for 'foobar'. Then again, the search query probably wouldn't work either + try { + if (highlights && highlights.length > 0) { + var highlighter = new HtmlHighlighter("mx_EventTile_searchHighlight", opts.highlightLink); + var safeHighlights = highlights.map(function(highlight) { + return sanitizeHtml(highlight, sanitizeHtmlParams); + }); + // XXX: hacky bodge to temporarily apply a textFilter to the sanitizeHtmlParams structure. + sanitizeHtmlParams.textFilter = function(safeText) { + return highlighter.applyHighlights(safeText, safeHighlights).join(''); + }; } + safeBody = sanitizeHtml(body, sanitizeHtmlParams); + safeBody = unicodeToImage(safeBody); + } + finally { + delete sanitizeHtmlParams.textFilter; + } - EMOJI_REGEX.lastIndex = 0; - let contentBodyTrimmed = content.body.trim(); - let match = EMOJI_REGEX.exec(contentBodyTrimmed); - let emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length; + EMOJI_REGEX.lastIndex = 0; + let contentBodyTrimmed = content.body.trim(); + let match = EMOJI_REGEX.exec(contentBodyTrimmed); + let emojiBody = match && match[0] && match[0].length === contentBodyTrimmed.length; - const className = classNames({ - 'mx_EventTile_body': true, - 'mx_EventTile_bigEmoji': emojiBody, - 'markdown-body': isHtml, - }); - return ; - }, + const className = classNames({ + 'mx_EventTile_body': true, + 'mx_EventTile_bigEmoji': emojiBody, + 'markdown-body': isHtml, + }); + return ; +} - highlightDom: function(element) { - var blocks = element.getElementsByTagName("code"); - for (var i = 0; i < blocks.length; i++) { - highlight.highlightBlock(blocks[i]); - } - }, - - emojifyText: function(text) { - return { - __html: unicodeToImage(escape(text)), - }; - }, -}; +export function highlightDom(element) { + var blocks = element.getElementsByTagName("code"); + for (var i = 0; i < blocks.length; i++) { + highlight.highlightBlock(blocks[i]); + } +} +export function emojifyText(text) { + return { + __html: unicodeToImage(escape(text)), + }; +} diff --git a/src/SlashCommands.js b/src/SlashCommands.js index 16df5ef2e1..523d1d8f3c 100644 --- a/src/SlashCommands.js +++ b/src/SlashCommands.js @@ -304,7 +304,7 @@ module.exports = { // IRC-style commands input = input.replace(/\s+$/, ""); if (input[0] === "/" && input[1] !== "/") { - var bits = input.match(/^(\S+?)( +(.*))?$/); + var bits = input.match(/^(\S+?)( +((.|\n)*))?$/); var cmd, args; if (bits) { cmd = bits[1].substring(1).toLowerCase(); diff --git a/src/components/views/rooms/MessageComposerInput.js b/src/components/views/rooms/MessageComposerInput.js index 3c15259dd9..2733bddc14 100644 --- a/src/components/views/rooms/MessageComposerInput.js +++ b/src/components/views/rooms/MessageComposerInput.js @@ -47,6 +47,7 @@ import KeyCode from '../../../KeyCode'; import UserSettingsStore from '../../../UserSettingsStore'; import * as RichText from '../../../RichText'; +import * as HtmlUtils from '../../../HtmlUtils'; import Autocomplete from './Autocomplete'; import {Completion} from "../../../autocomplete/Autocompleter"; @@ -63,18 +64,9 @@ function stateToMarkdown(state) { ''); // this is *not* a zero width space, trust me :) } - -// FIXME Breaks markdown with multiple paragraphs, since it only strips first and last

function mdownToHtml(mdown: string): string { let html = marked(mdown) || ""; html = html.trim(); - // strip start and end

tags else you get 'orrible spacing - if (html.indexOf("

") === 0) { - html = html.substring("

".length); - } - if (html.lastIndexOf("

") === (html.length - "

".length)) { - html = html.substring(0, html.length - "

".length); - } return html; } @@ -547,6 +539,8 @@ export default class MessageComposerInput extends React.Component { contentHTML = mdownToHtml(contentText); } + contentHTML = HtmlUtils.stripParagraphs(contentHTML); + let sendFn = this.client.sendHtmlMessage; if (contentText.startsWith('/me')) { @@ -561,16 +555,16 @@ export default class MessageComposerInput extends React.Component { sendMessagePromise.then(() => { dis.dispatch({ - action: 'message_sent' + action: 'message_sent', }); }, () => { dis.dispatch({ - action: 'message_send_failed' + action: 'message_send_failed', }); }); this.setState({ - editorState: this.createEditorState() + editorState: this.createEditorState(), }); return true;