Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Include any files or directories that you don't want to be copied to your
# container here (e.g., local build artifacts, temporary files, etc.).
#
# For more help, visit the .dockerignore file reference guide at
# https://docs.docker.com/go/build-context-dockerignore/

**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.next
**/.cache
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/charts
**/docker-compose*
**/compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
**/build
**/dist
**/coverage
**/reports
**/tsconfig.build.tsbuildinfo
LICENSE
**/README.md
81 changes: 81 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# syntax=docker/dockerfile:1

ARG NODE_VERSION=20.9.0

################################################################################
# Use node image for base image for all stages.
FROM node:${NODE_VERSION}-alpine AS base

# Set working directory for all build stages.
WORKDIR /usr/src/app/

RUN corepack enable
RUN corepack prepare [email protected] --activate

################################################################################
# Create a stage for installing production dependecies.
FROM base AS deps

# Download dependencies as a separate step to take advantage of Docker's caching.
COPY .yarnrc.yml package.json yarn.lock ./
COPY .yarn .yarn
COPY packages/model/package.json packages/model/package.json
COPY packages/sdk/package.json packages/sdk/package.json
COPY packages/collaboration-manager/package.json packages/collaboration-manager/package.json
COPY packages/ot-server/package.json packages/ot-server/package.json

RUN yarn workspaces focus @editorjs/ot-server

################################################################################
# Create a stage for building the application.
FROM deps AS build


# Copy the rest of the source files into the image.
COPY packages/model packages/model
COPY packages/sdk packages/sdk
COPY packages/collaboration-manager packages/collaboration-manager
COPY packages/ot-server packages/ot-server

# Run the build script.
RUN yarn workspace @editorjs/ot-server build

################################################################################
# Create a new stage to run the application with minimal runtime dependencies
# where the necessary files are copied from the build stage.
FROM base AS final

ARG NODE_ENV=production
ARG WSS_PORT=8080

# Use production node environment by default.
ENV NODE_ENV $NODE_ENV
ENV WSS_PORT $WSS_PORT

COPY --from=build /usr/src/app/.yarn /usr/src/app/.yarn
COPY --from=build /usr/src/app/package.json /usr/src/app/.yarnrc.yml /usr/src/app/yarn.lock /usr/src/app/
COPY --from=build /usr/src/app/node_modules /usr/src/app/node_modules

COPY --from=build /usr/src/app/packages/model/dist /usr/src/app/packages/model/dist
COPY --from=build /usr/src/app/packages/model/package.json /usr/src/app/packages/model/package.json

COPY --from=build /usr/src/app/packages/sdk/dist /usr/src/app/packages/sdk/dist
COPY --from=build /usr/src/app/packages/sdk/package.json /usr/src/app/packages/sdk/package.json

COPY --from=build /usr/src/app/packages/collaboration-manager/dist /usr/src/app/packages/collaboration-manager/dist
COPY --from=build /usr/src/app/packages/collaboration-manager/package.json /usr/src/app/packages/collaboration-manager/package.json

COPY --from=build /usr/src/app/packages/ot-server/dist /usr/src/app/packages/ot-server/dist
COPY --from=build /usr/src/app/packages/ot-server/package.json /usr/src/app/packages/ot-server/package.json


# Run the application as a non-root user.
USER node

RUN find ./

# Expose the port that the application listens on.
EXPOSE $WSS_PORT

# Run the application.
CMD yarn workspace @editorjs/ot-server run start
11 changes: 11 additions & 0 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
services:
wsserver:
build:
context: .
args:
NODE_ENV: production
WSS_PORT: ${WSS_PORT}
ports:
- ${WSS_PORT}:${WSS_PORT}


9 changes: 7 additions & 2 deletions packages/collaboration-manager/src/CollaborationManager.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {
BlockAddedEvent, type BlockNodeSerialized,
BlockRemovedEvent, type DocumentId,
BlockRemovedEvent,
type EditorJSModel,
EventType,
type ModelEvents,
Expand Down Expand Up @@ -59,7 +59,12 @@
this.#model = model;
this.#undoRedoManager = new UndoRedoManager();
model.addEventListener(EventType.Changed, this.#handleEvent.bind(this));
}

/**
* Connects to OT server
*/
public connect(): void {

Check warning on line 67 in packages/collaboration-manager/src/CollaborationManager.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
if (this.#config.collaborationServer === undefined) {
return;
}
Expand All @@ -79,7 +84,7 @@
}
);

void this.#client.connectDocument(this.#config.documentId! as DocumentId);
void this.#client.connectDocument(this.#model.serialized);

Check warning on line 87 in packages/collaboration-manager/src/CollaborationManager.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

