Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Replace <p>s with <br/>s consistently #482

Merged
merged 1 commit into from
Sep 16, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 76 additions & 56 deletions src/HtmlUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 = {
allowedTags: [
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -219,15 +242,14 @@ class TextHighlighter extends BaseHighlighter {
</span>;

if (highlight && this.highlightLink) {
node = <a key={key} href={this.highlightLink}>{node}</a>
node = <a key={key} href={this.highlightLink}>{node}</a>;
}

return node;
}
}


module.exports = {
/* turn a matrix event body into html
*
* content: 'content' of the MatrixEvent
Expand All @@ -236,59 +258,57 @@ module.exports = {
*
* opts.highlightLink: optional href to add to highlighted words
*/
bodyToHtml: function(content, highlights, opts) {
opts = opts || {};

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. foo<span/>bar 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;
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 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. foo<span/>bar 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;

const className = classNames({
'mx_EventTile_body': true,
'mx_EventTile_bigEmoji': emojiBody,
'markdown-body': isHtml,
});
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} />;
},
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;

highlightDom: function(element) {
var blocks = element.getElementsByTagName("code");
for (var i = 0; i < blocks.length; i++) {
highlight.highlightBlock(blocks[i]);
}
},
const className = classNames({
'mx_EventTile_body': true,
'mx_EventTile_bigEmoji': emojiBody,
'markdown-body': isHtml,
});
return <span className={className} dangerouslySetInnerHTML={{ __html: safeBody }} />;
}

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)),
};
}
2 changes: 1 addition & 1 deletion src/SlashCommands.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
18 changes: 6 additions & 12 deletions src/components/views/rooms/MessageComposerInput.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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 <p>
function mdownToHtml(mdown: string): string {
let html = marked(mdown) || "";
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;
}

Expand Down Expand Up @@ -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')) {
Expand All @@ -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;
Expand Down