Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(perf): initialisation and rendering performance optimisations #2430

Merged
merged 17 commits into from
Aug 8, 2023
Merged
15 changes: 14 additions & 1 deletion cypress.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,22 @@ export default defineConfig({
// We've imported your old cypress plugins here.
// You may want to clean this up later by importing these.
setupNodeEvents(on, config) {
return require('./test/cypress/plugins/index.ts')(on, config);
/**
* Plugin for cypress that adds better terminal output for easier debugging.
* Prints cy commands, browser console logs, cy.request and cy.intercept data. Great for your pipelines.
* https://github.com/archfz/cypress-terminal-report
*/
require('cypress-terminal-report/src/installLogsPrinter')(on);

require('./test/cypress/plugins/index.ts')(on, config);
},
specPattern: 'test/cypress/tests/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'test/cypress/support/index.ts',
},
'retries': {
// Configure retry attempts for `cypress run`
'runMode': 2,
// Configure retry attempts for `cypress open`
'openMode': 0,
},
});
11 changes: 10 additions & 1 deletion docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,21 @@
### 2.28.0

- `New` - Block ids now displayed in DOM via a data-id attribute. Could be useful for plugins that want access a Block's element by id.
- `New` - The `.convert(blockId, newType)` API method added
- `New` - The `blocks.convert(blockId, newType)` API method added. It allows to convert existed Block to a Block of another type.
- `New` - The `blocks.insertMany()` API method added. It allows to insert several Blocks to specified index.
- `Improvement` - The Delete keydown at the end of the Block will now work opposite a Backspace at the start. Next Block will be removed (if empty) or merged with the current one.
- `Improvement` - The Delete keydown will work like a Backspace when several Blocks are selected.
- `Improvement` - If we have two empty Blocks, and press Backspace at the start of the second one, the previous will be removed instead of current.
- `Improvement` - Tools shortcuts could be used to convert one Block to another.
- `Improvement` - Tools shortcuts displayed in the Conversion Toolbar
- `Improvement` - Initialization Loader has been removed.
- `Improvement` - Selection style won't override your custom style for `::selection` outside the editor.
- `Improvement` - Performance optimizations: initialization speed increased, `blocks.render()` API method optimized. Big documents will be displayed faster.
- `Improvement` - "Editor saving" log removed
- `Improvement` - "I'm ready" log removed
- `Improvement` - The stub-block style simplified.
- `Improvement` - If some Block's tool will throw an error during construction, we will show Stub block instead of skipping it during render
- `Improvement` - Call of `blocks.clear()` now will trigger onChange will "block-removed" event for all removed blocks.

### 2.27.2

Expand Down
2 changes: 2 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,8 @@

localStorage.setItem('theme', document.body.classList.contains("dark-mode") ? 'dark' : 'default');
})

window.editor = editor;
</script>
</body>
</html>
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
"@editorjs/code": "^2.7.0",
"@editorjs/delimiter": "^1.2.0",
"@editorjs/header": "^2.7.0",
"@editorjs/paragraph": "^2.9.0",
"@editorjs/paragraph": "^2.10.0",
"@editorjs/simple-image": "^1.4.1",
"@types/node": "^18.15.11",
"chai-subset": "^1.6.0",
Expand All @@ -53,6 +53,7 @@
"core-js": "3.30.0",
"cypress": "^12.9.0",
"cypress-intellij-reporter": "^0.0.7",
"cypress-terminal-report": "^5.3.2",
"eslint": "^8.37.0",
"eslint-config-codex": "^1.7.1",
"eslint-plugin-chai-friendly": "^0.7.2",
Expand Down
19 changes: 12 additions & 7 deletions src/components/block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,15 +252,20 @@ export default class Block extends EventsDispatcher<BlockEvents> {
this.holder = this.compose();

/**
* Start watching block mutations
* Bind block events in RIC for optimizing of constructing process time
*/
this.watchBlockMutations();
window.requestIdleCallback(() => {
/**
* Start watching block mutations
*/
this.watchBlockMutations();

/**
* Mutation observer doesn't track changes in "<input>" and "<textarea>"
* so we need to track focus events to update current input and clear cache.
*/
this.addInputEvents();
/**
* Mutation observer doesn't track changes in "<input>" and "<textarea>"
* so we need to track focus events to update current input and clear cache.
*/
this.addInputEvents();
});
}

/**
Expand Down
38 changes: 38 additions & 0 deletions src/components/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,44 @@ export default class Blocks {
}
}

/**
* Inserts several blocks at once
*
* @param blocks - blocks to insert
* @param index - index to insert blocks at
*/
public insertMany(blocks: Block[], index: number ): void {
const fragment = new DocumentFragment();

for (const block of blocks) {
fragment.appendChild(block.holder);
}

if (this.length > 0) {
if (index > 0) {
const previousBlockIndex = Math.min(index - 1, this.length - 1);
const previousBlock = this.blocks[previousBlockIndex];

previousBlock.holder.after(fragment);
} else if (index === 0) {
this.workingArea.prepend(fragment);
}

/**
* Insert blocks to the array at the specified index
*/
this.blocks.splice(index, 0, ...blocks);
} else {
this.blocks.push(...blocks);
this.workingArea.appendChild(fragment);
}

/**
* Call Rendered event for each block
*/
blocks.forEach((block) => block.call(BlockToolAPI.RENDERED));
}

