Skip to content
Open
2 changes: 1 addition & 1 deletion .github/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ $ yarn # install local np
### Start Dev Server

```bash
$ cp configs/firebase.sample.json configs/firebase.json # copy dummy config file
$ cp configs/firebase.json.sample configs/firebase.json # copy dummy config file

$ vi configs/firebase.json # update configuration

Expand Down
1 change: 1 addition & 0 deletions .github/workflows/npm-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ on:
pull_request:
branches:
- main
- vscode-firepad

jobs:
build:
Expand Down
16 changes: 14 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ Tags:
Good to have: commit or PR links.

-->
## v0.5.2-beta [#56](https://github.com/interviewstreet/firepad-x/pull/56)

### Changed

- Added MonacoEditorUtilAdapter to support native monaco and monaco from vscode

## v0.4.2-beta [#55](https://github.com/interviewstreet/firepad-x/pull/55)

### Changed

- Added `Firepd.enable()` and `Firepad.disable()` API methods for enbling and disabling firepad without disposing.
- No breaking changes on the API level.

## v0.4.1-beta

Expand All @@ -30,11 +42,11 @@ No Changes

### Changed

- Fix a bug monaco-adapter/_operationFromMonacoChange function
- Fix a bug monaco-adapter/\_operationFromMonacoChange function
- Revert https://github.com/interviewstreet/firepad-x/commit/2aebf79871d3fc9bf21e9b056a0faa60c6da0b3a
- Revert https://github.com/interviewstreet/firepad-x/commit/a12177892c692b44c106d60f2819fbf7f1094f22
- No breaking change on external APIs.
- Revert to firebase v7.12.0 (https://github.com/interviewstreet/firepad-x/commit/03910ce475e04ddcab2e683877f579bfcbd32d91).
- Revert to firebase v7.12.0 (https://github.com/interviewstreet/firepad-x/commit/03910ce475e04ddcab2e683877f579bfcbd32d91).

## v0.3.1 [#44](https://github.com/interviewstreet/firepad-x/pull/44)

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@hackerrank/firepad",
"description": "Collaborative text editing powered by Firebase",
"version": "0.7.4-beta",
"version": "0.8.1-beta",
"author": {
"email": "[email protected]",
"name": "Progyan Bhattacharya",
Expand Down
6 changes: 5 additions & 1 deletion src/cursor-widget-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as monaco from "monaco-editor";

import { CursorWidget, ICursorWidget } from "./cursor-widget";
import { ClientIDType } from "./editor-adapter";
import { IMonacoEditorUtilsAdapter } from "./monaco-editor-utils";
import { IDisposable } from "./utils";

export interface ICursorWidgetController extends IDisposable {
Expand Down Expand Up @@ -42,11 +43,13 @@ export class CursorWidgetController implements ICursorWidgetController {
protected readonly _cursors: Map<ClientIDType, ICursorWidget>;
protected readonly _editor: monaco.editor.IStandaloneCodeEditor;
protected readonly _tooltipDuration: number;
protected readonly _editorUtils: IMonacoEditorUtilsAdapter;

constructor(editor: monaco.editor.IStandaloneCodeEditor) {
constructor(editor: monaco.editor.IStandaloneCodeEditor, editorUtils: IMonacoEditorUtilsAdapter) {
this._editor = editor;
this._tooltipDuration = 1000;
this._cursors = new Map<ClientIDType, ICursorWidget>();
this._editorUtils = editorUtils;
}

addCursor(
Expand All @@ -62,6 +65,7 @@ export class CursorWidgetController implements ICursorWidgetController {
range,
label: userName || clientId.toString(),
tooltipDuration: this._tooltipDuration,
editorUtils: this._editorUtils,
onDisposed: () => {
this.removeCursor(clientId);
},
Expand Down
11 changes: 8 additions & 3 deletions src/cursor-widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import * as monaco from "monaco-editor";

import { ClientIDType } from "./editor-adapter";
import { IMonacoEditorUtilsAdapter } from "./monaco-editor-utils";
import * as Utils from "./utils";

type OnDisposed = Utils.VoidFunctionType;
Expand All @@ -22,6 +23,7 @@ export interface ICursorWidgetConstructorOptions {
range: monaco.Range;
tooltipDuration?: number;
opacity?: string;
editorUtils: IMonacoEditorUtilsAdapter;
onDisposed: OnDisposed;
}

Expand Down Expand Up @@ -56,6 +58,7 @@ export class CursorWidget implements ICursorWidget {
protected readonly _tooltipDuration: number;
protected readonly _scrollListener: monaco.IDisposable | null;
protected readonly _onDisposed: OnDisposed;
protected readonly _editorUtils: IMonacoEditorUtilsAdapter;

protected _tooltipNode: HTMLElement;
protected _color: string;
Expand All @@ -76,7 +79,8 @@ export class CursorWidget implements ICursorWidget {
range,
tooltipDuration = 1000,
opacity = "1.0",
onDisposed,
editorUtils,
onDisposed
}: ICursorWidgetConstructorOptions) {
this._editor = codeEditor;
this._tooltipDuration = tooltipDuration;
Expand All @@ -85,6 +89,7 @@ export class CursorWidget implements ICursorWidget {
this._color = color;
this._content = label;
this._opacity = opacity;
this._editorUtils = editorUtils;

this._domNode = this._createWidgetNode();

Expand Down Expand Up @@ -148,8 +153,8 @@ export class CursorWidget implements ICursorWidget {
this._position = {
position: range.getEndPosition(),
preference: [
monaco.editor.ContentWidgetPositionPreference.ABOVE,
monaco.editor.ContentWidgetPositionPreference.BELOW,
this._editorUtils.getContentWidgetPositionPreference('ABOVE'),
this._editorUtils.getContentWidgetPositionPreference('BELOW'),
],
};

Expand Down
22 changes: 21 additions & 1 deletion src/firebase-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ export class FirebaseAdapter implements IDatabaseAdapter {
this._init();
}

public enable() {
this._zombie = false;
}

public disable() {
this._zombie = true;
}

protected _init(): void {
const connectedRef = this._databaseRef!.root.child(".info/connected");

Expand Down Expand Up @@ -393,7 +401,19 @@ export class FirebaseAdapter implements IDatabaseAdapter {
// We have an outstanding change at this revision id.
if (
this._sent.op.equals(revision.operation) &&
revision.author == this._userId
/**
* Adding the OR revisionId === "A0" condition because when firepad is
* initialized, both clients call Firepad.setText method which results
* in an operation being created for the default editor content of both the
* clients. If default editor content is same because of any code stub or
* if we have set some default value in monaco editor, this leads to the
* same default code appearing twice. To fix this, we make an assumption that
* if the sent op is same as received op and if it's the first op (A0), then
* it was probably a code stub. This condition will not hold true if both the
* clients input the same character after being initialized on purpose and want
* that content to be replicated twice. This however seems unlikely.
*/
(revision.author == this._userId || revisionId === "A0")
) {
// This is our change; it succeeded.
if (this._revision % FirebaseAdapter.CHECKPOINT_FREQUENCY === 0) {
Expand Down
20 changes: 20 additions & 0 deletions src/firepad-classic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,26 @@ export default class FirepadClassic implements IFirepad {
this.init();
}

public enable(): void {
throw new Error("Method not implemented.");
}

public disable(): void {
throw new Error("Method not implemented.");
}

public beforeApplyChanges(
callback: (changes: monaco.editor.IIdentifiedSingleEditOperation[]) => void
): void {
throw new Error("Method not implemented.");
}

public afterApplyChanges(
callback: (changes: monaco.editor.IIdentifiedSingleEditOperation[]) => void
): void {
throw new Error("Method not implemented.");
}

protected init(): void {
this._databaseAdapter.on(DatabaseAdapterEvent.Ready, () => {
this._ready = true;
Expand Down
6 changes: 4 additions & 2 deletions src/firepad-monaco.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { IDatabaseAdapter, UserIDType } from "./database-adapter";
import { FirebaseAdapter } from "./firebase-adapter";
import { Firepad, IFirepad, IFirepadConstructorOptions } from "./firepad";
import { MonacoAdapter } from "./monaco-adapter";
import { IMonacoEditorUtilsAdapter, NativeMonacoEditorUtils } from "./monaco-editor-utils";
import * as Utils from "./utils";
import { FirestoreAdapter } from "./firestore-adapter";

Expand All @@ -18,7 +19,8 @@ import { FirestoreAdapter } from "./firestore-adapter";
export function fromMonacoWithFirebase(
databaseRef: string | firebase.database.Reference,
editor: monaco.editor.IStandaloneCodeEditor,
options: Partial<IFirepadConstructorOptions> = {}
options: Partial<IFirepadConstructorOptions> = {},
editorUtils: IMonacoEditorUtilsAdapter = new NativeMonacoEditorUtils()
): IFirepad {
// Initialize constructor options with their default values
const userId: UserIDType = options.userId || uuid();
Expand All @@ -34,7 +36,7 @@ export function fromMonacoWithFirebase(
userName
);

const editorAdapter = new MonacoAdapter(editor, false);
const editorAdapter = new MonacoAdapter(editor, false, editorUtils);
return new Firepad(databaseAdapter, editorAdapter, {
userId,
userName,
Expand Down
65 changes: 54 additions & 11 deletions src/firepad.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { editor } from "monaco-editor";
import { Cursor } from "./cursor";
import {
DatabaseAdapterEvent,
Expand All @@ -13,6 +14,8 @@ import {
IEditorClientEvent,
} from "./editor-client";
import { EventEmitter, EventListenerType, IEventEmitter } from "./emitter";
import { FirebaseAdapter } from "./firebase-adapter";
import { MonacoAdapter } from "./monaco-adapter";
import * as Utils from "./utils";

export enum FirepadEvent {
Expand Down Expand Up @@ -102,13 +105,29 @@ export interface IFirepad extends Utils.IDisposable {
* @param option - Configuration option (same as constructor).
*/
getConfiguration(option: keyof IFirepadConstructorOptions): any;
/**
*Enable firepad if it is disabled.
*/
enable(): void;
/**
* Disable firepad without destroying it.
*/
disable(): void;

beforeApplyChanges(
callback: (changes: editor.IIdentifiedSingleEditOperation[]) => void
): void;

afterApplyChanges(
callback: (changes: editor.IIdentifiedSingleEditOperation[]) => void
): void;
}

export class Firepad implements IFirepad {
protected readonly _options: IFirepadConstructorOptions;
protected readonly _editorClient: IEditorClient;
protected readonly _editorAdapter: IEditorAdapter;
protected readonly _databaseAdapter: IDatabaseAdapter;
protected readonly _editorAdapter: MonacoAdapter;
protected readonly _databaseAdapter: FirebaseAdapter;

protected _ready: boolean;
protected _zombie: boolean;
Expand All @@ -134,8 +153,8 @@ export class Firepad implements IFirepad {
this._zombie = false;
this._options = options;

this._databaseAdapter = databaseAdapter;
this._editorAdapter = editorAdapter;
this._databaseAdapter = databaseAdapter as FirebaseAdapter;
this._editorAdapter = editorAdapter as MonacoAdapter;
this._editorClient = new EditorClient(databaseAdapter, editorAdapter);

this._emitter = new EventEmitter([
Expand All @@ -151,15 +170,17 @@ export class Firepad implements IFirepad {

protected _init(): void {
this._databaseAdapter.on(DatabaseAdapterEvent.Ready, () => {
this._ready = true;
if (!this._zombie) {
this._ready = true;

const { defaultText } = this._options;
if (defaultText && this.isHistoryEmpty()) {
this.setText(defaultText);
this.clearUndoRedoStack();
}
const { defaultText } = this._options;
if (defaultText && this.isHistoryEmpty()) {
this.setText(defaultText);
this.clearUndoRedoStack();
}

this._trigger(FirepadEvent.Ready, true);
this._trigger(FirepadEvent.Ready, true);
}
});

this._editorClient.on(
Expand Down Expand Up @@ -203,6 +224,28 @@ export class Firepad implements IFirepad {
);
}

public enable() {
this._databaseAdapter.enable();
this._editorAdapter.enable();
}

public disable() {
this._databaseAdapter.disable();
this._editorAdapter.disable();
}

public beforeApplyChanges(
callback: (changes: editor.IIdentifiedSingleEditOperation[]) => void
) {
this._editorAdapter.beforeApplyChanges(callback);
}

public afterApplyChanges(
callback: (changes: editor.IIdentifiedSingleEditOperation[]) => void
) {
this._editorAdapter.afterApplyChanges(callback);
}

getConfiguration(option: keyof IFirepadConstructorOptions): any {
return option in this._options ? this._options[option] : null;
}
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ export * from "./firepad";
export * from "./firepad-monaco";
export * from "./monaco-adapter";
export * from "./text-operation";
export * from "./monaco-editor-utils";

export { Firepad as default } from "./firepad";
Loading