Basic diff visualisation for plain text edits
This commit is contained in:
parent
4fa7302f69
commit
f305d8ac08
5 changed files with 56 additions and 4 deletions
|
@ -66,6 +66,7 @@
|
||||||
"classnames": "^2.1.2",
|
"classnames": "^2.1.2",
|
||||||
"commonmark": "^0.28.1",
|
"commonmark": "^0.28.1",
|
||||||
"counterpart": "^0.18.0",
|
"counterpart": "^0.18.0",
|
||||||
|
"diff-match-patch": "^1.0.4",
|
||||||
"emojibase-data": "^4.0.0",
|
"emojibase-data": "^4.0.0",
|
||||||
"emojibase-regex": "^3.0.0",
|
"emojibase-regex": "^3.0.0",
|
||||||
"file-saver": "^1.3.3",
|
"file-saver": "^1.3.3",
|
||||||
|
|
|
@ -39,6 +39,20 @@ limitations under the License.
|
||||||
padding: 0;
|
padding: 0;
|
||||||
color: $primary-fg-color;
|
color: $primary-fg-color;
|
||||||
|
|
||||||
|
span.mx_EditHistoryMessage_deletion, span.mx_EditHistoryMessage_insertion {
|
||||||
|
padding: 0px 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.mx_EditHistoryMessage_deletion {
|
||||||
|
color: rgb(255, 76, 85);
|
||||||
|
background-color: rgba(255, 76, 85, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
span.mx_EditHistoryMessage_insertion {
|
||||||
|
color: rgb(26, 169, 123);
|
||||||
|
background-color: rgba(26, 169, 123, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
.mx_EventTile_line, .mx_EventTile_content {
|
.mx_EventTile_line, .mx_EventTile_content {
|
||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,7 +108,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent {
|
||||||
allEvents = allEvents.concat(this.state.originalEvent);
|
allEvents = allEvents.concat(this.state.originalEvent);
|
||||||
}
|
}
|
||||||
const baseEventId = this.props.mxEvent.getId();
|
const baseEventId = this.props.mxEvent.getId();
|
||||||
allEvents.forEach(e => {
|
allEvents.forEach((e, i) => {
|
||||||
if (!lastEvent || wantsDateSeparator(lastEvent.getDate(), e.getDate())) {
|
if (!lastEvent || wantsDateSeparator(lastEvent.getDate(), e.getDate())) {
|
||||||
nodes.push(<li key={e.getTs() + "~"}><DateSeparator ts={e.getTs()} /></li>);
|
nodes.push(<li key={e.getTs() + "~"}><DateSeparator ts={e.getTs()} /></li>);
|
||||||
}
|
}
|
||||||
|
@ -116,6 +116,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent {
|
||||||
nodes.push((
|
nodes.push((
|
||||||
<EditHistoryMessage
|
<EditHistoryMessage
|
||||||
key={e.getId()}
|
key={e.getId()}
|
||||||
|
previousEdit={!isBaseEvent && allEvents[i + 1]}
|
||||||
isBaseEvent={isBaseEvent}
|
isBaseEvent={isBaseEvent}
|
||||||
mxEvent={e}
|
mxEvent={e}
|
||||||
isTwelveHour={this.state.isTwelveHour}
|
isTwelveHour={this.state.isTwelveHour}
|
||||||
|
|
|
@ -25,6 +25,17 @@ import sdk from '../../../index';
|
||||||
import MatrixClientPeg from '../../../MatrixClientPeg';
|
import MatrixClientPeg from '../../../MatrixClientPeg';
|
||||||
import Modal from '../../../Modal';
|
import Modal from '../../../Modal';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import DiffMatchPatch from 'diff-match-patch';
|
||||||
|
|
||||||
|
function getReplacedContent(event) {
|
||||||
|
const originalContent = event.getOriginalContent();
|
||||||
|
return originalContent["m.new_content"] || originalContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlainMessage(event) {
|
||||||
|
const content = getReplacedContent(event);
|
||||||
|
return content.msgtype === "m.text" && !content.format;
|
||||||
|
}
|
||||||
|
|
||||||
export default class EditHistoryMessage extends React.PureComponent {
|
export default class EditHistoryMessage extends React.PureComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
@ -115,16 +126,36 @@ export default class EditHistoryMessage extends React.PureComponent {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_diffIt(oldBody, newBody) {
|
||||||
|
const dpm = new DiffMatchPatch();
|
||||||
|
const diff = dpm.diff_main(oldBody, newBody);
|
||||||
|
dpm.diff_cleanupSemantic(diff);
|
||||||
|
return diff.map(([modifier, text], i) => {
|
||||||
|
// not using del and ins tags here as del is used for strikethrough
|
||||||
|
if (modifier < 0) {
|
||||||
|
return (<span className="mx_EditHistoryMessage_deletion" key={i}>{text}</span>);
|
||||||
|
} else if (modifier > 0) {
|
||||||
|
return (<span className="mx_EditHistoryMessage_insertion" key={i}>{text}</span>);
|
||||||
|
} else {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {mxEvent} = this.props;
|
const {mxEvent} = this.props;
|
||||||
const originalContent = mxEvent.getOriginalContent();
|
const content = getReplacedContent(mxEvent);
|
||||||
const content = originalContent["m.new_content"] || originalContent;
|
|
||||||
let contentContainer;
|
let contentContainer;
|
||||||
if (mxEvent.isRedacted()) {
|
if (mxEvent.isRedacted()) {
|
||||||
const UnknownBody = sdk.getComponent('messages.UnknownBody');
|
const UnknownBody = sdk.getComponent('messages.UnknownBody');
|
||||||
contentContainer = <UnknownBody mxEvent={this.props.mxEvent} />;
|
contentContainer = <UnknownBody mxEvent={this.props.mxEvent} />;
|
||||||
} else {
|
} else {
|
||||||
const contentElements = HtmlUtils.bodyToHtml(content, null, {stripReplyFallback: true});
|
let contentElements;
|
||||||
|
if (isPlainMessage(mxEvent) && this.props.previousEdit && isPlainMessage(this.props.previousEdit)) {
|
||||||
|
contentElements = this._diffIt(getReplacedContent(this.props.previousEdit).body, content.body);
|
||||||
|
} else {
|
||||||
|
contentElements = HtmlUtils.bodyToHtml(content, null, {stripReplyFallback: true});
|
||||||
|
}
|
||||||
if (mxEvent.getContent().msgtype === "m.emote") {
|
if (mxEvent.getContent().msgtype === "m.emote") {
|
||||||
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
|
const name = mxEvent.sender ? mxEvent.sender.name : mxEvent.getSender();
|
||||||
contentContainer = (
|
contentContainer = (
|
||||||
|
|
|
@ -2464,6 +2464,11 @@ di@^0.0.1:
|
||||||
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
|
resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c"
|
||||||
integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=
|
integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=
|
||||||
|
|
||||||
|
diff-match-patch@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/diff-match-patch/-/diff-match-patch-1.0.4.tgz#6ac4b55237463761c4daf0dc603eb869124744b1"
|
||||||
|
integrity sha512-Uv3SW8bmH9nAtHKaKSanOQmj2DnlH65fUpcrMdfdaOxUG02QQ4YGZ8AE7kKOMisF7UqvOlGKVYWRvezdncW9lg==
|
||||||
|
|
||||||
diff-sequences@^24.3.0:
|
diff-sequences@^24.3.0:
|
||||||
version "24.3.0"
|
version "24.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
|
resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975"
|
||||||
|
|
Loading…
Reference in a new issue