/**
Expand Down
9 changes: 5 additions & 4 deletions packages/collaboration-manager/src/client/OTClient.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { DocumentId, EditorDocumentSerialized } from '@editorjs/model';
import type { EditorDocumentSerialized } from '@editorjs/model';
import { Operation, type SerializedOperation } from '../Operation.js';
import type { HandshakeMessage, HandshakePayload, Message, OperationMessage } from './Message.js';
import { MessageType } from './MessageType.js';
Expand Down Expand Up @@ -90,9 +90,9 @@ export class OTClient {
/**
* Sends handshake event to the server to connect the client to passed document
*
* @param documentId - document identifier
* @param document - serialized document data
*/
public async connectDocument(documentId: DocumentId): Promise<void> {
public async connectDocument(document: EditorDocumentSerialized): Promise<void> {
const ws = await this.#ws;

this.#handshake = new Promise(resolve => {
Expand Down Expand Up @@ -125,9 +125,10 @@ export class OTClient {
ws.send(JSON.stringify({
type: MessageType.Handshake,
payload: {
document: documentId,
document: document.identifier,
userId: this.#userId,
rev: this.#rev,
data: document,
} as HandshakePayload,
}));
}
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ export default class Core {
.then(() => {
this.#model.initializeDocument({ blocks });
})
.then(() => {
this.#collaborationManager.connect();
})
.catch((error) => {
console.error('Editor.js initialization failed', error);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
TuneModifiedEvent
} from '../../EventBus/events/index.js';
import { EventAction } from '../../EventBus/types/EventAction.js';
import { jest } from '@jest/globals';
import { describe, jest } from '@jest/globals';

jest.mock('../BlockNode');

Expand Down Expand Up @@ -50,6 +50,72 @@ describe('EditorDocument', () => {
jest.clearAllMocks();
});

describe('.initalize()', () => {
it('should initalize the document', () => {
const doc = new EditorDocument({
identifier: 'document',
properties: {
readOnly: false,
},
});

const blocks = [ {
name: 'header' as BlockToolName,
data: {
text: {
$t: 't',
value: 'some long text',
fragments: [],
},
},
tunes: {},
} ];

doc.initialize(blocks);

expect(doc.serialized.blocks).toHaveLength(blocks.length);
});

it('should clear the document before initialization', () => {
const doc = new EditorDocument({
identifier: 'document',
properties: {
readOnly: false,
},
});

const blocks = [ {
name: 'header' as BlockToolName,
data: {
text: {
$t: 't',
value: 'some long text',
fragments: [],
},
},
tunes: {},
} ];

doc.initialize(blocks);

blocks[0].data.text.value = 'another text';

doc.initialize(blocks);

expect(doc.serialized.blocks).toHaveLength(1);
});
});

describe('.clear()', () => {
it('should clear the document', () => {
const doc = createEditorDocumentWithSomeBlocks();

doc.clear();

expect(doc.serialized.blocks).toHaveLength(0);
});
});

describe('.length', () => {
it('should return the number of blocks in the document', () => {
// Arrange
Expand Down
9 changes: 9 additions & 0 deletions packages/model/src/entities/EditorDocument/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export class EditorDocument extends EventBus {
* @param blocks - document serialized blocks
*/
public initialize(blocks: BlockNodeSerialized[]): void {
this.clear();

blocks.forEach((block) => {
this.addBlock(block);
});
Expand Down Expand Up @@ -417,6 +419,13 @@ export class EditorDocument extends EventBus {
}
}

/**
* Clear all document's blocks (doesn't emit an event)
*/
public clear(): void {
Array.from(this.#children).forEach(() => this.removeBlock(0));
}

/**
* Listens to BlockNode events and bubbles them to the EditorDocument
*
Expand Down
1 change: 1 addition & 0 deletions packages/ot-server/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WSS_PORT=8080
1 change: 1 addition & 0 deletions packages/ot-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"dependencies": {
"@editorjs/collaboration-manager": "workspace:^",
"@editorjs/model": "workspace:^",
"dotenv": "^16.4.7",
"ws": "^8.18.1"
},
"devDependencies": {
Expand Down
8 changes: 8 additions & 0 deletions packages/ot-server/src/DocumentManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@
return this.#model.serialized;
}

/**
* Initialises document model with data from the first connected client
* @param data - document data to initialise
*/
public initializeDocument(...data: Parameters<EditorJSModel['initializeDocument']>): void {

Check warning on line 66 in packages/ot-server/src/DocumentManager.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🕹️ Function is not covered

Warning! Not covered function
this.#model.initializeDocument(...data);

Check warning on line 67 in packages/ot-server/src/DocumentManager.ts

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🧾 Statement is not covered

Warning! Not covered statement
}

/**
* Process next operation
* - Transform relative to operations in stack if needed
Expand Down
Loading