make RTE sending work

This commit is contained in:
Matthew Hodgson 2018-05-19 20:28:38 +01:00
parent 4972a234c4
commit 167742d900
3 changed files with 85 additions and 69 deletions

View file

@ -91,6 +91,15 @@ limitations under the License.
overflow: auto; overflow: auto;
} }
// FIXME: rather unpleasant hack to get rid of <p/> margins.
// really we should be mixing in markdown-body from gfm.css instead
.mx_MessageComposer_editor > :first-child {
margin-top: 0 ! important;
}
.mx_MessageComposer_editor > :last-child {
margin-bottom: 0 ! important;
}
@keyframes visualbell @keyframes visualbell
{ {
from { background-color: #faa } from { background-color: #faa }

View file

@ -61,14 +61,6 @@ export function stateToMarkdown(state) {
''); // this is *not* a zero width space, trust me :) ''); // this is *not* a zero width space, trust me :)
} }
export const editorStateToHTML = (editorState: Value) => {
return Html.deserialize(editorState);
}
export function htmlToEditorState(html: string): Value {
return Html.serialize(html);
}
export function unicodeToEmojiUri(str) { export function unicodeToEmojiUri(str) {
let replaceWith, unicode, alt; let replaceWith, unicode, alt;
if ((!emojione.unicodeAlt) || (emojione.sprites)) { if ((!emojione.unicodeAlt) || (emojione.sprites)) {

View file

@ -145,7 +145,26 @@ export default class MessageComposerInput extends React.Component {
this.plainWithIdPills = new PlainWithPillsSerializer({ pillFormat: 'id' }); this.plainWithIdPills = new PlainWithPillsSerializer({ pillFormat: 'id' });
this.plainWithPlainPills = new PlainWithPillsSerializer({ pillFormat: 'plain' }); this.plainWithPlainPills = new PlainWithPillsSerializer({ pillFormat: 'plain' });
this.md = new Md(); this.md = new Md();
//this.html = new Html(); // not used atm this.html = new Html({
rules: [
{
serialize: (obj, children) => {
if (obj.object === 'block' || obj.object === 'inline') {
return this.renderNode({
node: obj,
children: children,
});
}
else if (obj.object === 'mark') {
return this.renderMark({
mark: obj,
children: children,
});
}
}
}
]
});
this.suppressAutoComplete = false; this.suppressAutoComplete = false;
this.direction = ''; this.direction = '';
@ -397,6 +416,7 @@ export default class MessageComposerInput extends React.Component {
editorState = EditorState.forceSelection(editorState, currentSelection); editorState = EditorState.forceSelection(editorState, currentSelection);
} }
*/ */
if (editorState.startText !== null) {
const text = editorState.startText.text; const text = editorState.startText.text;
const currentStartOffset = editorState.startOffset; const currentStartOffset = editorState.startOffset;
@ -420,6 +440,7 @@ export default class MessageComposerInput extends React.Component {
editorState = change.value; editorState = change.value;
} }
} }
}
// Record the editor state for this room so that it can be retrieved after // Record the editor state for this room so that it can be retrieved after
// switching to another room and back // switching to another room and back
@ -444,13 +465,15 @@ export default class MessageComposerInput extends React.Component {
let editorState = null; let editorState = null;
if (enabled) { if (enabled) {
// for simplicity when roundtripping, we use slate-md-serializer rather than commonmark
editorState = this.md.deserialize(this.plainWithMdPills.serialize(this.state.editorState));
// the alternative would be something like:
//
// const sourceWithPills = this.plainWithMdPills.serialize(this.state.editorState); // const sourceWithPills = this.plainWithMdPills.serialize(this.state.editorState);
// const markdown = new Markdown(sourceWithPills); // const markdown = new Markdown(sourceWithPills);
// editorState = this.html.deserialize(markdown.toHTML()); // editorState = this.html.deserialize(markdown.toHTML());
// we don't really want a custom MD parser hanging around, but the
// alternative would be:
editorState = this.md.deserialize(this.plainWithMdPills.serialize(this.state.editorState));
} else { } else {
// let markdown = RichText.stateToMarkdown(this.state.editorState.getCurrentContent()); // let markdown = RichText.stateToMarkdown(this.state.editorState.getCurrentContent());
// value = ContentState.createFromText(markdown); // value = ContentState.createFromText(markdown);
@ -547,6 +570,8 @@ export default class MessageComposerInput extends React.Component {
let newState: ?Value = null; let newState: ?Value = null;
const DEFAULT_NODE = 'paragraph';
// Draft handles rich text mode commands by default but we need to do it ourselves for Markdown. // Draft handles rich text mode commands by default but we need to do it ourselves for Markdown.
if (this.state.isRichtextEnabled) { if (this.state.isRichtextEnabled) {
const type = command; const type = command;
@ -725,11 +750,14 @@ export default class MessageComposerInput extends React.Component {
*/ */
handleReturn = (ev) => { handleReturn = (ev) => {
if (ev.shiftKey) { if (ev.shiftKey) {
// FIXME: we should insert a <br/> equivalent rather than letting Slate
// split the current block, otherwise <p> will be split into two paragraphs
// and it'll look like a double line-break.
return; return;
} }
if (this.state.editorState.blocks.some( if (this.state.editorState.blocks.some(
block => block in ['code-block', 'block-quote', 'bulleted-list', 'numbered-list'] block => ['code-block', 'block-quote', 'list-item'].includes(block.type)
)) { )) {
// allow the user to terminate blocks by hitting return rather than sending a msg // allow the user to terminate blocks by hitting return rather than sending a msg
return; return;
@ -788,47 +816,25 @@ export default class MessageComposerInput extends React.Component {
const mustSendHTML = Boolean(replyingToEv); const mustSendHTML = Boolean(replyingToEv);
if (this.state.isRichtextEnabled) { if (this.state.isRichtextEnabled) {
/*
// We should only send HTML if any block is styled or contains inline style // We should only send HTML if any block is styled or contains inline style
let shouldSendHTML = false; let shouldSendHTML = false;
if (mustSendHTML) shouldSendHTML = true; if (mustSendHTML) shouldSendHTML = true;
const blocks = contentState.getBlocksAsArray();
if (blocks.some((block) => block.getType() !== 'unstyled')) {
shouldSendHTML = true;
} else {
const characterLists = blocks.map((block) => block.getCharacterList());
// For each block of characters, determine if any inline styles are applied
// and if yes, send HTML
characterLists.forEach((characters) => {
const numberOfStylesForCharacters = characters.map(
(character) => character.getStyle().toArray().length,
).toArray();
// If any character has more than 0 inline styles applied, send HTML
if (numberOfStylesForCharacters.some((styles) => styles > 0)) {
shouldSendHTML = true;
}
});
}
if (!shouldSendHTML) { if (!shouldSendHTML) {
const hasLink = blocks.some((block) => { shouldSendHTML = !!editorState.document.findDescendant(node => {
return block.getCharacterList().filter((c) => { // N.B. node.getMarks() might be private?
const entityKey = c.getEntity(); return ((node.object === 'block' && node.type !== 'line') ||
return entityKey && contentState.getEntity(entityKey).getType() === 'LINK'; (node.object === 'inline') ||
}).size > 0; (node.object === 'text' && node.getMarks().size > 0));
}); });
shouldSendHTML = hasLink;
} }
*/
contentText = this.plainWithPlainPills.serialize(editorState); contentText = this.plainWithPlainPills.serialize(editorState);
if (contentText === '') return true; if (contentText === '') return true;
let shouldSendHTML = true;
if (shouldSendHTML) { if (shouldSendHTML) {
contentHTML = HtmlUtils.processHtmlForSending( contentHTML = this.html.serialize(editorState); // HtmlUtils.processHtmlForSending();
RichText.editorStateToHTML(editorState),
);
} }
} else { } else {
const sourceWithPills = this.plainWithMdPills.serialize(editorState); const sourceWithPills = this.plainWithMdPills.serialize(editorState);
@ -1047,7 +1053,7 @@ export default class MessageComposerInput extends React.Component {
marks: editorState.activeMarks, marks: editorState.activeMarks,
// XXX: shouldn't we return all the types of blocks in the current selection, // XXX: shouldn't we return all the types of blocks in the current selection,
// not just the anchor? // not just the anchor?
blockType: editorState.anchorBlock.type, blockType: editorState.anchorBlock ? editorState.anchorBlock.type : null,
}; };
} }
@ -1121,6 +1127,10 @@ export default class MessageComposerInput extends React.Component {
const { attributes, children, node, isSelected } = props; const { attributes, children, node, isSelected } = props;
switch (node.type) { switch (node.type) {
case 'line':
// ideally we'd return { children }<br/>, but as this isn't
// a valid react component, we don't have much choice.
return <div {...attributes}>{children}</div>;
case 'paragraph': case 'paragraph':
return <p {...attributes}>{children}</p>; return <p {...attributes}>{children}</p>;
case 'block-quote': case 'block-quote':
@ -1138,7 +1148,7 @@ export default class MessageComposerInput extends React.Component {
case 'numbered-list': case 'numbered-list':
return <ol {...attributes}>{children}</ol>; return <ol {...attributes}>{children}</ol>;
case 'code-block': case 'code-block':
return <p {...attributes}><code {...attributes}>{children}</code></p>; return <pre {...attributes}><code {...attributes}>{children}</code></pre>;
case 'pill': { case 'pill': {
const { data } = node; const { data } = node;
const url = data.get('url'); const url = data.get('url');
@ -1187,15 +1197,15 @@ export default class MessageComposerInput extends React.Component {
const { children, mark, attributes } = props; const { children, mark, attributes } = props;
switch (mark.type) { switch (mark.type) {
case 'bold': case 'bold':
return <strong {...{ attributes }}>{children}</strong>; return <strong {...attributes}>{children}</strong>;
case 'italic': case 'italic':
return <em {...{ attributes }}>{children}</em>; return <em {...attributes}>{children}</em>;
case 'code': case 'code':
return <code {...{ attributes }}>{children}</code>; return <code {...attributes}>{children}</code>;
case 'underline': case 'underline':
return <u {...{ attributes }}>{children}</u>; return <u {...attributes}>{children}</u>;
case 'strikethrough': case 'strikethrough':
return <del {...{ attributes }}>{children}</del>; return <del {...attributes}>{children}</del>;
} }
}; };
@ -1219,8 +1229,13 @@ export default class MessageComposerInput extends React.Component {
// This avoids us having to serialize the whole thing to plaintext and convert // This avoids us having to serialize the whole thing to plaintext and convert
// selection offsets in & out of the plaintext domain. // selection offsets in & out of the plaintext domain.
if (editorState.selection.anchorKey) {
return editorState.document.getDescendant(editorState.selection.anchorKey).text; return editorState.document.getDescendant(editorState.selection.anchorKey).text;
} }
else {
return '';
}
}
getSelectionRange(editorState: Value) { getSelectionRange(editorState: Value) {
let beginning = false; let beginning = false;