bring back composer send history and arrow up to edit previous message

This commit is contained in:
Bruno Windels 2019-08-20 17:18:46 +02:00
parent ca3539d53e
commit cc82353d8f
3 changed files with 84 additions and 46 deletions

View file

@ -15,38 +15,8 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import {Value} from 'slate';
import _clamp from 'lodash/clamp';
type MessageFormat = 'rich' | 'markdown';
class HistoryItem {
// We store history items in their native format to ensure history is accurate
// and then convert them if our RTE has subsequently changed format.
value: Value;
format: MessageFormat = 'rich';
constructor(value: ?Value, format: ?MessageFormat) {
this.value = value;
this.format = format;
}
static fromJSON(obj: Object): HistoryItem {
return new HistoryItem(
Value.fromJSON(obj.value),
obj.format,
);
}
toJSON(): Object {
return {
value: this.value.toJSON(),
format: this.format,
};
}
}
export default class ComposerHistoryManager {
history: Array<HistoryItem> = [];
prefix: string;
@ -57,26 +27,30 @@ export default class ComposerHistoryManager {
this.prefix = prefix + roomId;
// TODO: Performance issues?
let item;
for (; item = sessionStorage.getItem(`${this.prefix}[${this.currentIndex}]`); this.currentIndex++) {
let index = 0;
let itemJSON;
while (itemJSON = sessionStorage.getItem(`${this.prefix}[${index}]`)) {
try {
this.history.push(
HistoryItem.fromJSON(JSON.parse(item)),
);
const serializedParts = JSON.parse(itemJSON);
this.history.push(serializedParts);
} catch (e) {
console.warn("Throwing away unserialisable history", e);
break;
}
++index;
}
this.lastIndex = this.currentIndex;
this.lastIndex = this.history.length - 1;
// reset currentIndex to account for any unserialisable history
this.currentIndex = this.history.length;
this.currentIndex = this.lastIndex + 1;
}
save(value: Value, format: MessageFormat) {
const item = new HistoryItem(value, format);
this.history.push(item);
save(editorModel: Object) {
const serializedParts = editorModel.serializeParts();
this.history.push(serializedParts);
this.currentIndex = this.history.length;
sessionStorage.setItem(`${this.prefix}[${this.lastIndex++}]`, JSON.stringify(item.toJSON()));
this.lastIndex += 1;
sessionStorage.setItem(`${this.prefix}[${this.lastIndex}]`, JSON.stringify(serializedParts));
}
getItem(offset: number): ?HistoryItem {

View file

@ -144,6 +144,10 @@ export default class BasicMessageEditor extends React.Component {
return this._lastCaret;
}
isSelectionCollapsed() {
return !this._lastSelection || this._lastSelection.isCollapsed;
}
isCaretAtStart() {
return this.getCaret().offset === 0;
}

View file

@ -27,6 +27,8 @@ import ReplyPreview from "./ReplyPreview";
import RoomViewStore from '../../../stores/RoomViewStore';
import ReplyThread from "../elements/ReplyThread";
import {parseEvent} from '../../../editor/deserialize';
import {findEditableEvent} from '../../../utils/EventUtils';
import ComposerHistoryManager from "../../../ComposerHistoryManager";
function addReplyToMessageContent(content, repliedToEvent, permalinkCreator) {
const replyContent = ReplyThread.makeReplyMixIn(repliedToEvent);
@ -79,6 +81,7 @@ export default class SendMessageComposer extends React.Component {
super(props, context);
this.model = null;
this._editorRef = null;
this.currentlyComposedEditorState = null;
}
_setEditorRef = ref => {
@ -86,19 +89,75 @@ export default class SendMessageComposer extends React.Component {
};
_onKeyDown = (event) => {
if (event.metaKey || event.altKey || event.shiftKey) {
return;
}
if (event.key === "Enter") {
const hasModifier = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
if (event.key === "Enter" && !hasModifier) {
this._sendMessage();
event.preventDefault();
} else if (event.key === "ArrowUp") {
this.onVerticalArrow(event, true);
} else if (event.key === "ArrowDown") {
this.onVerticalArrow(event, false);
}
}
onVerticalArrow(e, up) {
if (e.ctrlKey || e.shiftKey || e.metaKey) return;
const shouldSelectHistory = e.altKey;
const shouldEditLastMessage = !e.altKey && up && !RoomViewStore.getQuotingEvent();
if (shouldSelectHistory) {
// Try select composer history
const selected = this.selectSendHistory(up);
if (selected) {
// We're selecting history, so prevent the key event from doing anything else
e.preventDefault();
}
} else if (shouldEditLastMessage) {
// selection must be collapsed and caret at start
if (this._editorRef.isSelectionCollapsed() && this._editorRef.isCaretAtStart()) {
const editEvent = findEditableEvent(this.props.room, false);
if (editEvent) {
// We're selecting history, so prevent the key event from doing anything else
e.preventDefault();
dis.dispatch({
action: 'edit_event',
event: editEvent,
});
}
}
}
}
selectSendHistory(up) {
const delta = up ? -1 : 1;
// True if we are not currently selecting history, but composing a message
if (this.sendHistoryManager.currentIndex === this.sendHistoryManager.history.length) {
// We can't go any further - there isn't any more history, so nop.
if (!up) {
return;
}
this.currentlyComposedEditorState = this.model.serializeParts();
} else if (this.sendHistoryManager.currentIndex + delta === this.sendHistoryManager.history.length) {
// True when we return to the message being composed currently
this.model.reset(this.currentlyComposedEditorState);
this.sendHistoryManager.currentIndex = this.sendHistoryManager.history.length;
return;
}
const serializedParts = this.sendHistoryManager.getItem(delta);
if (serializedParts) {
this.model.reset(serializedParts);
this._editorRef.focus();
}
}
_sendMessage() {
const isReply = !!RoomViewStore.getQuotingEvent();
const {roomId} = this.props.room;
this.context.matrixClient.sendMessage(roomId, createMessageContent(this.model, this.props.permalinkCreator));
const content = createMessageContent(this.model, this.props.permalinkCreator);
this.context.matrixClient.sendMessage(roomId, content);
this.sendHistoryManager.save(this.model);
this.model.reset([]);
this._editorRef.clearUndoHistory();
@ -125,6 +184,7 @@ export default class SendMessageComposer extends React.Component {
const partCreator = new PartCreator(this.props.room, this.context.matrixClient);
this.model = new EditorModel([], partCreator);
this.dispatcherRef = dis.register(this.onAction);
this.sendHistoryManager = new ComposerHistoryManager(this.props.room.roomId, 'mx_slate_composer_history_');
}
onAction = (payload) => {