Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 177b76d

Browse files
authored
Merge pull request #5284 from matrix-org/t3chguy/fix/10353
Track replyToEvent along with Cider state & history
2 parents 513eb03 + b2d04de commit 177b76d

File tree

9 files changed

+1679
-711
lines changed

9 files changed

+1679
-711
lines changed

__test-utils__/environment.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
const BaseEnvironment = require("jest-environment-jsdom-sixteen");
2+
3+
class Environment extends BaseEnvironment {
4+
constructor(config, options) {
5+
super(Object.assign({}, config, {
6+
globals: Object.assign({}, config.globals, {
7+
// Explicitly specify the correct globals to workaround Jest bug
8+
// https://github.com/facebook/jest/issues/7780
9+
Uint32Array: Uint32Array,
10+
Uint8Array: Uint8Array,
11+
ArrayBuffer: ArrayBuffer,
12+
}),
13+
}), options);
14+
}
15+
}
16+
17+
module.exports = Environment;

package.json

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@
121121
"@babel/preset-typescript": "^7.10.4",
122122
"@babel/register": "^7.10.5",
123123
"@babel/traverse": "^7.11.0",
124-
"@peculiar/webcrypto": "^1.1.2",
124+
"@peculiar/webcrypto": "^1.1.3",
125125
"@types/classnames": "^2.2.10",
126126
"@types/counterpart": "^0.18.1",
127127
"@types/flux": "^3.1.9",
@@ -151,8 +151,9 @@
151151
"eslint-plugin-react": "^7.20.3",
152152
"eslint-plugin-react-hooks": "^2.5.1",
153153
"glob": "^5.0.15",
154-
"jest": "^24.9.0",
155-
"jest-canvas-mock": "^2.2.0",
154+
"jest": "^26.5.2",
155+
"jest-canvas-mock": "^2.3.0",
156+
"jest-environment-jsdom-sixteen": "^1.0.3",
156157
"lolex": "^5.1.2",
157158
"matrix-mock-request": "^1.2.3",
158159
"matrix-react-test-utils": "^0.2.2",
@@ -165,6 +166,7 @@
165166
"walk": "^2.3.14"
166167
},
167168
"jest": {
169+
"testEnvironment": "./__test-utils__/environment.js",
168170
"testMatch": [
169171
"<rootDir>/test/**/*-test.js"
170172
],

src/SendHistoryManager.js renamed to src/SendHistoryManager.ts

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,21 @@ limitations under the License.
1616
*/
1717

1818
import {clamp} from "lodash";
19+
import {MatrixEvent} from "matrix-js-sdk/src/models/event";
20+
21+
import {SerializedPart} from "./editor/parts";
22+
import EditorModel from "./editor/model";
23+
24+
interface IHistoryItem {
25+
parts: SerializedPart[];
26+
replyEventId?: string;
27+
}
1928

2029
export default class SendHistoryManager {
21-
history: Array<HistoryItem> = [];
30+
history: Array<IHistoryItem> = [];
2231
prefix: string;
23-
lastIndex: number = 0; // used for indexing the storage
24-
currentIndex: number = 0; // used for indexing the loaded validated history Array
32+
lastIndex = 0; // used for indexing the storage
33+
currentIndex = 0; // used for indexing the loaded validated history Array
2534

2635
constructor(roomId: string, prefix: string) {
2736
this.prefix = prefix + roomId;
@@ -32,8 +41,7 @@ export default class SendHistoryManager {
3241

3342
while (itemJSON = sessionStorage.getItem(`${this.prefix}[${index}]`)) {
3443
try {
35-
const serializedParts = JSON.parse(itemJSON);
36-
this.history.push(serializedParts);
44+
this.history.push(JSON.parse(itemJSON));
3745
} catch (e) {
3846
console.warn("Throwing away unserialisable history", e);
3947
break;
@@ -45,15 +53,22 @@ export default class SendHistoryManager {
4553
this.currentIndex = this.lastIndex + 1;
4654
}
4755

48-
save(editorModel: Object) {
49-
const serializedParts = editorModel.serializeParts();
50-
this.history.push(serializedParts);
56+
static createItem(model: EditorModel, replyEvent?: MatrixEvent): IHistoryItem {
57+
return {
58+
parts: model.serializeParts(),
59+
replyEventId: replyEvent ? replyEvent.getId() : undefined,
60+
};
61+
}
62+
63+
save(editorModel: EditorModel, replyEvent?: MatrixEvent) {
64+
const item = SendHistoryManager.createItem(editorModel, replyEvent);
65+
this.history.push(item);
5166
this.currentIndex = this.history.length;
5267
this.lastIndex += 1;
53-
sessionStorage.setItem(`${this.prefix}[${this.lastIndex}]`, JSON.stringify(serializedParts));
68+
sessionStorage.setItem(`${this.prefix}[${this.lastIndex}]`, JSON.stringify(item));
5469
}
5570

56-
getItem(offset: number): ?HistoryItem {
71+
getItem(offset: number): IHistoryItem {
5772
this.currentIndex = clamp(this.currentIndex + offset, 0, this.history.length - 1);
5873
return this.history[this.currentIndex];
5974
}

src/components/views/rooms/BasicMessageComposer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ interface IProps {
9292
label?: string;
9393
initialCaret?: DocumentOffset;
9494

95-
onChange();
95+
onChange?();
9696
onPaste?(event: ClipboardEvent<HTMLDivElement>, model: EditorModel): boolean;
9797
}
9898

src/components/views/rooms/MessageComposer.js

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ export default class MessageComposer extends React.Component {
257257
this._dispatcherRef = null;
258258

259259
this.state = {
260-
isQuoting: Boolean(RoomViewStore.getQuotingEvent()),
260+
replyToEvent: RoomViewStore.getQuotingEvent(),
261261
tombstone: this._getRoomTombstone(),
262262
canSendMessages: this.props.room.maySendMessage(),
263263
showCallButtons: SettingsStore.getValue("showCallButtonsInComposer"),
@@ -337,9 +337,9 @@ export default class MessageComposer extends React.Component {
337337
}
338338

339339
_onRoomViewStoreUpdate() {
340-
const isQuoting = Boolean(RoomViewStore.getQuotingEvent());
341-
if (this.state.isQuoting === isQuoting) return;
342-
this.setState({ isQuoting });
340+
const replyToEvent = RoomViewStore.getQuotingEvent();
341+
if (this.state.replyToEvent === replyToEvent) return;
342+
this.setState({ replyToEvent });
343343
}
344344

345345
onInputStateChanged(inputState) {
@@ -378,7 +378,7 @@ export default class MessageComposer extends React.Component {
378378
}
379379

380380
renderPlaceholderText() {
381-
if (this.state.isQuoting) {
381+
if (this.state.replyToEvent) {
382382
if (this.props.e2eStatus) {
383383
return _t('Send an encrypted reply…');
384384
} else {
@@ -423,7 +423,9 @@ export default class MessageComposer extends React.Component {
423423
room={this.props.room}
424424
placeholder={this.renderPlaceholderText()}
425425
resizeNotifier={this.props.resizeNotifier}
426-
permalinkCreator={this.props.permalinkCreator} />,
426+
permalinkCreator={this.props.permalinkCreator}
427+
replyToEvent={this.state.replyToEvent}
428+
/>,
427429
<UploadButton key="controls_upload" roomId={this.props.room.roomId} />,
428430
<EmojiButton key="emoji_button" addEmoji={this.addEmoji} />,
429431
);

src/components/views/rooms/SendMessageComposer.js

Lines changed: 40 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ import {
2929
} from '../../../editor/serialize';
3030
import {CommandPartCreator} from '../../../editor/parts';
3131
import BasicMessageComposer from "./BasicMessageComposer";
32-
import RoomViewStore from '../../../stores/RoomViewStore';
3332
import ReplyThread from "../elements/ReplyThread";
3433
import {parseEvent} from '../../../editor/deserialize';
3534
import {findEditableEvent} from '../../../utils/EventUtils';
@@ -41,7 +40,6 @@ import {_t, _td} from '../../../languageHandler';
4140
import ContentMessages from '../../../ContentMessages';
4241
import {Key} from "../../../Keyboard";
4342
import MatrixClientContext from "../../../contexts/MatrixClientContext";
44-
import {MatrixClientPeg} from "../../../MatrixClientPeg";
4543
import RateLimitedFunc from '../../../ratelimitedfunc';
4644
import {Action} from "../../../dispatcher/actions";
4745

@@ -61,7 +59,7 @@ function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
6159
}
6260

6361
// exported for tests
64-
export function createMessageContent(model, permalinkCreator) {
62+
export function createMessageContent(model, permalinkCreator, replyToEvent) {
6563
const isEmote = containsEmote(model);
6664
if (isEmote) {
6765
model = stripEmoteCommand(model);
@@ -70,21 +68,20 @@ export function createMessageContent(model, permalinkCreator) {
7068
model = stripPrefix(model, "/");
7169
}
7270
model = unescapeMessage(model);
73-
const repliedToEvent = RoomViewStore.getQuotingEvent();
7471

7572
const body = textSerialize(model);
7673
const content = {
7774
msgtype: isEmote ? "m.emote" : "m.text",
7875
body: body,
7976
};
80-
const formattedBody = htmlSerializeIfNeeded(model, {forceHTML: !!repliedToEvent});
77+
const formattedBody = htmlSerializeIfNeeded(model, {forceHTML: !!replyToEvent});
8178
if (formattedBody) {
8279
content.format = "org.matrix.custom.html";
8380
content.formatted_body = formattedBody;
8481
}
8582

86-
if (repliedToEvent) {
87-
addReplyToMessageContent(content, repliedToEvent, permalinkCreator);
83+
if (replyToEvent) {
84+
addReplyToMessageContent(content, replyToEvent, permalinkCreator);
8885
}
8986

9087
return content;
@@ -95,6 +92,7 @@ export default class SendMessageComposer extends React.Component {
9592
room: PropTypes.object.isRequired,
9693
placeholder: PropTypes.string,
9794
permalinkCreator: PropTypes.object.isRequired,
95+
replyToEvent: PropTypes.object,
9896
};
9997

10098
static contextType = MatrixClientContext;
@@ -104,12 +102,13 @@ export default class SendMessageComposer extends React.Component {
104102
this.model = null;
105103
this._editorRef = null;
106104
this.currentlyComposedEditorState = null;
107-
const cli = MatrixClientPeg.get();
108-
if (cli.isCryptoEnabled() && cli.isRoomEncrypted(this.props.room.roomId)) {
105+
if (this.context.isCryptoEnabled() && this.context.isRoomEncrypted(this.props.room.roomId)) {
109106
this._prepareToEncrypt = new RateLimitedFunc(() => {
110-
cli.prepareToEncrypt(this.props.room);
107+
this.context.prepareToEncrypt(this.props.room);
111108
}, 60000);
112109
}
110+
111+
window.addEventListener("beforeunload", this._saveStoredEditorState);
113112
}
114113

115114
_setEditorRef = ref => {
@@ -145,7 +144,7 @@ export default class SendMessageComposer extends React.Component {
145144
if (e.shiftKey || e.metaKey) return;
146145

147146
const shouldSelectHistory = e.altKey && e.ctrlKey;
148-
const shouldEditLastMessage = !e.altKey && !e.ctrlKey && up && !RoomViewStore.getQuotingEvent();
147+
const shouldEditLastMessage = !e.altKey && !e.ctrlKey && up && !this.props.replyToEvent;
149148

150149
if (shouldSelectHistory) {
151150
// Try select composer history
@@ -187,9 +186,13 @@ export default class SendMessageComposer extends React.Component {
187186
this.sendHistoryManager.currentIndex = this.sendHistoryManager.history.length;
188187
return;
189188
}
190-
const serializedParts = this.sendHistoryManager.getItem(delta);
191-
if (serializedParts) {
192-
this.model.reset(serializedParts);
189+
const {parts, replyEventId} = this.sendHistoryManager.getItem(delta);
190+
dis.dispatch({
191+
action: 'reply_to_event',
192+
event: replyEventId ? this.props.room.findEventById(replyEventId) : null,
193+
});
194+
if (parts) {
195+
this.model.reset(parts);
193196
this._editorRef.focus();
194197
}
195198
}
@@ -299,12 +302,12 @@ export default class SendMessageComposer extends React.Component {
299302
}
300303
}
301304

305+
const replyToEvent = this.props.replyToEvent;
302306
if (shouldSend) {
303-
const isReply = !!RoomViewStore.getQuotingEvent();
304307
const {roomId} = this.props.room;
305-
const content = createMessageContent(this.model, this.props.permalinkCreator);
308+
const content = createMessageContent(this.model, this.props.permalinkCreator, replyToEvent);
306309
this.context.sendMessage(roomId, content);
307-
if (isReply) {
310+
if (replyToEvent) {
308311
// Clear reply_to_event as we put the message into the queue
309312
// if the send fails, retry will handle resending.
310313
dis.dispatch({
@@ -315,7 +318,7 @@ export default class SendMessageComposer extends React.Component {
315318
dis.dispatch({action: "message_sent"});
316319
}
317320

318-
this.sendHistoryManager.save(this.model);
321+
this.sendHistoryManager.save(this.model, replyToEvent);
319322
// clear composer
320323
this.model.reset([]);
321324
this._editorRef.clearUndoHistory();
@@ -325,6 +328,8 @@ export default class SendMessageComposer extends React.Component {
325328

326329
componentWillUnmount() {
327330
dis.unregister(this.dispatcherRef);
331+
window.removeEventListener("beforeunload", this._saveStoredEditorState);
332+
this._saveStoredEditorState();
328333
}
329334

330335
// TODO: [REACT-WARNING] Move this to constructor
@@ -333,11 +338,11 @@ export default class SendMessageComposer extends React.Component {
333338
const parts = this._restoreStoredEditorState(partCreator) || [];
334339
this.model = new EditorModel(parts, partCreator);
335340
this.dispatcherRef = dis.register(this.onAction);
336-
this.sendHistoryManager = new SendHistoryManager(this.props.room.roomId, 'mx_cider_composer_history_');
341+
this.sendHistoryManager = new SendHistoryManager(this.props.room.roomId, 'mx_cider_history_');
337342
}
338343

339344
get _editorStateKey() {
340-
return `cider_editor_state_${this.props.room.roomId}`;
345+
return `mx_cider_state_${this.props.room.roomId}`;
341346
}
342347

343348
_clearStoredEditorState() {
@@ -347,17 +352,28 @@ export default class SendMessageComposer extends React.Component {
347352
_restoreStoredEditorState(partCreator) {
348353
const json = localStorage.getItem(this._editorStateKey);
349354
if (json) {
350-
const serializedParts = JSON.parse(json);
351-
const parts = serializedParts.map(p => partCreator.deserializePart(p));
352-
return parts;
355+
try {
356+
const {parts: serializedParts, replyEventId} = JSON.parse(json);
357+
const parts = serializedParts.map(p => partCreator.deserializePart(p));
358+
if (replyEventId) {
359+
dis.dispatch({
360+
action: 'reply_to_event',
361+
event: this.props.room.findEventById(replyEventId),
362+
});
363+
}
364+
return parts;
365+
} catch (e) {
366+
console.error(e);
367+
}
353368
}
354369
}
355370

356371
_saveStoredEditorState = () => {
357372
if (this.model.isEmpty) {
358373
this._clearStoredEditorState();
359374
} else {
360-
localStorage.setItem(this._editorStateKey, JSON.stringify(this.model.serializeParts()));
375+
const item = SendHistoryManager.createItem(this.model, this.props.replyToEvent);
376+
localStorage.setItem(this._editorStateKey, JSON.stringify(item));
361377
}
362378
}
363379

@@ -449,7 +465,6 @@ export default class SendMessageComposer extends React.Component {
449465
room={this.props.room}
450466
label={this.props.placeholder}
451467
placeholder={this.props.placeholder}
452-
onChange={this._saveStoredEditorState}
453468
onPaste={this._onPaste}
454469
/>
455470
</div>

0 commit comments

Comments
 (0)