make quoting work
parent
4439a04689
commit
7de45f8b7b
|
@ -403,19 +403,22 @@ class TextHighlighter extends BaseHighlighter {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* turn a matrix event body into html
|
/* turn a matrix event body into html
|
||||||
*
|
*
|
||||||
* content: 'content' of the MatrixEvent
|
* content: 'content' of the MatrixEvent
|
||||||
*
|
*
|
||||||
* highlights: optional list of words to highlight, ordered by longest word first
|
* highlights: optional list of words to highlight, ordered by longest word first
|
||||||
*
|
*
|
||||||
* opts.highlightLink: optional href to add to highlighted words
|
* opts.highlightLink: optional href to add to highlighted words
|
||||||
* opts.disableBigEmoji: optional argument to disable the big emoji class.
|
* 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.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={}) {
|
export function bodyToHtml(content, highlights, opts={}) {
|
||||||
const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body;
|
const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body;
|
||||||
|
|
||||||
|
const doEmojiOne = opts.emojiOne === undefined ? true : opts.emojiOne;
|
||||||
let bodyHasEmoji = false;
|
let bodyHasEmoji = false;
|
||||||
|
|
||||||
let strippedBody;
|
let strippedBody;
|
||||||
|
@ -441,8 +444,9 @@ export function bodyToHtml(content, highlights, opts={}) {
|
||||||
if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyThread.stripHTMLReply(formattedBody);
|
if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyThread.stripHTMLReply(formattedBody);
|
||||||
strippedBody = opts.stripReplyFallback ? ReplyThread.stripPlainReply(content.body) : content.body;
|
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
|
// Only generate safeBody if the message was sent as org.matrix.custom.html
|
||||||
if (isHtmlMessage) {
|
if (isHtmlMessage) {
|
||||||
|
@ -467,6 +471,10 @@ export function bodyToHtml(content, highlights, opts={}) {
|
||||||
delete sanitizeHtmlParams.textFilter;
|
delete sanitizeHtmlParams.textFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (opts.returnString) {
|
||||||
|
return isDisplayedWithHtml ? safeBody : strippedBody;
|
||||||
|
}
|
||||||
|
|
||||||
let emojiBody = false;
|
let emojiBody = false;
|
||||||
if (!opts.disableBigEmoji && bodyHasEmoji) {
|
if (!opts.disableBigEmoji && bodyHasEmoji) {
|
||||||
EMOJI_REGEX.lastIndex = 0;
|
EMOJI_REGEX.lastIndex = 0;
|
||||||
|
|
|
@ -179,7 +179,7 @@ module.exports = React.createClass({
|
||||||
onQuoteClick: function() {
|
onQuoteClick: function() {
|
||||||
dis.dispatch({
|
dis.dispatch({
|
||||||
action: 'quote',
|
action: 'quote',
|
||||||
text: this.props.eventTileOps.getInnerText(),
|
event: this.props.mxEvent,
|
||||||
});
|
});
|
||||||
this.closeMenu();
|
this.closeMenu();
|
||||||
},
|
},
|
||||||
|
|
|
@ -21,7 +21,7 @@ import type SyntheticKeyboardEvent from 'react/lib/SyntheticKeyboardEvent';
|
||||||
|
|
||||||
import { Editor } from 'slate-react';
|
import { Editor } from 'slate-react';
|
||||||
import { getEventTransfer } 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 Html from 'slate-html-serializer';
|
||||||
import Md from 'slate-md-serializer';
|
import Md from 'slate-md-serializer';
|
||||||
|
@ -342,37 +342,44 @@ export default class MessageComposerInput extends React.Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
/*
|
case 'quote': {
|
||||||
case 'quote': { // old quoting, whilst rich quoting is in labs
|
const html = HtmlUtils.bodyToHtml(payload.event.getContent(), null, {
|
||||||
/// XXX: Not doing rich-text quoting from formatted-body because draft-js
|
returnString: true,
|
||||||
/// has regressed such that when links are quoted, errors are thrown. See
|
emojiOne: false,
|
||||||
/// https://github.com/vector-im/riot-web/issues/4756.
|
});
|
||||||
const body = escape(payload.text);
|
const fragment = this.html.deserialize(html);
|
||||||
if (body) {
|
// FIXME: do we want to put in a permalink to the original quote here?
|
||||||
let content = RichText.htmlToContentState(`<blockquote>${body}</blockquote>`);
|
// If so, what should be the format, and how do we differentiate it from replies?
|
||||||
if (!this.state.isRichTextEnabled) {
|
|
||||||
content = ContentState.createFromText(RichText.stateToMarkdown(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
const blockMap = content.getBlockMap();
|
const quote = Block.create('block-quote');
|
||||||
let startSelection = SelectionState.createEmpty(contentState.getFirstBlock().getKey());
|
if (this.state.isRichTextEnabled) {
|
||||||
contentState = Modifier.splitBlock(contentState, startSelection);
|
let change = editorState.change();
|
||||||
startSelection = SelectionState.createEmpty(contentState.getFirstBlock().getKey());
|
if (editorState.anchorText.text === '' && editorState.anchorBlock.nodes.size === 1) {
|
||||||
contentState = Modifier.replaceWithFragment(contentState,
|
// replace the current block rather than split the block
|
||||||
startSelection,
|
change = change.replaceNodeByKey(editorState.anchorBlock.key, quote);
|
||||||
blockMap);
|
|
||||||
startSelection = SelectionState.createEmpty(contentState.getFirstBlock().getKey());
|
|
||||||
if (this.state.isRichTextEnabled) {
|
|
||||||
contentState = Modifier.setBlockType(contentState, startSelection, 'blockquote');
|
|
||||||
}
|
}
|
||||||
let editorState = EditorState.push(this.state.editorState, contentState, 'insert-characters');
|
else {
|
||||||
editorState = EditorState.moveSelectionToEnd(editorState);
|
// insert it into the middle of the block (splitting it)
|
||||||
this.onEditorContentChanged(editorState);
|
change = change.insertBlock(quote);
|
||||||
editor.focus();
|
}
|
||||||
|
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;
|
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;
|
let blockType = editorState.blocks.first().type;
|
||||||
// console.log("onInputStateChanged; current block type is " + blockType + " and marks are " + editorState.activeMarks);
|
// 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');
|
.unwrapBlock('numbered-list');
|
||||||
return change;
|
return change;
|
||||||
}
|
}
|
||||||
else if (editorState.anchorOffset == 0 &&
|
else if (editorState.anchorOffset == 0 && editorState.isCollapsed) {
|
||||||
(this.hasBlock('block-quote') ||
|
// turn blocks back into paragraphs
|
||||||
this.hasBlock('heading1') ||
|
if ((this.hasBlock('block-quote') ||
|
||||||
this.hasBlock('heading2') ||
|
this.hasBlock('heading1') ||
|
||||||
this.hasBlock('heading3') ||
|
this.hasBlock('heading2') ||
|
||||||
this.hasBlock('heading4') ||
|
this.hasBlock('heading3') ||
|
||||||
this.hasBlock('heading5') ||
|
this.hasBlock('heading4') ||
|
||||||
this.hasBlock('heading6') ||
|
this.hasBlock('heading5') ||
|
||||||
this.hasBlock('code-block')))
|
this.hasBlock('heading6') ||
|
||||||
{
|
this.hasBlock('code-block')))
|
||||||
return change.setBlocks(DEFAULT_NODE);
|
{
|
||||||
|
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;
|
return;
|
||||||
|
@ -1013,7 +1034,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
if (!shouldSendHTML) {
|
if (!shouldSendHTML) {
|
||||||
shouldSendHTML = !!editorState.document.findDescendant(node => {
|
shouldSendHTML = !!editorState.document.findDescendant(node => {
|
||||||
// N.B. node.getMarks() might be private?
|
// 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 === 'inline') ||
|
||||||
(node.object === 'text' && node.getMarks().size > 0));
|
(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
|
// heuristic to handle tall emoji, pills, etc pushing the cursor away from the top
|
||||||
// or bottom of the page.
|
// or bottom of the page.
|
||||||
// XXX: is this going to break on large inline images or top-to-bottom scripts?
|
// 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;
|
let navigateHistory = false;
|
||||||
if (up) {
|
if (up) {
|
||||||
const scrollCorrection = editorNode.scrollTop;
|
const scrollCorrection = editorNode.scrollTop;
|
||||||
const distanceFromTop = cursorRect.top - editorRect.top + scrollCorrection;
|
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) {
|
if (distanceFromTop < EDGE_THRESHOLD) {
|
||||||
navigateHistory = true;
|
navigateHistory = true;
|
||||||
}
|
}
|
||||||
|
@ -1146,7 +1167,7 @@ export default class MessageComposerInput extends React.Component {
|
||||||
const scrollCorrection =
|
const scrollCorrection =
|
||||||
editorNode.scrollHeight - editorNode.clientHeight - editorNode.scrollTop;
|
editorNode.scrollHeight - editorNode.clientHeight - editorNode.scrollTop;
|
||||||
const distanceFromBottom = editorRect.bottom - cursorRect.bottom + scrollCorrection;
|
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) {
|
if (distanceFromBottom < EDGE_THRESHOLD) {
|
||||||
navigateHistory = true;
|
navigateHistory = true;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue