make RTE sending work
This commit is contained in:
parent
4972a234c4
commit
167742d900
3 changed files with 85 additions and 69 deletions
|
@ -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 }
|
||||||
|
|
|
@ -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)) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue