make quoting work

pull/21833/head
Matthew Hodgson 2018-05-21 03:48:59 +01:00
parent 4439a04689
commit 7de45f8b7b
3 changed files with 85 additions and 56 deletions

View File

@ -403,19 +403,22 @@ class TextHighlighter extends BaseHighlighter {
}
/* turn a matrix event body into html
*
* content: 'content' of the MatrixEvent
*
* highlights: optional list of words to highlight, ordered by longest word first
*
* opts.highlightLink: optional href to add to highlighted words
* opts.disableBigEmoji: optional argument to disable the big emoji class.
* opts.stripReplyFallback: optional argument specifying the event is a reply and so fallback needs removing
*/
/* turn a matrix event body into html
*
* content: 'content' of the MatrixEvent
*
* highlights: optional list of words to highlight, ordered by longest word first
*
* opts.highlightLink: optional href to add to highlighted words
* opts.disableBigEmoji: optional argument to disable the big emoji class.
* opts.stripReplyFallback: optional argument specifying the event is a reply and so fallback needs removing
* opts.returnString: return an HTML string rather than JSX elements
* opts.emojiOne: optional param to do emojiOne (default true)
*/
export function bodyToHtml(content, highlights, opts={}) {
const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body;
const doEmojiOne = opts.emojiOne === undefined ? true : opts.emojiOne;
let bodyHasEmoji = false;
let strippedBody;
@ -441,8 +444,9 @@ export function bodyToHtml(content, highlights, opts={}) {
if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyThread.stripHTMLReply(formattedBody);
strippedBody = opts.stripReplyFallback ? ReplyThread.stripPlainReply(content.body) : content.body;
bodyHasEmoji = containsEmoji(isHtmlMessage ? formattedBody : content.body);
if (doEmojiOne) {
bodyHasEmoji = containsEmoji(isHtmlMessage ? formattedBody : content.body);
}
// Only generate safeBody if the message was sent as org.matrix.custom.html
if (isHtmlMessage) {
@ -467,6 +471,10 @@ export function bodyToHtml(content, highlights, opts={}) {
delete sanitizeHtmlParams.textFilter;
}
if (opts.returnString) {
return isDisplayedWithHtml ? safeBody : strippedBody;
}
let emojiBody = false;
if (!opts.disableBigEmoji && bodyHasEmoji) {
EMOJI_REGEX.lastIndex = 0;

View File

@ -179,7 +179,7 @@ module.exports = React.createClass({
onQuoteClick: function() {
dis.dispatch({
action: 'quote',
text: this.props.eventTileOps.getInnerText(),
event: this.props.mxEvent,
});
this.closeMenu();
},

View File

@ -21,7 +21,7 @@ import type SyntheticKeyboardEvent from 'react/lib/SyntheticKeyboardEvent';
import { Editor } from 'slate-react';
import { getEventTransfer } from 'slate-react';
import { Value, Document, Event, Inline, Text, Range, Node } from 'slate';
import { Value, Document, Event, Block, Inline, Text, Range, Node } from 'slate';
import Html from 'slate-html-serializer';
import Md from 'slate-md-serializer';
@ -342,37 +342,44 @@ export default class MessageComposerInput extends React.Component {
});
}
break;
/*
case 'quote': { // old quoting, whilst rich quoting is in labs
/// XXX: Not doing rich-text quoting from formatted-body because draft-js
/// has regressed such that when links are quoted, errors are thrown. See
/// https://github.com/vector-im/riot-web/issues/4756.
const body = escape(payload.text);
if (body) {
let content = RichText.htmlToContentState(`<blockquote>${body}</blockquote>`);
if (!this.state.isRichTextEnabled) {
content = ContentState.createFromText(RichText.stateToMarkdown(content));
}
case 'quote': {
const html = HtmlUtils.bodyToHtml(payload.event.getContent(), null, {
returnString: true,
emojiOne: false,
});
const fragment = this.html.deserialize(html);
// FIXME: do we want to put in a permalink to the original quote here?
// If so, what should be the format, and how do we differentiate it from replies?
const blockMap = content.getBlockMap();
let startSelection = SelectionState.createEmpty(contentState.getFirstBlock().getKey());
contentState = Modifier.splitBlock(contentState, startSelection);
startSelection = SelectionState.createEmpty(contentState.getFirstBlock().getKey());
contentState = Modifier.replaceWithFragment(contentState,
startSelection,
blockMap);
startSelection = SelectionState.createEmpty(contentState.getFirstBlock().getKey());
if (this.state.isRichTextEnabled) {
contentState = Modifier.setBlockType(contentState, startSelection, 'blockquote');
const quote = Block.create('block-quote');
if (this.state.isRichTextEnabled) {
let change = editorState.change();
if (editorState.anchorText.text === '' && editorState.anchorBlock.nodes.size === 1) {
// replace the current block rather than split the block
change = change.replaceNodeByKey(editorState.anchorBlock.key, quote);
}
let editorState = EditorState.push(this.state.editorState, contentState, 'insert-characters');
editorState = EditorState.moveSelectionToEnd(editorState);
this.onEditorContentChanged(editorState);
editor.focus();
else {
// insert it into the middle of the block (splitting it)
change = change.insertBlock(quote);
}
change = change.insertFragmentByKey(quote.key, 0, fragment.document)
.focus();
this.onChange(change);
}
else {
let fragmentChange = fragment.change();
fragmentChange.moveToRangeOf(fragment.document)
.wrapBlock(quote);
// FIXME: handle pills and use commonmark rather than md-serialize
const md = this.md.serialize(fragmentChange.value);
let change = editorState.change()
.insertText(md + '\n\n')
.focus();
this.onChange(change);
}
}
break;
*/
}
};
@ -555,7 +562,7 @@ export default class MessageComposerInput extends React.Component {
}
}
if (this.props.onInputStateChanged) {
if (this.props.onInputStateChanged && editorState.blocks.size > 0) {
let blockType = editorState.blocks.first().type;
// console.log("onInputStateChanged; current block type is " + blockType + " and marks are " + editorState.activeMarks);
@ -740,17 +747,31 @@ export default class MessageComposerInput extends React.Component {
.unwrapBlock('numbered-list');
return change;
}
else if (editorState.anchorOffset == 0 &&
(this.hasBlock('block-quote') ||
this.hasBlock('heading1') ||
this.hasBlock('heading2') ||
this.hasBlock('heading3') ||
this.hasBlock('heading4') ||
this.hasBlock('heading5') ||
this.hasBlock('heading6') ||
this.hasBlock('code-block')))
{
return change.setBlocks(DEFAULT_NODE);
else if (editorState.anchorOffset == 0 && editorState.isCollapsed) {
// turn blocks back into paragraphs
if ((this.hasBlock('block-quote') ||
this.hasBlock('heading1') ||
this.hasBlock('heading2') ||
this.hasBlock('heading3') ||
this.hasBlock('heading4') ||
this.hasBlock('heading5') ||
this.hasBlock('heading6') ||
this.hasBlock('code-block')))
{
return change.setBlocks(DEFAULT_NODE);
}
// remove paragraphs entirely if they're nested
const parent = editorState.document.getParent(editorState.anchorBlock.key);
if (editorState.anchorOffset == 0 &&
this.hasBlock('paragraph') &&
parent.nodes.size == 1 &&
parent.object !== 'document')
{
return change.replaceNodeByKey(editorState.anchorBlock.key, editorState.anchorText)
.collapseToEndOf(parent)
.focus();
}
}
}
return;
@ -1013,7 +1034,7 @@ export default class MessageComposerInput extends React.Component {
if (!shouldSendHTML) {
shouldSendHTML = !!editorState.document.findDescendant(node => {
// N.B. node.getMarks() might be private?
return ((node.object === 'block' && node.type !== 'line') ||
return ((node.object === 'block' && node.type !== 'paragraph') ||
(node.object === 'inline') ||
(node.object === 'text' && node.getMarks().size > 0));
});
@ -1131,13 +1152,13 @@ export default class MessageComposerInput extends React.Component {
// heuristic to handle tall emoji, pills, etc pushing the cursor away from the top
// or bottom of the page.
// XXX: is this going to break on large inline images or top-to-bottom scripts?
const EDGE_THRESHOLD = 8;
const EDGE_THRESHOLD = 15;
let navigateHistory = false;
if (up) {
const scrollCorrection = editorNode.scrollTop;
const distanceFromTop = cursorRect.top - editorRect.top + scrollCorrection;
//console.log(`Cursor distance from editor top is ${distanceFromTop}`);
console.log(`Cursor distance from editor top is ${distanceFromTop}`);
if (distanceFromTop < EDGE_THRESHOLD) {
navigateHistory = true;
}
@ -1146,7 +1167,7 @@ export default class MessageComposerInput extends React.Component {
const scrollCorrection =
editorNode.scrollHeight - editorNode.clientHeight - editorNode.scrollTop;
const distanceFromBottom = editorRect.bottom - cursorRect.bottom + scrollCorrection;
//console.log(`Cursor distance from editor bottom is ${distanceFromBottom}`);
console.log(`Cursor distance from editor bottom is ${distanceFromBottom}`);
if (distanceFromBottom < EDGE_THRESHOLD) {
navigateHistory = true;
}