/**
* Remove block
*
Expand Down
40 changes: 14 additions & 26 deletions src/components/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ export default class Core {
/**
* Ready promise. Resolved if Editor.js is ready to work, rejected otherwise
*/
let onReady, onFail;
let onReady: (value?: void | PromiseLike<void>) => void;
let onFail: (reason?: unknown) => void;

this.isReady = new Promise((resolve, reject) => {
onReady = resolve;
Expand All @@ -50,33 +51,22 @@ export default class Core {
.then(async () => {
this.configuration = config;

await this.validate();
await this.init();
this.validate();
this.init();
await this.start();
await this.render();

_.logLabeled('I\'m ready! (ノ◕ヮ◕)ノ*:・゚✧', 'log', '', 'color: #E24A75');
const { BlockManager, Caret, UI, ModificationsObserver } = this.moduleInstances;

setTimeout(async () => {
await this.render();
UI.checkEmptiness();
ModificationsObserver.enable();

if ((this.configuration as EditorConfig).autofocus) {
const { BlockManager, Caret } = this.moduleInstances;

Caret.setToBlock(BlockManager.blocks[0], Caret.positions.START);
BlockManager.highlightCurrentNode();
}

/**
* Remove loader, show content
*/
this.moduleInstances.UI.removeLoader();
if ((this.configuration as EditorConfig).autofocus) {
Caret.setToBlock(BlockManager.blocks[0], Caret.positions.START);
BlockManager.highlightCurrentNode();
}

/**
* Resolve this.isReady promise
*/
onReady();
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
}, 500);
onReady();
})
.catch((error) => {
_.log(`Editor.js is not ready because of ${error}`, 'error');
Expand Down Expand Up @@ -210,10 +200,8 @@ export default class Core {

/**
* Checks for required fields in Editor's config
*
* @returns {Promise<void>}
*/
public async validate(): Promise<void> {
public validate(): void {
const { holderId, holder } = this.config;

if (holderId && holder) {
Expand Down
58 changes: 55 additions & 3 deletions src/components/modules/api/blocks.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BlockAPI as BlockAPIInterface, Blocks } from '../../../../types/api';
import { BlockToolData, OutputData, ToolConfig } from '../../../../types';
import { BlockToolData, OutputBlockData, OutputData, ToolConfig } from '../../../../types';
import * as _ from './../../utils';
import BlockAPI from '../../block/api';
import Module from '../../__module';
Expand Down Expand Up @@ -32,6 +32,7 @@ export default class BlocksAPI extends Module {
stretchBlock: (index: number, status = true): void => this.stretchBlock(index, status),
insertNewBlock: (): void => this.insertNewBlock(),
insert: this.insert,
insertMany: this.insertMany,
update: this.update,
composeBlockData: this.composeBlockData,
convert: this.convert,
Expand Down Expand Up @@ -181,8 +182,12 @@ export default class BlocksAPI extends Module {
*
* @param {OutputData} data — Saved Editor data
*/
public render(data: OutputData): Promise<void> {
this.Editor.BlockManager.clear();
public async render(data: OutputData): Promise<void> {
if (data === undefined || data.blocks === undefined) {
throw new Error('Incorrect data passed to the render() method');
}

await this.Editor.BlockManager.clear();

return this.Editor.Renderer.render(data.blocks);
}
Expand Down Expand Up @@ -351,4 +356,51 @@ export default class BlocksAPI extends Module {
throw new Error(`Conversion from "${blockToConvert.name}" to "${newType}" is not possible. ${unsupportedBlockTypes} tool(s) should provide a "conversionConfig"`);
}
};


/**
* Inserts several Blocks to a specified index
*
* @param blocks - blocks data to insert
* @param index - index to insert the blocks at
*/
private insertMany = (
blocks: OutputBlockData[],
index: number = this.Editor.BlockManager.blocks.length - 1
): BlockAPIInterface[] => {
this.validateIndex(index);

const blocksToInsert = blocks.map(({ id, type, data }) => {
return this.Editor.BlockManager.composeBlock({
id,
tool: type || (this.config.defaultBlock as string),
data,
});
});

this.Editor.BlockManager.insertMany(blocksToInsert, index);

// we cast to any because our BlockAPI has no "new" signature
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return blocksToInsert.map((block) => new (BlockAPI as any)(block));
};

/**
* Validated block index and throws an error if it's invalid
*
* @param index - index to validate
*/
private validateIndex(index: unknown): void {
if (typeof index !== 'number') {
throw new Error('Index should be a number');
}

if (index < 0) {
throw new Error(`Index should be greater than or equal to 0`);
}

if (index === null) {
throw new Error(`Index should be greater than or equal to 0`);
}
}
}
10 changes: 6 additions & 4 deletions src/components/modules/blockEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,10 +454,12 @@ export default class BlockEvents extends Module {
BlockManager
.mergeBlocks(targetBlock, blockToMerge)
.then(() => {
/** Restore caret position after merge */
Caret.restoreCaret(targetBlock.pluginsContent as HTMLElement);
targetBlock.pluginsContent.normalize();
Toolbar.close();
window.requestAnimationFrame(() => {
/** Restore caret position after merge */
Caret.restoreCaret(targetBlock.pluginsContent as HTMLElement);
targetBlock.pluginsContent.normalize();
Toolbar.close();
});
});
}

Expand Down
Loading
Loading