RTE drafts (#12674)
* Add drafts to the RTE and tests * test drafts in threads * lint * Add unit test. * Fix test failure * Remove unused import * Clean up wysiwyg drafts and add test. * Fix typo * Add timeout to allow for wasm loading. --------- Co-authored-by: Florian Duros <florian.duros@ormaz.fr>
This commit is contained in:
parent
fdc5acd5a4
commit
70665d3ce3
5 changed files with 266 additions and 38 deletions
|
@ -18,6 +18,7 @@ import { logger } from "matrix-js-sdk/src/logger";
|
|||
|
||||
import { MatrixClientPeg } from "./MatrixClientPeg";
|
||||
import { EDITOR_STATE_STORAGE_PREFIX } from "./components/views/rooms/SendMessageComposer";
|
||||
import { WYSIWYG_EDITOR_STATE_STORAGE_PREFIX } from "./components/views/rooms/MessageComposer";
|
||||
|
||||
// The key used to persist the the timestamp we last cleaned up drafts
|
||||
export const DRAFT_LAST_CLEANUP_KEY = "mx_draft_cleanup";
|
||||
|
@ -61,14 +62,21 @@ function shouldCleanupDrafts(): boolean {
|
|||
}
|
||||
|
||||
/**
|
||||
* Clear all drafts for the CIDER editor if the room does not exist in the known rooms.
|
||||
* Clear all drafts for the CIDER and WYSIWYG editors if the room does not exist in the known rooms.
|
||||
*/
|
||||
function cleaupDrafts(): void {
|
||||
for (let i = 0; i < localStorage.length; i++) {
|
||||
const keyName = localStorage.key(i);
|
||||
if (!keyName?.startsWith(EDITOR_STATE_STORAGE_PREFIX)) continue;
|
||||
if (!keyName) continue;
|
||||
let roomId: string | undefined = undefined;
|
||||
if (keyName.startsWith(EDITOR_STATE_STORAGE_PREFIX)) {
|
||||
roomId = keyName.slice(EDITOR_STATE_STORAGE_PREFIX.length).split("_$")[0];
|
||||
}
|
||||
if (keyName.startsWith(WYSIWYG_EDITOR_STATE_STORAGE_PREFIX)) {
|
||||
roomId = keyName.slice(WYSIWYG_EDITOR_STATE_STORAGE_PREFIX.length).split("_$")[0];
|
||||
}
|
||||
if (!roomId) continue;
|
||||
// Remove the prefix and the optional event id suffix to leave the room id
|
||||
const roomId = keyName.slice(EDITOR_STATE_STORAGE_PREFIX.length).split("_$")[0];
|
||||
const room = MatrixClientPeg.safeGet().getRoom(roomId);
|
||||
if (!room) {
|
||||
logger.debug(`Removing draft for unknown room with key ${keyName}`);
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
} from "matrix-js-sdk/src/matrix";
|
||||
import { Optional } from "matrix-events-sdk";
|
||||
import { Tooltip } from "@vector-im/compound-web";
|
||||
import { logger } from "matrix-js-sdk/src/logger";
|
||||
|
||||
import { _t } from "../../../languageHandler";
|
||||
import { MatrixClientPeg } from "../../../MatrixClientPeg";
|
||||
|
@ -65,6 +66,9 @@ import { createCantStartVoiceMessageBroadcastDialog } from "../dialogs/CantStart
|
|||
import { UIFeature } from "../../../settings/UIFeature";
|
||||
import { formatTimeLeft } from "../../../DateUtils";
|
||||
|
||||
// The prefix used when persisting editor drafts to localstorage.
|
||||
export const WYSIWYG_EDITOR_STATE_STORAGE_PREFIX = "mx_wysiwyg_state_";
|
||||
|
||||
let instanceCount = 0;
|
||||
|
||||
interface ISendButtonProps {
|
||||
|
@ -109,6 +113,12 @@ interface IState {
|
|||
initialComposerContent: string;
|
||||
}
|
||||
|
||||
type WysiwygComposerState = {
|
||||
content: string;
|
||||
isRichText: boolean;
|
||||
replyEventId?: string;
|
||||
};
|
||||
|
||||
export class MessageComposer extends React.Component<IProps, IState> {
|
||||
private dispatcherRef?: string;
|
||||
private messageComposerInput = createRef<SendMessageComposerClass>();
|
||||
|
@ -129,11 +139,32 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
|||
|
||||
public constructor(props: IProps, context: React.ContextType<typeof RoomContext>) {
|
||||
super(props, context);
|
||||
this.context = context; // otherwise React will only set it prior to render due to type def above
|
||||
|
||||
VoiceRecordingStore.instance.on(UPDATE_EVENT, this.onVoiceStoreUpdate);
|
||||
|
||||
window.addEventListener("beforeunload", this.saveWysiwygEditorState);
|
||||
const isWysiwygLabEnabled = SettingsStore.getValue<boolean>("feature_wysiwyg_composer");
|
||||
let isRichTextEnabled = true;
|
||||
let initialComposerContent = "";
|
||||
if (isWysiwygLabEnabled) {
|
||||
const wysiwygState = this.restoreWysiwygEditorState();
|
||||
if (wysiwygState) {
|
||||
isRichTextEnabled = wysiwygState.isRichText;
|
||||
initialComposerContent = wysiwygState.content;
|
||||
if (wysiwygState.replyEventId) {
|
||||
dis.dispatch({
|
||||
action: "reply_to_event",
|
||||
event: this.props.room.findEventById(wysiwygState.replyEventId),
|
||||
context: this.context.timelineRenderingType,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.state = {
|
||||
isComposerEmpty: true,
|
||||
composerContent: "",
|
||||
isComposerEmpty: initialComposerContent?.length === 0,
|
||||
composerContent: initialComposerContent,
|
||||
haveRecording: false,
|
||||
recordingTimeLeftSeconds: undefined, // when set to a number, shows a toast
|
||||
isMenuOpen: false,
|
||||
|
@ -141,9 +172,9 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
|||
showStickersButton: SettingsStore.getValue("MessageComposerInput.showStickersButton"),
|
||||
showPollsButton: SettingsStore.getValue("MessageComposerInput.showPollsButton"),
|
||||
showVoiceBroadcastButton: SettingsStore.getValue(Features.VoiceBroadcast),
|
||||
isWysiwygLabEnabled: SettingsStore.getValue<boolean>("feature_wysiwyg_composer"),
|
||||
isRichTextEnabled: true,
|
||||
initialComposerContent: "",
|
||||
isWysiwygLabEnabled: isWysiwygLabEnabled,
|
||||
isRichTextEnabled: isRichTextEnabled,
|
||||
initialComposerContent: initialComposerContent,
|
||||
};
|
||||
|
||||
this.instanceId = instanceCount++;
|
||||
|
@ -154,6 +185,52 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
|||
SettingsStore.monitorSetting("feature_wysiwyg_composer", null);
|
||||
}
|
||||
|
||||
private get editorStateKey(): string {
|
||||
let key = WYSIWYG_EDITOR_STATE_STORAGE_PREFIX + this.props.room.roomId;
|
||||
if (this.props.relation?.rel_type === THREAD_RELATION_TYPE.name) {
|
||||
key += `_${this.props.relation.event_id}`;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
private restoreWysiwygEditorState(): WysiwygComposerState | undefined {
|
||||
const json = localStorage.getItem(this.editorStateKey);
|
||||
if (json) {
|
||||
try {
|
||||
const state: WysiwygComposerState = JSON.parse(json);
|
||||
return state;
|
||||
} catch (e) {
|
||||
logger.error(e);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private saveWysiwygEditorState = (): void => {
|
||||
if (this.shouldSaveWysiwygEditorState()) {
|
||||
const { isRichTextEnabled, composerContent } = this.state;
|
||||
const replyEventId = this.props.replyToEvent ? this.props.replyToEvent.getId() : undefined;
|
||||
const item: WysiwygComposerState = {
|
||||
content: composerContent,
|
||||
isRichText: isRichTextEnabled,
|
||||
replyEventId: replyEventId,
|
||||
};
|
||||
localStorage.setItem(this.editorStateKey, JSON.stringify(item));
|
||||
} else {
|
||||
this.clearStoredEditorState();
|
||||
}
|
||||
};
|
||||
|
||||
// should save state when wysiwyg is enabled and has contents or reply is open
|
||||
private shouldSaveWysiwygEditorState = (): boolean => {
|
||||
const { isWysiwygLabEnabled, isComposerEmpty } = this.state;
|
||||
return isWysiwygLabEnabled && (!isComposerEmpty || !!this.props.replyToEvent);
|
||||
};
|
||||
|
||||
private clearStoredEditorState(): void {
|
||||
localStorage.removeItem(this.editorStateKey);
|
||||
}
|
||||
|
||||
private get voiceRecording(): Optional<VoiceMessageRecording> {
|
||||
return this._voiceRecording;
|
||||
}
|
||||
|
@ -265,6 +342,8 @@ export class MessageComposer extends React.Component<IProps, IState> {
|
|||
UIStore.instance.stopTrackingElementDimensions(`MessageComposer${this.instanceId}`);
|
||||
UIStore.instance.removeListener(`MessageComposer${this.instanceId}`, this.onResize);
|
||||
|
||||
window.removeEventListener("beforeunload", this.saveWysiwygEditorState);
|
||||
this.saveWysiwygEditorState();
|
||||
// clean up our listeners by setting our cached recording to falsy (see internal setter)
|
||||
this.voiceRecording = null;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue