2 EditorToClientReplyMessage,
3 ClientRequiresEditorMethods,
4 ParamsExcludingFunctions,
5 ClientToEditorInvokationMessage,
8 DataTypesThatDocumentCanBeExportedAs,
10 EditorInitializationConfig,
12 SyncedEditorStateValues,
13 } from '@proton/docs-shared'
14 import { EditorBridgeMessageType, BridgeOriginProvider } from '@proton/docs-shared'
15 import type { LoggerInterface } from '@proton/utils/logs'
16 import { GenerateUUID } from '../Util/GenerateUuid'
17 import type { SerializedEditorState } from 'lexical'
18 import type { UserSettings } from '@proton/shared/lib/interfaces'
20 /** Allows the client to invoke methods on the editor */
21 export class EditorInvoker implements ClientRequiresEditorMethods {
22 private pendingMessages: PendingMessage[] = []
25 private editorFrame: HTMLIFrameElement,
26 private readonly logger: LoggerInterface,
30 property: keyof SyncedEditorStateValues,
31 value: SyncedEditorStateValues[keyof SyncedEditorStateValues],
33 return this.invokeEditorMethod('syncProperty', [property, value])
36 async loadUserSettings(settings: UserSettings): Promise<void> {
37 return this.invokeEditorMethod('loadUserSettings', [settings])
40 async getClientId(): Promise<number> {
41 return this.invokeEditorMethod('getClientId', [])
44 async showEditor(): Promise<void> {
45 return this.invokeEditorMethod('showEditor', [])
48 async performOpeningCeremony(): Promise<void> {
49 return this.invokeEditorMethod('performOpeningCeremony', [])
52 async performClosingCeremony(): Promise<void> {
53 return this.invokeEditorMethod('performClosingCeremony', [])
56 async receiveMessage(message: RtsMessagePayload): Promise<void> {
57 return this.invokeEditorMethod('receiveMessage', [message])
60 async getDocumentState(): Promise<YjsState> {
61 return this.invokeEditorMethod('getDocumentState', [])
64 async replaceEditorState(state: SerializedEditorState): Promise<void> {
65 return this.invokeEditorMethod('replaceEditorState', [state])
68 async handleCommentsChange(): Promise<void> {
69 return this.invokeEditorMethod('handleCommentsChange', [])
72 async handleTypingStatusChange(threadId: string): Promise<void> {
73 return this.invokeEditorMethod('handleTypingStatusChange', [threadId])
76 async handleCreateCommentMarkNode(markID: string): Promise<void> {
77 return this.invokeEditorMethod('handleCreateCommentMarkNode', [markID])
80 async handleRemoveCommentMarkNode(markID: string): Promise<void> {
81 return this.invokeEditorMethod('handleRemoveCommentMarkNode', [markID])
84 async handleResolveCommentMarkNode(markID: string): Promise<void> {
85 return this.invokeEditorMethod('handleResolveCommentMarkNode', [markID])
88 async handleUnresolveCommentMarkNode(markID: string): Promise<void> {
89 return this.invokeEditorMethod('handleUnresolveCommentMarkNode', [markID])
92 async changeLockedState(locked: boolean): Promise<void> {
93 return this.invokeEditorMethod('changeLockedState', [locked])
96 async broadcastPresenceState(): Promise<void> {
97 return this.invokeEditorMethod('broadcastPresenceState', [])
100 async showCommentsPanel(): Promise<void> {
101 return this.invokeEditorMethod('showCommentsPanel', [])
104 async exportData(format: DataTypesThatDocumentCanBeExportedAs): Promise<Uint8Array> {
105 return this.invokeEditorMethod('exportData', [format])
108 async printAsPDF(): Promise<void> {
109 return this.invokeEditorMethod('printAsPDF', [])
112 async getCurrentEditorState(): Promise<SerializedEditorState | undefined> {
113 return this.invokeEditorMethod('getCurrentEditorState', [])
116 async toggleDebugTreeView(): Promise<void> {
117 return this.invokeEditorMethod('toggleDebugTreeView', [])
120 async handleIsSuggestionsFeatureEnabled(enabled: boolean): Promise<void> {
121 return this.invokeEditorMethod('handleIsSuggestionsFeatureEnabled', [enabled])
124 async initializeEditor(
127 documentRole: DocumentRoleType,
128 editorInitializationConfig?: EditorInitializationConfig,
130 return this.invokeEditorMethod('initializeEditor', [
134 editorInitializationConfig,
138 public handleReplyFromEditor(message: EditorToClientReplyMessage): void {
139 const pendingMessage = this.pendingMessages.find((m) => m.messageId === message.messageId)
140 if (pendingMessage) {
141 pendingMessage.resolve(message.returnValue)
142 this.pendingMessages = this.pendingMessages.filter((m) => m !== pendingMessage)
146 private async invokeEditorMethod<K extends keyof ClientRequiresEditorMethods>(
148 args: ParamsExcludingFunctions<Parameters<ClientRequiresEditorMethods[K]>>,
149 ): Promise<ReturnType<ClientRequiresEditorMethods[K]>> {
150 if (!this.editorFrame.contentWindow) {
151 throw new Error('Editor frame contentWindow is not ready')
154 const messageId = GenerateUUID()
156 const message: ClientToEditorInvokationMessage<K> = {
157 type: EditorBridgeMessageType.ClientToEditorInvokation,
163 this.logger.debug('Sending message to editor', message)
165 this.editorFrame.contentWindow.postMessage(message, BridgeOriginProvider.GetEditorOrigin())
167 return new Promise<ReturnType<ClientRequiresEditorMethods[K]>>((resolve) => {
168 this.pendingMessages.push({