File size: 2,138 Bytes
8f9c4ef
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import * as Y from 'yjs';
import { Socket } from 'socket.io-client';

export class YjsBinding {
  public doc: Y.Doc;
  public text: Y.Text;
  private socket: Socket;
  private fileId: string;
  private isLocalUpdate = false;

  constructor(socket: Socket, fileId: string) {
    this.socket = socket;
    this.fileId = fileId;
    this.doc = new Y.Doc();
    this.text = this.doc.getText('content');

    // Broadcast local changes
    this.doc.on('update', (update: Uint8Array, origin: any) => {
      if (origin !== 'remote') {
        this.socket.emit('doc:update', { update: Array.from(update), fileId: this.fileId });
      }
    });

    // Apply remote changes
    this.socket.on('doc:sync', (data: { update: number[]; fileId: string }) => {
      if (data.fileId !== this.fileId) return;
      this.isLocalUpdate = true;
      Y.applyUpdate(this.doc, new Uint8Array(data.update), 'remote');
      this.isLocalUpdate = false;
    });

    this.socket.on('doc:state', (data: { state: number[]; fileId: string }) => {
      if (data.fileId !== this.fileId) return;
      if (data.state.length > 0) {
        this.isLocalUpdate = true;
        Y.applyUpdate(this.doc, new Uint8Array(data.state), 'remote');
        this.isLocalUpdate = false;
      }
    });
  }

  requestState(): void { this.socket.emit('doc:request-state', { fileId: this.fileId }); }
  getText(): string { return this.text.toString(); }
  isRemoteUpdate(): boolean { return this.isLocalUpdate; }

  applyMonacoChanges(changes: Array<{ rangeOffset: number; rangeLength: number; text: string }>): void {
    this.doc.transact(() => {
      const sorted = [...changes].sort((a, b) => b.rangeOffset - a.rangeOffset);
      for (const change of sorted) {
        if (change.rangeLength > 0) this.text.delete(change.rangeOffset, change.rangeLength);
        if (change.text) this.text.insert(change.rangeOffset, change.text);
      }
    });
  }

  observe(callback: (event: Y.YTextEvent) => void): void { this.text.observe(callback); }

  destroy(): void {
    this.doc.destroy();
    this.socket.off('doc:sync');
    this.socket.off('doc:state');
  }
}

export { Y };