mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-17 22:14:58 +08:00
Merge pull request #3192 from matrix-org/bwindels/preserve-reply-fallback-on-edit
Preserve reply fallback on edit
This commit is contained in:
commit
cd803617b8
@ -33,6 +33,80 @@ import {MatrixClient} from 'matrix-js-sdk';
|
|||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import {EventStatus} from 'matrix-js-sdk';
|
import {EventStatus} from 'matrix-js-sdk';
|
||||||
|
|
||||||
|
function _isReply(mxEvent) {
|
||||||
|
const relatesTo = mxEvent.getContent()["m.relates_to"];
|
||||||
|
const isReply = !!(relatesTo && relatesTo["m.in_reply_to"]);
|
||||||
|
return isReply;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getHtmlReplyFallback(mxEvent) {
|
||||||
|
const html = mxEvent.getContent().formatted_body;
|
||||||
|
if (!html) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const rootNode = new DOMParser().parseFromString(html, "text/html").body;
|
||||||
|
const mxReply = rootNode.querySelector("mx-reply");
|
||||||
|
return (mxReply && mxReply.outerHTML) || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTextReplyFallback(mxEvent) {
|
||||||
|
const body = mxEvent.getContent().body;
|
||||||
|
const lines = body.split("\n").map(l => l.trim());
|
||||||
|
if (lines.length > 2 && lines[0].startsWith("> ") && lines[1].length === 0) {
|
||||||
|
return `${lines[0]}\n\n`;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
function _isEmote(model) {
|
||||||
|
const firstPart = model.parts[0];
|
||||||
|
return firstPart && firstPart.type === "plain" && firstPart.text.startsWith("/me ");
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEditContent(model, editedEvent) {
|
||||||
|
const isEmote = _isEmote(model);
|
||||||
|
if (isEmote) {
|
||||||
|
// trim "/me "
|
||||||
|
model = model.clone();
|
||||||
|
model.removeText({index: 0, offset: 0}, 4);
|
||||||
|
}
|
||||||
|
const isReply = _isReply(editedEvent);
|
||||||
|
let plainPrefix = "";
|
||||||
|
let htmlPrefix = "";
|
||||||
|
|
||||||
|
if (isReply) {
|
||||||
|
plainPrefix = getTextReplyFallback(editedEvent);
|
||||||
|
htmlPrefix = getHtmlReplyFallback(editedEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = textSerialize(model);
|
||||||
|
|
||||||
|
const newContent = {
|
||||||
|
"msgtype": isEmote ? "m.emote" : "m.text",
|
||||||
|
"body": plainPrefix + body,
|
||||||
|
};
|
||||||
|
const contentBody = {
|
||||||
|
msgtype: newContent.msgtype,
|
||||||
|
body: `${plainPrefix} * ${body}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
const formattedBody = htmlSerializeIfNeeded(model, {forceHTML: isReply});
|
||||||
|
if (formattedBody) {
|
||||||
|
newContent.format = "org.matrix.custom.html";
|
||||||
|
newContent.formatted_body = htmlPrefix + formattedBody;
|
||||||
|
contentBody.format = newContent.format;
|
||||||
|
contentBody.formatted_body = `${htmlPrefix} * ${formattedBody}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.assign({
|
||||||
|
"m.new_content": newContent,
|
||||||
|
"m.relates_to": {
|
||||||
|
"rel_type": "m.replace",
|
||||||
|
"event_id": editedEvent.getId(),
|
||||||
|
},
|
||||||
|
}, contentBody);
|
||||||
|
}
|
||||||
|
|
||||||
export default class MessageEditor extends React.Component {
|
export default class MessageEditor extends React.Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
// the message event being edited
|
// the message event being edited
|
||||||
@ -53,7 +127,7 @@ export default class MessageEditor extends React.Component {
|
|||||||
};
|
};
|
||||||
this._editorRef = null;
|
this._editorRef = null;
|
||||||
this._autocompleteRef = null;
|
this._autocompleteRef = null;
|
||||||
this._hasModifications = false;
|
this._modifiedFlag = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getRoom() {
|
_getRoom() {
|
||||||
@ -73,7 +147,7 @@ export default class MessageEditor extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onInput = (event) => {
|
_onInput = (event) => {
|
||||||
this._hasModifications = true;
|
this._modifiedFlag = true;
|
||||||
const sel = document.getSelection();
|
const sel = document.getSelection();
|
||||||
const {caret, text} = getCaretOffsetAndText(this._editorRef, sel);
|
const {caret, text} = getCaretOffsetAndText(this._editorRef, sel);
|
||||||
this.model.update(text, event.inputType, caret);
|
this.model.update(text, event.inputType, caret);
|
||||||
@ -131,7 +205,7 @@ export default class MessageEditor extends React.Component {
|
|||||||
} else if (event.key === "Escape") {
|
} else if (event.key === "Escape") {
|
||||||
this._cancelEdit();
|
this._cancelEdit();
|
||||||
} else if (event.key === "ArrowUp") {
|
} else if (event.key === "ArrowUp") {
|
||||||
if (this._hasModifications || !this._isCaretAtStart()) {
|
if (this._modifiedFlag || !this._isCaretAtStart()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const previousEvent = findEditableEvent(this._getRoom(), false, this.props.editState.getEvent().getId());
|
const previousEvent = findEditableEvent(this._getRoom(), false, this.props.editState.getEvent().getId());
|
||||||
@ -140,7 +214,7 @@ export default class MessageEditor extends React.Component {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
}
|
||||||
} else if (event.key === "ArrowDown") {
|
} else if (event.key === "ArrowDown") {
|
||||||
if (this._hasModifications || !this._isCaretAtEnd()) {
|
if (this._modifiedFlag || !this._isCaretAtEnd()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nextEvent = findEditableEvent(this._getRoom(), true, this.props.editState.getEvent().getId());
|
const nextEvent = findEditableEvent(this._getRoom(), true, this.props.editState.getEvent().getId());
|
||||||
@ -159,56 +233,28 @@ export default class MessageEditor extends React.Component {
|
|||||||
dis.dispatch({action: 'focus_composer'});
|
dis.dispatch({action: 'focus_composer'});
|
||||||
}
|
}
|
||||||
|
|
||||||
_isEmote() {
|
_hasModifications(newContent) {
|
||||||
const firstPart = this.model.parts[0];
|
|
||||||
return firstPart && firstPart.type === "plain" && firstPart.text.startsWith("/me ");
|
|
||||||
}
|
|
||||||
|
|
||||||
_sendEdit = () => {
|
|
||||||
const isEmote = this._isEmote();
|
|
||||||
let model = this.model;
|
|
||||||
if (isEmote) {
|
|
||||||
// trim "/me "
|
|
||||||
model = model.clone();
|
|
||||||
model.removeText({index: 0, offset: 0}, 4);
|
|
||||||
}
|
|
||||||
const newContent = {
|
|
||||||
"msgtype": isEmote ? "m.emote" : "m.text",
|
|
||||||
"body": textSerialize(model),
|
|
||||||
};
|
|
||||||
const contentBody = {
|
|
||||||
msgtype: newContent.msgtype,
|
|
||||||
body: ` * ${newContent.body}`,
|
|
||||||
};
|
|
||||||
const formattedBody = htmlSerializeIfNeeded(model);
|
|
||||||
if (formattedBody) {
|
|
||||||
newContent.format = "org.matrix.custom.html";
|
|
||||||
newContent.formatted_body = formattedBody;
|
|
||||||
contentBody.format = newContent.format;
|
|
||||||
contentBody.formatted_body = ` * ${newContent.formatted_body}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if nothing has changed then bail
|
// if nothing has changed then bail
|
||||||
const oldContent = this.props.editState.getEvent().getContent();
|
const oldContent = this.props.editState.getEvent().getContent();
|
||||||
if (!this._hasModifications ||
|
if (!this._modifiedFlag ||
|
||||||
(oldContent["msgtype"] === newContent["msgtype"] && oldContent["body"] === newContent["body"] &&
|
(oldContent["msgtype"] === newContent["msgtype"] && oldContent["body"] === newContent["body"] &&
|
||||||
oldContent["format"] === newContent["format"] &&
|
oldContent["format"] === newContent["format"] &&
|
||||||
oldContent["formatted_body"] === newContent["formatted_body"])) {
|
oldContent["formatted_body"] === newContent["formatted_body"])) {
|
||||||
this._cancelEdit();
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sendEdit = () => {
|
||||||
|
const editedEvent = this.props.editState.getEvent();
|
||||||
|
const editContent = createEditContent(this.model, editedEvent);
|
||||||
|
const newContent = editContent["m.new_content"];
|
||||||
|
if (!this._hasModifications(newContent)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const roomId = editedEvent.getRoomId();
|
||||||
const content = Object.assign({
|
|
||||||
"m.new_content": newContent,
|
|
||||||
"m.relates_to": {
|
|
||||||
"rel_type": "m.replace",
|
|
||||||
"event_id": this.props.editState.getEvent().getId(),
|
|
||||||
},
|
|
||||||
}, contentBody);
|
|
||||||
|
|
||||||
const roomId = this.props.editState.getEvent().getRoomId();
|
|
||||||
this._cancelPreviousPendingEdit();
|
this._cancelPreviousPendingEdit();
|
||||||
this.context.matrixClient.sendMessage(roomId, content);
|
this.context.matrixClient.sendMessage(roomId, editContent);
|
||||||
|
|
||||||
dis.dispatch({action: "edit_event", event: null});
|
dis.dispatch({action: "edit_event", event: null});
|
||||||
dis.dispatch({action: 'focus_composer'});
|
dis.dispatch({action: 'focus_composer'});
|
||||||
|
@ -119,7 +119,7 @@ export default class EditHistoryMessage extends React.PureComponent {
|
|||||||
const {mxEvent} = this.props;
|
const {mxEvent} = this.props;
|
||||||
const originalContent = mxEvent.getOriginalContent();
|
const originalContent = mxEvent.getOriginalContent();
|
||||||
const content = originalContent["m.new_content"] || originalContent;
|
const content = originalContent["m.new_content"] || originalContent;
|
||||||
const contentElements = HtmlUtils.bodyToHtml(content);
|
const contentElements = HtmlUtils.bodyToHtml(content, null, {stripReplyFallback: true});
|
||||||
let contentContainer;
|
let contentContainer;
|
||||||
if (mxEvent.isRedacted()) {
|
if (mxEvent.isRedacted()) {
|
||||||
const UnknownBody = sdk.getComponent('messages.UnknownBody');
|
const UnknownBody = sdk.getComponent('messages.UnknownBody');
|
||||||
|
@ -33,10 +33,10 @@ export function mdSerialize(model) {
|
|||||||
}, "");
|
}, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function htmlSerializeIfNeeded(model) {
|
export function htmlSerializeIfNeeded(model, {forceHTML = false}) {
|
||||||
const md = mdSerialize(model);
|
const md = mdSerialize(model);
|
||||||
const parser = new Markdown(md);
|
const parser = new Markdown(md);
|
||||||
if (!parser.isPlainText()) {
|
if (!parser.isPlainText() || forceHTML) {
|
||||||
return parser.toHTML();
|
return parser.toHTML();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user