Skip to content

Commit

Permalink
feat(blocks-api): blocks.update() now can update tunes as well as `…
Browse files Browse the repository at this point in the history
…data` (codex-team#2720)

* Update `tunes` data when new `tunes` data is provided

AFAIK, when you update block using `editor.blocks.update` method, only `data` attribute is merged and updated. I believe `tunes` data should be updated if provided.

* commit

* edit parameter type, move test

* restore package-lock and yarn lock

* update in api docs

* make data optional

* edit changelog

---------

Co-authored-by: Thomas <[email protected]>
Co-authored-by: Peter Savchenko <[email protected]>
  • Loading branch information
3 people authored Jul 6, 2024
1 parent 0e8cc0d commit 44c29dd
Show file tree
Hide file tree
Showing 6 changed files with 113 additions and 12 deletions.
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- `New` – Block Tunes now supports nesting items
- `New` – Block Tunes now supports separator items
- `Improvment` - The API `blocks.update` now accepts `tunes` data as optional third argument and makes `data` - block data as optional.
- `New` – "Convert to" control is now also available in Block Tunes
- `Improvement` — The ability to merge blocks of different types (if both tools provide the conversionConfig)
- `Fix``onChange` will be called when removing the entire text within a descendant element of a block.
Expand Down
2 changes: 1 addition & 1 deletion docs/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ use 'move' instead)

`insert(type?: string, data?: BlockToolData, config?: ToolConfig, index?: number, needToFocus?: boolean)` - insert new Block with passed parameters

`update(id: string, data: BlockToolData)` - updates data for the block with passed id
`update(id: string, data?: BlockToolData, tunes?: {[name: string]: BlockTuneData})` - updates block data and block tunes for the block with passed id

#### SanitizerAPI

Expand Down
10 changes: 6 additions & 4 deletions src/components/modules/api/blocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import * as _ from './../../utils';
import BlockAPI from '../../block/api';
import Module from '../../__module';
import Block from '../../block';
import { capitalize } from './../../utils';
import { capitalize } from '../../utils';
import { BlockTuneData } from '../../../../types/block-tunes/block-tune-data';

/**
* @class BlocksAPI
Expand Down Expand Up @@ -320,17 +321,18 @@ export default class BlocksAPI extends Module {
* Updates block data by id
*
* @param id - id of the block to update
* @param data - the new data
* @param data - (optional) the new data
* @param tunes - (optional) tune data
*/
public update = async (id: string, data: Partial<BlockToolData>): Promise<BlockAPIInterface> => {
public update = async (id: string, data?: Partial<BlockToolData>, tunes?: {[name: string]: BlockTuneData}): Promise<BlockAPIInterface> => {
const { BlockManager } = this.Editor;
const block = BlockManager.getBlockById(id);

if (block === undefined) {
throw new Error(`Block with id "${id}" not found`);
}

const updatedBlock = await BlockManager.update(block, data);
const updatedBlock = await BlockManager.update(block, data, tunes);

// we cast to any because our BlockAPI has no "new" signature
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down
17 changes: 12 additions & 5 deletions src/components/modules/blockManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,19 +337,26 @@ export default class BlockManager extends Module {
* Update Block data.
*
* Currently we don't have an 'update' method in the Tools API, so we just create a new block with the same id and type
* Should not trigger 'block-removed' or 'block-added' events
* Should not trigger 'block-removed' or 'block-added' events.
*
* If neither data nor tunes is provided, return the provided block instead.
*
* @param block - block to update
* @param data - new data
* @param data - (optional) new data
* @param tunes - (optional) tune data
*/
public async update(block: Block, data: Partial<BlockToolData>): Promise<Block> {
public async update(block: Block, data?: Partial<BlockToolData>, tunes?: {[name: string]: BlockTuneData}): Promise<Block> {
if (!data && !tunes) {
return block;
}

const existingData = await block.data;

const newBlock = this.composeBlock({
id: block.id,
tool: block.name,
data: Object.assign({}, existingData, data),
tunes: block.tunes,
data: Object.assign({}, existingData, data ?? {}),
tunes: tunes ?? block.tunes,
});

const blockIndex = this.getBlockIndex(block);
Expand Down
89 changes: 89 additions & 0 deletions test/cypress/tests/api/blocks.cy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type EditorJS from '../../../../types/index';
import type { ConversionConfig, ToolboxConfig } from '../../../../types';
import ToolMock from '../../fixtures/tools/ToolMock';
import {nanoid} from "nanoid";

/**
* There will be described test cases of 'blocks.*' API
Expand Down Expand Up @@ -102,6 +103,94 @@ describe('api.blocks', () => {
});
});

it('should update tune data when it is provided', () => {
/**
* Example Tune Class
*/
class ExampleTune {

protected data: object;
/**
*
* @param data
*/
constructor({ data}) {
this.data = data;
}

/**
* Tell editor.js that this Tool is a Block Tune
*
* @returns {boolean}
*/
public static get isTune(): boolean {
return true;
}

/**
* Create Tunes controls wrapper that will be appended to the Block Tunes panel
*
* @returns {Element}
*/
public render(): Element {
return document.createElement('div');
}

/**
* CSS selectors used in Tune
*/
public static get CSS(): object {
return {};
}

/**
* Returns Tune state
*
* @returns {string}
*/
public save(): object | string {
return this.data || '';
}
}


cy.createEditor({
tools: {
exampleTune: ExampleTune,
},
tunes: [ 'exampleTune' ],
data: {
blocks: [
{
id: nanoid(),
type: 'paragraph',
data: {
text: 'First block',
},
tunes: {
exampleTune: 'citation',
},
},
],
},
}).as('editorInstance');

// Update the tunes data of a block
// Check if it is updated
cy.get<EditorJS>('@editorInstance')
.then(async (editor) => {
await editor.blocks.update(editor.blocks.getBlockByIndex(0).id, null, {
exampleTune: 'test',
});
const data = await editor.save();

const actual = JSON.stringify(data.blocks[0].tunes);
const expected = JSON.stringify({ exampleTune: 'test' });

expect(actual).to.eq(expected);
});
});

/**
* When incorrect id passed, editor should not update any block
*/
Expand Down
6 changes: 4 additions & 2 deletions types/api/blocks.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Block from '../../src/components/block';
import {OutputBlockData, OutputData} from '../data-formats/output-data';
import {BlockToolData, ToolConfig} from '../tools';
import {BlockAPI} from './block';
import {BlockTuneData} from '../block-tunes/block-tune-data';

/**
* Describes methods to manipulate with Editor`s blocks
Expand Down Expand Up @@ -142,9 +143,10 @@ export interface Blocks {
* Updates block data by id
*
* @param id - id of the block to update
* @param data - the new data. Can be partial.
* @param data - (optional) the new data. Can be partial.
* @param tunes - (optional) tune data
*/
update(id: string, data: Partial<BlockToolData>): Promise<BlockAPI>;
update(id: string, data?: Partial<BlockToolData>, tunes?: {[name: string]: BlockTuneData}): Promise<BlockAPI>;

/**
* Converts block to another type. Both blocks should provide the conversionConfig.
Expand Down

0 comments on commit 44c29dd

Please sign in to comment.