Skip to content

Commit

Permalink
feat(playground): playground uses editor, input rendering (#90)
Browse files Browse the repository at this point in the history
* playground uses editor

* model update and tests

* rm logs

* Update Config.ts

* review fixes

* upd docs
  • Loading branch information
neSpecc authored Aug 30, 2024
1 parent efcfe22 commit 906e7a6
Show file tree
Hide file tree
Showing 14 changed files with 229 additions and 109 deletions.
31 changes: 20 additions & 11 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EditorJSModel } from '@editorjs/model';
import { EditorJSModel, EventType } from '@editorjs/model';
import type { ContainerInstance } from 'typedi';
import { Container } from 'typedi';
import { composeDataFromVersion2 } from './utils/composeDataFromVersion2.js';
Expand Down Expand Up @@ -62,8 +62,10 @@ export default class Core {
* Inline toolbar is responsible for handling selection changes
* When model selection changes, it determines, whenever to show toolbar element,
* Which calls apply format method of the adapter
*
* null when inline toolbar is not initialized
*/
#inlineToolbar: InlineToolbar;
#inlineToolbar: InlineToolbar | null = null;

/**
* @param config - Editor configuration
Expand Down Expand Up @@ -92,19 +94,26 @@ export default class Core {
this.#formattingAdapter = new FormattingAdapter(this.#model, this.#caretAdapter);
this.#iocContainer.set(FormattingAdapter, this.#formattingAdapter);

this.#inlineToolbar = new InlineToolbar(this.#model, this.#formattingAdapter, this.#toolsManager.inlineTools, this.#config.holder);
this.#iocContainer.set(InlineToolbar, this.#inlineToolbar);
this.#iocContainer.get(BlocksManager);

this.#prepareUI();
if (config.onModelUpdate !== undefined) {
this.#model.addEventListener(EventType.Changed, () => {
config.onModelUpdate?.(this.#model);
});
}

this.#iocContainer.get(BlocksManager);
this.#prepareUI();

/**
* @todo avait when isReady API is implemented
*/
void this.#toolsManager.prepareTools();
this.#toolsManager.prepareTools()
.then(() => {
this.#inlineToolbar = new InlineToolbar(this.#model, this.#formattingAdapter, this.#toolsManager.inlineTools, this.#config.holder);
this.#iocContainer.set(InlineToolbar, this.#inlineToolbar);

this.#model.initializeDocument({ blocks });
this.#model.initializeDocument({ blocks });
})
.catch((error) => {
console.error('Editor.js initialization failed', error);
});
}

/**
Expand Down
10 changes: 9 additions & 1 deletion packages/core/src/utils/composeDataFromVersion2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ import type { OutputData } from '@editorjs/editorjs';
import type { InlineFragment } from '@editorjs/model';
import { createInlineToolData, createInlineToolName, TextNode, ValueNode, type BlockNodeSerialized } from '@editorjs/model';

/**
* Removes HTML tags from the input string
* @param input - any string with HTML tags like '<b>bold</b> <a href="https://editorjs.io">link</a>'
*/
function stripTags(input: string): string {
return input.replace(/<\/?[^>]+(>|$)/g, '');
}

