diff --git a/src/app/components/editor/code/code.component.ts b/src/app/components/editor/code/code.component.ts index 3f5c1fc..060571a 100644 --- a/src/app/components/editor/code/code.component.ts +++ b/src/app/components/editor/code/code.component.ts @@ -7,7 +7,7 @@ import {EditorComponent} from 'ngx-monaco-editor'; import * as MonacoCollabExt from '@convergencelabs/monaco-collab-ext'; import {FlitterSocketConnection, FlitterSocketServerClientTransaction} from '../../../flitter-socket'; import {debug} from '../../../utility'; -import {environment} from "../../../../environments/environment"; +import {environment} from '../../../../environments/environment'; export interface RemoteUser { uuid: string; @@ -22,6 +22,7 @@ export interface RemoteInsertOperation { type: 'insert'; index: number; text: string; + fullContents?: string; } export interface RemoteReplaceOperation { @@ -29,12 +30,14 @@ export interface RemoteReplaceOperation { index: number; text: string; length: number; + fullContents?: string; } export interface RemoteDeleteOperation { type: 'delete'; index: number; length: number; + fullContents?: string; } export type RemoteOperation = RemoteInsertOperation | RemoteReplaceOperation | RemoteDeleteOperation; diff --git a/src/app/components/nodes/markdown/markdown.component.html b/src/app/components/nodes/markdown/markdown.component.html index cf6ce2e..d29d7ed 100644 --- a/src/app/components/nodes/markdown/markdown.component.html +++ b/src/app/components/nodes/markdown/markdown.component.html @@ -9,4 +9,4 @@ >
- \ No newline at end of file + diff --git a/src/app/components/nodes/markdown/markdown.component.ts b/src/app/components/nodes/markdown/markdown.component.ts index 6ce51d9..6fbaede 100644 --- a/src/app/components/nodes/markdown/markdown.component.ts +++ b/src/app/components/nodes/markdown/markdown.component.ts @@ -3,8 +3,12 @@ import {EditorNodeContract} from '../EditorNode.contract'; import {EditorService} from '../../../service/editor.service'; import {v4} from 'uuid'; import {EditorComponent} from 'ngx-monaco-editor'; -import {KatexOptions, MarkedOptions} from 'ngx-markdown'; -import * as hljs from 'highlight.js'; +import {KatexOptions} from 'ngx-markdown'; +import {environment} from '../../../../environments/environment'; +import {debug} from '../../../utility'; +import {FlitterSocketConnection, FlitterSocketServerClientTransaction} from '../../../flitter-socket'; +import {RemoteOperation, RemoteUser} from '../../editor/code/code.component'; +import * as MonacoCollabExt from '@convergencelabs/monaco-collab-ext'; @Component({ selector: 'editor-markdown', @@ -33,6 +37,17 @@ export class MarkdownComponent extends EditorNodeContract implements OnInit { protected hadOneFocusOut = false; public singleColumn = false; public containerHeight = 540; + public editorGroupID!: string; + protected socket?: FlitterSocketConnection; + protected localUser!: RemoteUser; + protected remoteUsers: RemoteUser[] = []; + protected cursorManager: any; + protected selectionManager: any; + protected contentManager: any; + + public get readonly() { + return !this.node || !this.editorService.canEdit(); + } public editorOptions = { theme: this.isDark() ? 'vs-dark' : 'vs', @@ -73,6 +88,26 @@ export class MarkdownComponent extends EditorNodeContract implements OnInit { this.contents = this.initialValue; }); + + const url = `${environment.websocketBase}/api/v1/socket/markdown/.websocket`; + debug(`Editor socket URL: ${url}`); + + if ( !this.editorService.isVersion() ) { + const socket = new FlitterSocketConnection(url); + socket.controller(this); + + socket.on_open().then(() => { + debug('Connected to markdown editor socket', socket); + socket.asyncRequest('subscribe', { resource_id: this.node.UUID }).then(([transaction, _, data]) => { + debug('Subscribed to markdown group:', data); + if ( data.editor_group_id ) { + this.editorGroupID = data.editor_group_id; + this.socket = socket; + this.localUser = data.local_user; + } + }); + }); + } } onMonacoEditorInit(editor) { @@ -91,6 +126,96 @@ export class MarkdownComponent extends EditorNodeContract implements OnInit { editor.onDidContentSizeChange(updateHeight); updateHeight(); + + editor.onDidChangeCursorPosition(event => { + this.socket?.asyncRequest('update_cursor', { + position: event.position, + uuid: this.localUser?.uuid, + editor_group_id: this.editorGroupID, + }); + }); + + editor.onDidChangeCursorSelection(event => { + this.socket?.asyncRequest('update_selection', { + startPosition: { + lineNumber: event.selection.startLineNumber, + column: event.selection.startColumn, + }, + endPosition: { + lineNumber: event.selection.endLineNumber, + column: event.selection.endColumn, + }, + uuid: this.localUser?.uuid, + editor_group_id: this.editorGroupID, + }); + }); + + editor.onDidContentSizeChange(updateHeight); + updateHeight(); + + this.cursorManager = new MonacoCollabExt.RemoteCursorManager({ + editor, + tooltips: true, + tooltipDuration: 2, + }); + + this.selectionManager = new MonacoCollabExt.RemoteSelectionManager({ editor }); + + this.contentManager = new MonacoCollabExt.EditorContentManager({ + editor, + onInsert: (index, text) => { + if ( this.readonly ) { + return; + } + + this.socket?.asyncRequest('apply', { + editor_group_id: this.editorGroupID, + operations: [ + { + type: 'insert', + index, + text, + fullContents: this.contents, + }, + ], + }); + }, + onReplace: (index, length, text) => { + if ( this.readonly ) { + return; + } + + this.socket?.asyncRequest('apply', { + editor_group_id: this.editorGroupID, + operations: [ + { + type: 'replace', + index, + text, + length, + fullContents: this.contents, + }, + ], + }); + }, + onDelete: (index, length) => { + if ( this.readonly ) { + return; + } + + this.socket?.asyncRequest('apply', { + editor_group_id: this.editorGroupID, + operations: [ + { + type: 'delete', + index, + length, + fullContents: this.contents, + }, + ], + }); + }, + }); } public isDirty(): boolean | Promise