mirror of
https://github.com/codex-team/editor.js
synced 2024-06-01 21:42:26 +02:00
Compare commits
2 commits
5eafda5ec4
...
238c909016
Author | SHA1 | Date | |
---|---|---|---|
238c909016 | |||
23858e0025 |
|
@ -2,17 +2,19 @@
|
||||||
|
|
||||||
### 2.30.0
|
### 2.30.0
|
||||||
|
|
||||||
– `New` – Block Tunes now supports nesting items
|
- `New` – Block Tunes now supports nesting items
|
||||||
– `New` – Block Tunes now supports separator items
|
- `New` – Block Tunes now supports separator items
|
||||||
– `New` – "Convert to" control is now also available in Block Tunes
|
- `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)
|
- `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.
|
- `Fix` — `onChange` will be called when removing the entire text within a descendant element of a block.
|
||||||
- `Fix` - Unexpected new line on Enter press with selected block without caret
|
- `Fix` - Unexpected new line on Enter press with selected block without caret
|
||||||
- `Fix` - Search input autofocus loosing after Block Tunes opening
|
- `Fix` - Search input autofocus loosing after Block Tunes opening
|
||||||
- `Fix` - Block removing while Enter press on Block Tunes
|
- `Fix` - Block removing while Enter press on Block Tunes
|
||||||
– `Fix` – Unwanted scroll on first typing on iOS devices
|
- `Fix` – Unwanted scroll on first typing on iOS devices
|
||||||
- `Fix` - Unwanted soft line break on Enter press after period and space (". |") on iOS devices
|
- `Fix` - Unwanted soft line break on Enter press after period and space (". |") on iOS devices
|
||||||
- `Fix` - Caret lost after block conversion on mobile devices.
|
- `Fix` - Caret lost after block conversion on mobile devices.
|
||||||
|
- `Improvement` - The API `blocks.convert()` now returns the new block API
|
||||||
|
- `Improvement` - The API `caret.setToBlock()` now can accept either BlockAPI or block index or block id
|
||||||
|
|
||||||
### 2.29.1
|
### 2.29.1
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@editorjs/editorjs",
|
"name": "@editorjs/editorjs",
|
||||||
"version": "2.30.0-rc.7",
|
"version": "2.30.0-rc.8",
|
||||||
"description": "Editor.js — Native JS, based on API and Open Source",
|
"description": "Editor.js — Native JS, based on API and Open Source",
|
||||||
"main": "dist/editorjs.umd.js",
|
"main": "dist/editorjs.umd.js",
|
||||||
"module": "dist/editorjs.mjs",
|
"module": "dist/editorjs.mjs",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { BlockAPI as BlockAPIInterface, Blocks } from '../../../../types/api';
|
import type { BlockAPI as BlockAPIInterface, Blocks } from '../../../../types/api';
|
||||||
import { BlockToolData, OutputBlockData, OutputData, ToolConfig } from '../../../../types';
|
import { BlockToolData, OutputBlockData, OutputData, ToolConfig } from '../../../../types';
|
||||||
import * as _ from './../../utils';
|
import * as _ from './../../utils';
|
||||||
import BlockAPI from '../../block/api';
|
import BlockAPI from '../../block/api';
|
||||||
|
@ -327,7 +327,7 @@ export default class BlocksAPI extends Module {
|
||||||
* @param dataOverrides - optional data overrides for the new block
|
* @param dataOverrides - optional data overrides for the new block
|
||||||
* @throws Error if conversion is not possible
|
* @throws Error if conversion is not possible
|
||||||
*/
|
*/
|
||||||
private convert = (id: string, newType: string, dataOverrides?: BlockToolData): void => {
|
private convert = async (id: string, newType: string, dataOverrides?: BlockToolData): Promise<BlockAPIInterface> => {
|
||||||
const { BlockManager, Tools } = this.Editor;
|
const { BlockManager, Tools } = this.Editor;
|
||||||
const blockToConvert = BlockManager.getBlockById(id);
|
const blockToConvert = BlockManager.getBlockById(id);
|
||||||
|
|
||||||
|
@ -346,7 +346,9 @@ export default class BlocksAPI extends Module {
|
||||||
const targetBlockConvertable = targetBlockTool.conversionConfig?.import !== undefined;
|
const targetBlockConvertable = targetBlockTool.conversionConfig?.import !== undefined;
|
||||||
|
|
||||||
if (originalBlockConvertable && targetBlockConvertable) {
|
if (originalBlockConvertable && targetBlockConvertable) {
|
||||||
BlockManager.convert(blockToConvert, newType, dataOverrides);
|
const newBlock = await BlockManager.convert(blockToConvert, newType, dataOverrides);
|
||||||
|
|
||||||
|
return new BlockAPI(newBlock);
|
||||||
} else {
|
} else {
|
||||||
const unsupportedBlockTypes = [
|
const unsupportedBlockTypes = [
|
||||||
!originalBlockConvertable ? capitalize(blockToConvert.name) : false,
|
!originalBlockConvertable ? capitalize(blockToConvert.name) : false,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { Caret } from '../../../../types/api';
|
import { BlockAPI, Caret } from '../../../../types/api';
|
||||||
import Module from '../../__module';
|
import Module from '../../__module';
|
||||||
|
import { resolveBlock } from '../../utils/api';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class CaretAPI
|
* @class CaretAPI
|
||||||
|
@ -96,21 +97,23 @@ export default class CaretAPI extends Module {
|
||||||
/**
|
/**
|
||||||
* Sets caret to the Block by passed index
|
* Sets caret to the Block by passed index
|
||||||
*
|
*
|
||||||
* @param {number} index - index of Block where to set caret
|
* @param blockOrIdOrIndex - either BlockAPI or Block id or Block index
|
||||||
* @param {string} position - position where to set caret
|
* @param position - position where to set caret
|
||||||
* @param {number} offset - caret offset
|
* @param offset - caret offset
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
private setToBlock = (
|
private setToBlock = (
|
||||||
index: number,
|
blockOrIdOrIndex: BlockAPI | BlockAPI['id'] | number,
|
||||||
position: string = this.Editor.Caret.positions.DEFAULT,
|
position: string = this.Editor.Caret.positions.DEFAULT,
|
||||||
offset = 0
|
offset = 0
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (!this.Editor.BlockManager.blocks[index]) {
|
const block = resolveBlock(blockOrIdOrIndex, this.Editor);
|
||||||
|
|
||||||
|
if (block === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.Editor.Caret.setToBlock(this.Editor.BlockManager.blocks[index], position, offset);
|
this.Editor.Caret.setToBlock(block, position, offset);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
|
@ -183,16 +183,14 @@ export default class ConversionToolbar extends Module<ConversionToolbarNodes> {
|
||||||
public async replaceWithBlock(replacingToolName: string, blockDataOverrides?: BlockToolData): Promise<void> {
|
public async replaceWithBlock(replacingToolName: string, blockDataOverrides?: BlockToolData): Promise<void> {
|
||||||
const { BlockManager, BlockSelection, InlineToolbar, Caret } = this.Editor;
|
const { BlockManager, BlockSelection, InlineToolbar, Caret } = this.Editor;
|
||||||
|
|
||||||
BlockManager.convert(this.Editor.BlockManager.currentBlock, replacingToolName, blockDataOverrides);
|
const newBlock = await BlockManager.convert(this.Editor.BlockManager.currentBlock, replacingToolName, blockDataOverrides);
|
||||||
|
|
||||||
BlockSelection.clearSelection();
|
BlockSelection.clearSelection();
|
||||||
|
|
||||||
this.close();
|
this.close();
|
||||||
InlineToolbar.close();
|
InlineToolbar.close();
|
||||||
|
|
||||||
window.requestAnimationFrame(() => {
|
Caret.setToBlock(newBlock, Caret.positions.END);
|
||||||
Caret.setToBlock(this.Editor.BlockManager.currentBlock, Caret.positions.END);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -427,6 +427,10 @@ export default class InlineToolbar extends Module<InlineToolbarNodes> {
|
||||||
|
|
||||||
this.nodes.togglerAndButtonsWrapper.appendChild(this.nodes.conversionToggler);
|
this.nodes.togglerAndButtonsWrapper.appendChild(this.nodes.conversionToggler);
|
||||||
|
|
||||||
|
if (import.meta.env.MODE === 'test') {
|
||||||
|
this.nodes.conversionToggler.setAttribute('data-cy', 'conversion-toggler');
|
||||||
|
}
|
||||||
|
|
||||||
this.listeners.on(this.nodes.conversionToggler, 'click', () => {
|
this.listeners.on(this.nodes.conversionToggler, 'click', () => {
|
||||||
this.Editor.ConversionToolbar.toggle((conversionToolbarOpened) => {
|
this.Editor.ConversionToolbar.toggle((conversionToolbarOpened) => {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -356,7 +356,7 @@ export default class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
||||||
Shortcuts.add({
|
Shortcuts.add({
|
||||||
name: shortcut,
|
name: shortcut,
|
||||||
on: this.api.ui.nodes.redactor,
|
on: this.api.ui.nodes.redactor,
|
||||||
handler: (event: KeyboardEvent) => {
|
handler: async (event: KeyboardEvent) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
|
const currentBlockIndex = this.api.blocks.getCurrentBlockIndex();
|
||||||
|
@ -368,11 +368,9 @@ export default class Toolbox extends EventsDispatcher<ToolboxEventMap> {
|
||||||
*/
|
*/
|
||||||
if (currentBlock) {
|
if (currentBlock) {
|
||||||
try {
|
try {
|
||||||
this.api.blocks.convert(currentBlock.id, toolName);
|
const newBlock = await this.api.blocks.convert(currentBlock.id, toolName);
|
||||||
|
|
||||||
window.requestAnimationFrame(() => {
|
this.api.caret.setToBlock(newBlock, 'end');
|
||||||
this.api.caret.setToBlock(currentBlockIndex, 'end');
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
|
|
21
src/components/utils/api.ts
Normal file
21
src/components/utils/api.ts
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import type { BlockAPI } from '../../../types/api/block';
|
||||||
|
import { EditorModules } from '../../types-internal/editor-modules';
|
||||||
|
import Block from '../block';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Block instance by passed Block index or Block id
|
||||||
|
*
|
||||||
|
* @param attribute - either BlockAPI or Block id or Block index
|
||||||
|
* @param editor - Editor instance
|
||||||
|
*/
|
||||||
|
export function resolveBlock(attribute: BlockAPI | BlockAPI['id'] | number, editor: EditorModules): Block | undefined {
|
||||||
|
if (typeof attribute === 'number') {
|
||||||
|
return editor.BlockManager.getBlockByIndex(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof attribute === 'string') {
|
||||||
|
return editor.BlockManager.getBlockById(attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
return editor.BlockManager.getBlockById(attribute.id);
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import type EditorJS from '../../../../types/index';
|
import type EditorJS from '../../../../types/index';
|
||||||
import { ConversionConfig, ToolboxConfig } from '../../../../types';
|
import type { ConversionConfig, ToolboxConfig } from '../../../../types';
|
||||||
import ToolMock from '../../fixtures/tools/ToolMock';
|
import ToolMock from '../../fixtures/tools/ToolMock';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -202,7 +202,7 @@ describe('api.blocks', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('.convert()', function () {
|
describe('.convert()', function () {
|
||||||
it('should convert a Block to another type if original Tool has "conversionConfig.export" and target Tool has "conversionConfig.import"', function () {
|
it('should convert a Block to another type if original Tool has "conversionConfig.export" and target Tool has "conversionConfig.import". Should return BlockAPI as well.', function () {
|
||||||
/**
|
/**
|
||||||
* Mock of Tool with conversionConfig
|
* Mock of Tool with conversionConfig
|
||||||
*/
|
*/
|
||||||
|
@ -246,20 +246,28 @@ describe('api.blocks', () => {
|
||||||
existingBlock,
|
existingBlock,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
}).then((editor) => {
|
}).then(async (editor) => {
|
||||||
const { convert } = editor.blocks;
|
const { convert } = editor.blocks;
|
||||||
|
|
||||||
convert(existingBlock.id, 'convertableTool');
|
const returnValue = await convert(existingBlock.id, 'convertableTool');
|
||||||
|
|
||||||
// wait for block to be converted
|
// wait for block to be converted
|
||||||
cy.wait(100).then(() => {
|
cy.wait(100).then(async () => {
|
||||||
/**
|
/**
|
||||||
* Check that block was converted
|
* Check that block was converted
|
||||||
*/
|
*/
|
||||||
editor.save().then(( { blocks }) => {
|
const { blocks } = await editor.save();
|
||||||
expect(blocks.length).to.eq(1);
|
|
||||||
expect(blocks[0].type).to.eq('convertableTool');
|
expect(blocks.length).to.eq(1);
|
||||||
expect(blocks[0].data.text).to.eq(existingBlock.data.text);
|
expect(blocks[0].type).to.eq('convertableTool');
|
||||||
|
expect(blocks[0].data.text).to.eq(existingBlock.data.text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that returned value is BlockAPI
|
||||||
|
*/
|
||||||
|
expect(returnValue).to.containSubset({
|
||||||
|
name: 'convertableTool',
|
||||||
|
id: blocks[0].id,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -274,9 +282,10 @@ describe('api.blocks', () => {
|
||||||
const fakeId = 'WRNG_ID';
|
const fakeId = 'WRNG_ID';
|
||||||
const { convert } = editor.blocks;
|
const { convert } = editor.blocks;
|
||||||
|
|
||||||
const exec = (): void => convert(fakeId, 'convertableTool');
|
return convert(fakeId, 'convertableTool')
|
||||||
|
.catch((error) => {
|
||||||
expect(exec).to.throw(`Block with id "${fakeId}" not found`);
|
expect(error.message).to.be.eq(`Block with id "${fakeId}" not found`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -302,9 +311,10 @@ describe('api.blocks', () => {
|
||||||
const nonexistingToolName = 'WRNG_TOOL_NAME';
|
const nonexistingToolName = 'WRNG_TOOL_NAME';
|
||||||
const { convert } = editor.blocks;
|
const { convert } = editor.blocks;
|
||||||
|
|
||||||
const exec = (): void => convert(existingBlock.id, nonexistingToolName);
|
return convert(existingBlock.id, nonexistingToolName)
|
||||||
|
.catch((error) => {
|
||||||
expect(exec).to.throw(`Block Tool with type "${nonexistingToolName}" not found`);
|
expect(error.message).to.be.eq(`Block Tool with type "${nonexistingToolName}" not found`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -340,9 +350,10 @@ describe('api.blocks', () => {
|
||||||
*/
|
*/
|
||||||
const { convert } = editor.blocks;
|
const { convert } = editor.blocks;
|
||||||
|
|
||||||
const exec = (): void => convert(existingBlock.id, 'nonConvertableTool');
|
return convert(existingBlock.id, 'nonConvertableTool')
|
||||||
|
.catch((error) => {
|
||||||
expect(exec).to.throw(`Conversion from "paragraph" to "nonConvertableTool" is not possible. NonConvertableTool tool(s) should provide a "conversionConfig"`);
|
expect(error.message).to.be.eq(`Conversion from "paragraph" to "nonConvertableTool" is not possible. NonConvertableTool tool(s) should provide a "conversionConfig"`);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
113
test/cypress/tests/api/caret.cy.ts
Normal file
113
test/cypress/tests/api/caret.cy.ts
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
import EditorJS from '../../../../types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test cases for Caret API
|
||||||
|
*/
|
||||||
|
describe('Caret API', () => {
|
||||||
|
const paragraphDataMock = {
|
||||||
|
id: 'bwnFX5LoX7',
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'The first block content mock.',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('.setToBlock()', () => {
|
||||||
|
/**
|
||||||
|
* The arrange part of the following tests are the same:
|
||||||
|
* - create an editor
|
||||||
|
* - move caret out of the block by default
|
||||||
|
*/
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.createEditor({
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
paragraphDataMock,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}).as('editorInstance');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Blur caret from the block before setting via api
|
||||||
|
*/
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.click();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set caret to a block (and return true) if block index is passed as argument', () => {
|
||||||
|
cy.get<EditorJS>('@editorInstance')
|
||||||
|
.then(async (editor) => {
|
||||||
|
const returnedValue = editor.caret.setToBlock(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that caret belongs block
|
||||||
|
*/
|
||||||
|
cy.window()
|
||||||
|
.then((window) => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-block')
|
||||||
|
.first()
|
||||||
|
.should(($block) => {
|
||||||
|
expect($block[0].contains(range.startContainer)).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(returnedValue).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set caret to a block (and return true) if block id is passed as argument', () => {
|
||||||
|
cy.get<EditorJS>('@editorInstance')
|
||||||
|
.then(async (editor) => {
|
||||||
|
const returnedValue = editor.caret.setToBlock(paragraphDataMock.id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that caret belongs block
|
||||||
|
*/
|
||||||
|
cy.window()
|
||||||
|
.then((window) => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-block')
|
||||||
|
.first()
|
||||||
|
.should(($block) => {
|
||||||
|
expect($block[0].contains(range.startContainer)).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(returnedValue).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should set caret to a block (and return true) if Block API is passed as argument', () => {
|
||||||
|
cy.get<EditorJS>('@editorInstance')
|
||||||
|
.then(async (editor) => {
|
||||||
|
const block = editor.blocks.getById(paragraphDataMock.id);
|
||||||
|
const returnedValue = editor.caret.setToBlock(block);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that caret belongs block
|
||||||
|
*/
|
||||||
|
cy.window()
|
||||||
|
.then((window) => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-block')
|
||||||
|
.first()
|
||||||
|
.should(($block) => {
|
||||||
|
expect($block[0].contains(range.startContainer)).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(returnedValue).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,3 +1,5 @@
|
||||||
|
import Header from '@editorjs/header';
|
||||||
|
|
||||||
describe('Inline Toolbar', () => {
|
describe('Inline Toolbar', () => {
|
||||||
it('should appear aligned with left coord of selection rect', () => {
|
it('should appear aligned with left coord of selection rect', () => {
|
||||||
cy.createEditor({
|
cy.createEditor({
|
||||||
|
@ -73,4 +75,56 @@ describe('Inline Toolbar', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Conversion toolbar', () => {
|
||||||
|
it('should restore caret after converting of a block', () => {
|
||||||
|
cy.createEditor({
|
||||||
|
tools: {
|
||||||
|
header: {
|
||||||
|
class: Header,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'Some text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-paragraph')
|
||||||
|
.selectText('Some text');
|
||||||
|
|
||||||
|
cy.get('[data-cy=conversion-toggler]')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-conversion-tool[data-tool=header]')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-header')
|
||||||
|
.should('have.text', 'Some text');
|
||||||
|
|
||||||
|
cy.window()
|
||||||
|
.then((window) => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
|
||||||
|
expect(selection.rangeCount).to.be.equal(1);
|
||||||
|
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-header')
|
||||||
|
.should(($block) => {
|
||||||
|
expect($block[0].contains(range.startContainer)).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -287,5 +287,61 @@ describe('BlockTunes', function () {
|
||||||
.contains('Title 2')
|
.contains('Title 2')
|
||||||
.should('exist');
|
.should('exist');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should convert block to another type and set caret to the new block', () => {
|
||||||
|
cy.createEditor({
|
||||||
|
tools: {
|
||||||
|
header: Header,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
blocks: [
|
||||||
|
{
|
||||||
|
type: 'paragraph',
|
||||||
|
data: {
|
||||||
|
text: 'Some text',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Open block tunes menu */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.cdx-block')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-toolbar__settings-btn')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/** Click "Convert to" option*/
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover-item')
|
||||||
|
.contains('Convert to')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/** Click "Heading" option */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-popover--nested [data-item-name=header]')
|
||||||
|
.click();
|
||||||
|
|
||||||
|
/** Check the block was converted to the second option */
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.get('.ce-header')
|
||||||
|
.should('have.text', 'Some text');
|
||||||
|
|
||||||
|
/** Check that caret set to the end of the new block */
|
||||||
|
cy.window()
|
||||||
|
.then((window) => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find('.ce-header')
|
||||||
|
.should(($block) => {
|
||||||
|
expect($block[0].contains(range.startContainer)).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ import ToolMock from '../../fixtures/tools/ToolMock';
|
||||||
|
|
||||||
describe('Toolbox', function () {
|
describe('Toolbox', function () {
|
||||||
describe('Shortcuts', function () {
|
describe('Shortcuts', function () {
|
||||||
it('should covert current Block to the Shortcuts\'s Block if both tools provides a "conversionConfig" ', function () {
|
it('should convert current Block to the Shortcuts\'s Block if both tools provides a "conversionConfig". Caret should be restored after conversion.', function () {
|
||||||
/**
|
/**
|
||||||
* Mock of Tool with conversionConfig
|
* Mock of Tool with conversionConfig
|
||||||
*/
|
*/
|
||||||
|
@ -54,6 +54,21 @@ describe('Toolbox', function () {
|
||||||
expect(blocks.length).to.eq(1);
|
expect(blocks.length).to.eq(1);
|
||||||
expect(blocks[0].type).to.eq('convertableTool');
|
expect(blocks[0].type).to.eq('convertableTool');
|
||||||
expect(blocks[0].data.text).to.eq('Some text');
|
expect(blocks[0].data.text).to.eq('Some text');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check that caret belongs to the new block after conversion
|
||||||
|
*/
|
||||||
|
cy.window()
|
||||||
|
.then((window) => {
|
||||||
|
const selection = window.getSelection();
|
||||||
|
const range = selection.getRangeAt(0);
|
||||||
|
|
||||||
|
cy.get('[data-cy=editorjs]')
|
||||||
|
.find(`.ce-block[data-id=${blocks[0].id}]`)
|
||||||
|
.should(($block) => {
|
||||||
|
expect($block[0].contains(range.startContainer)).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
2
types/api/blocks.d.ts
vendored
2
types/api/blocks.d.ts
vendored
|
@ -147,5 +147,5 @@ export interface Blocks {
|
||||||
*
|
*
|
||||||
* @throws Error if conversion is not possible
|
* @throws Error if conversion is not possible
|
||||||
*/
|
*/
|
||||||
convert(id: string, newType: string, dataOverrides?: BlockToolData): void;
|
convert(id: string, newType: string, dataOverrides?: BlockToolData): Promise<BlockAPI>;
|
||||||
}
|
}
|
||||||
|
|
10
types/api/caret.d.ts
vendored
10
types/api/caret.d.ts
vendored
|
@ -1,3 +1,5 @@
|
||||||
|
import { BlockAPI } from "./block";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes Editor`s caret API
|
* Describes Editor`s caret API
|
||||||
*/
|
*/
|
||||||
|
@ -46,13 +48,13 @@ export interface Caret {
|
||||||
/**
|
/**
|
||||||
* Sets caret to the Block by passed index
|
* Sets caret to the Block by passed index
|
||||||
*
|
*
|
||||||
* @param {number} index - index of Block where to set caret
|
* @param blockOrIdOrIndex - BlockAPI or Block id or Block index
|
||||||
* @param {string} position - position where to set caret
|
* @param position - position where to set caret
|
||||||
* @param {number} offset - caret offset
|
* @param offset - caret offset
|
||||||
*
|
*
|
||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
setToBlock(index: number, position?: 'end'|'start'|'default', offset?: number): boolean;
|
setToBlock(blockOrIdOrIndex: BlockAPI | BlockAPI['id'] | number, position?: 'end'|'start'|'default', offset?: number): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets caret to the Editor
|
* Sets caret to the Editor
|
||||||
|
|
Loading…
Reference in a new issue