/**
* Extracts inline fragments from the HTML string
* @param html - any html string like '<b>bold</b> <a href="https://editorjs.io">link</a>'
Expand Down Expand Up @@ -97,7 +105,7 @@ export function composeDataFromVersion2(data: OutputData): {
if (typeof value === 'string') {
const fragments = extractFragments(value);
const textNode = new TextNode({
value,
value: stripTags(value),
fragments,
});

Expand Down
8 changes: 6 additions & 2 deletions packages/dom-adapters/src/BlockToolAdapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,15 @@ export class BlockToolAdapter implements BlockToolAdapterInterface {
this.#caretAdapter.attachInput(input, builder.build());

try {
const value = this.#model.getText(this.#blockIndex, key);
const fragments = this.#model.getFragments(this.#blockIndex, key);

input.textContent = value;

const nodeToFormat = input.firstChild as HTMLElement; // we just set textContent, so it's always a TextNode

fragments.forEach(fragment => {
console.log('fragment', fragment);
// this.#formattingAdapter.formatElementContent(input, fragment);
this.#formattingAdapter.formatElementContent(nodeToFormat, fragment);
});
} catch (_) {
// do nothing — TextNode is not created yet as there is no initial data in the model
Expand Down
20 changes: 12 additions & 8 deletions packages/dom-adapters/src/FormattingAdapter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,21 @@ export class FormattingAdapter {

const [start, end] = index;

/**
* Create range with positions specified in index
*/
const range = document.createRange();
try {
/**
* Create range with positions specified in index
*/
const range = document.createRange();

range.setStart(input, start);
range.setEnd(input, end);
range.setStart(input, start);
range.setEnd(input, end);

const inlineElement = tool.createWrapper(toolData);
const inlineElement = tool.createWrapper(toolData);

surround(range, inlineElement);
surround(range, inlineElement);
} catch (e) {
console.error('Error while formatting element content', e);
}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions packages/model/src/EditorJSModel.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ describe('EditorJSModel', () => {
'updateValue',
'removeBlock',
'moveBlock',
'getText',
'insertText',
'removeText',
'format',
Expand All @@ -25,6 +26,7 @@ describe('EditorJSModel', () => {
'createCaret',
'updateCaret',
'removeCaret',
'devModeGetDocument',
];
const ownProperties = Object.getOwnPropertyNames(EditorJSModel.prototype);

Expand Down
20 changes: 20 additions & 0 deletions packages/model/src/EditorJSModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,17 @@ export class EditorJSModel extends EventBus {
return this.#document.updateTuneData(...parameters);

Check warning on line 234 in packages/model/src/EditorJSModel.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement
}

/**
* Returns a text from the specified block and data key
*
* @param parameters - getText method parameters
* @param parameters.blockIndex - index of the block
* @param parameters.dataKey - key of the data
*/
public getText(...parameters: Parameters<EditorDocument['getText']>): ReturnType<EditorDocument['getText']> {
return this.#document.getText(...parameters);

Check warning on line 245 in packages/model/src/EditorJSModel.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement
}

/**
* Inserts text to the specified block
*
Expand Down Expand Up @@ -303,6 +314,15 @@ export class EditorJSModel extends EventBus {
return this.#document.getFragments(...parameters);

Check warning on line 314 in packages/model/src/EditorJSModel.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement
}

/**
* Exposing document for dev-tools
*
* USE ONLY FOR DEV PURPOSES
*/
public devModeGetDocument(): EditorDocument {
return this.#document;

Check warning on line 323 in packages/model/src/EditorJSModel.ts

View workflow job for this annotation

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

🧾 Statement is not covered

Warning! Not covered statement
}

/**
* Listens to BlockNode events and bubbles re-emits them from the EditorJSModel instance
*
Expand Down
27 changes: 26 additions & 1 deletion packages/model/src/entities/BlockNode/BlockNode.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type { EditorDocument } from '../EditorDocument';
import type { ValueNodeConstructorParameters } from '../ValueNode';
import type { InlineFragment, InlineToolData, InlineToolName } from '../inline-fragments';
import { TextNode } from '../inline-fragments/index.js';
import type { BlockNodeData, BlockNodeDataSerialized } from './types';
import type { BlockNodeData, BlockNodeDataSerialized, DataKey } from './types';
import { BlockChildType } from './types/index.js';
import { NODE_TYPE_HIDDEN_PROP } from './consts.js';
import { TextAddedEvent, TuneModifiedEvent, ValueModifiedEvent } from '../../EventBus/events/index.js';
Expand Down Expand Up @@ -574,6 +574,31 @@ describe('BlockNode', () => {
});
});

describe('.getText()', () => {
it('should call .serialized getter of the TextNode', () => {
const spy = jest.spyOn(TextNode.prototype, 'serialized', 'get');
const node = createBlockNodeWithData({
text: {
[NODE_TYPE_HIDDEN_PROP]: BlockChildType.Text,
value: '',
fragments: [],
},
});

node.getText(createDataKey('text'));

expect(spy)
.toHaveBeenCalled();
});

it('should throw an error if data key is invalid', () => {
const node = createBlockNodeWithData({});

expect(() => node.getText('invalid-key' as DataKey))
.toThrow();
});
});

describe('.insertText()', () => {
const dataKey = createDataKey('text');
const text = 'Some text';
Expand Down
7 changes: 7 additions & 0 deletions packages/model/src/entities/BlockNode/__mocks__/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ export class BlockNode extends EventBus {
return;
}

/**
* Mock method
*/
public getText(): string {
return 'mocked text';
}

/**
* Mock method
*/
Expand Down
13 changes: 13 additions & 0 deletions packages/model/src/entities/BlockNode/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,19 @@ export class BlockNode extends EventBus {
node.update(value);
}

/**
* Returns a text value for the specified data key
*
* @param dataKey - key of the data
*/
public getText(dataKey: DataKey): string {
this.#validateKey(dataKey, TextNode);

const node = get(this.#data, dataKey as string) as TextNode;

return node.serialized.value;
}

/**
* Inserts text to the specified text node by index, by default appends text to the end of the current value
*
Expand Down
36 changes: 36 additions & 0 deletions packages/model/src/entities/EditorDocument/EditorDocument.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,42 @@ describe('EditorDocument', () => {
});
});

describe('.getText()', () => {
let document: EditorDocument;
const dataKey = 'text' as DataKey;
const text = 'Some text';
let block: BlockNode;

beforeEach(() => {
const blockData = {
name: 'text' as BlockToolName,
data: {
[dataKey]: text,
},
};

document = new EditorDocument();

document.initialize([ blockData ]);

block = document.getBlock(0);
});

it('should call .getText() method of the BlockNode if index and data key are correct', () => {
const spy = jest.spyOn(block, 'getText');

document.getText(0, dataKey);

expect(spy)
.toHaveBeenCalledWith(dataKey);
});

it('should throw an error if index is out of bounds', () => {
expect(() => document.getText(document.length + 1, dataKey))
.toThrow('Index out of bounds');
});
});

describe('.insertText()', () => {
let document: EditorDocument;
const dataKey = 'text' as DataKey;
Expand Down
12 changes: 12 additions & 0 deletions packages/model/src/entities/EditorDocument/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,18 @@ export class EditorDocument extends EventBus {
this.#children[blockIndex].updateTuneData(tuneName, data);
}

/**
* Returns text for the specified block and data key
*
* @param blockIndex - index of the block
* @param dataKey - key of the data containing the text
*/
public getText(blockIndex: number, dataKey: DataKey): string {
this.#checkIndexOutOfBounds(blockIndex, this.length - 1);

return this.#children[blockIndex].getText(dataKey);
}

/**
* Inserts text to the specified block
*
Expand Down
Loading

2 comments on commit 906e7a6

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for ./packages/model

St.
Category Percentage Covered / Total
🟢 Statements 96.74% 800/827
🟢 Branches 98.17% 214/218
🟢 Functions 88.07% 192/218
🟢 Lines 96.62% 772/799

Test suite run success

404 tests passing in 24 suites.

Report generated by 🧪jest coverage report action from 906e7a6

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage report for ./packages/collaboration-manager

St.
Category Percentage Covered / Total
🟢 Statements 86.11% 62/72
🟡 Branches 62.5% 15/24
🟢 Functions 100% 10/10
🟢 Lines 86.11% 62/72

Test suite run success

6 tests passing in 1 suite.

Report generated by 🧪jest coverage report action from 906e7a6

Please sign in to comment.