mirror of
https://github.com/codex-team/editor.js
synced 2024-05-09 18:06:59 +02:00
Unique ids in blocks with nanoid (#1667)
* feat: Add unique ids for each block * fix: Improve code based on code review * feat(block ids): Use nanoid library for block id generation * Remove unused files * Add tests * Fix lint & test * fix: Remove unnecessary id generation, use nanoid(10) to shorten the id, add changelog and some documentation Also improved some documentation along the lines and fixed linting * Update copy-paste.spec.ts * fix id generation, add api method * Update blocks.spec.ts * update tests Co-authored-by: cobb <kebincheng@yeah.net> Co-authored-by: George Berezhnoy <gohabereg@users.noreply.github.com> Co-authored-by: Georgy Berezhnoy <gohabereg@gmail.com> Co-authored-by: Peter Savchenko <specc.dev@gmail.com>
This commit is contained in:
parent
ef0c7d76a7
commit
de364175eb
|
@ -1,5 +1,8 @@
|
|||
# Changelog
|
||||
|
||||
### 2.21.0
|
||||
- `New` - Blocks now have a unique ID [#873](https://github.com/codex-team/editor.js/issues/873)
|
||||
|
||||
### 2.20.2
|
||||
|
||||
- `Fix` — Append default Tunes if user tunes are provided for Block Tool [#1640](https://github.com/codex-team/editor.js/issues/1640)
|
||||
|
|
|
@ -197,6 +197,7 @@
|
|||
data: {
|
||||
blocks: [
|
||||
{
|
||||
id: "zcKCF1S7X8",
|
||||
type: "header",
|
||||
data: {
|
||||
text: "Editor.js",
|
||||
|
@ -205,12 +206,14 @@
|
|||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "b6ji-DvaKb",
|
||||
data : {
|
||||
text : 'Hey. Meet the new Editor. On this page you can see it in action — try to edit this text. Source code of the page contains the example of connection and configuration.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "header",
|
||||
id: "7ItVl5biRo",
|
||||
data: {
|
||||
text: "Key features",
|
||||
level: 3
|
||||
|
@ -218,6 +221,7 @@
|
|||
},
|
||||
{
|
||||
type : 'list',
|
||||
id: "SSBSguGvP7",
|
||||
data : {
|
||||
items : [
|
||||
{
|
||||
|
@ -238,6 +242,7 @@
|
|||
},
|
||||
{
|
||||
type: "header",
|
||||
id: "QZFox1m_ul",
|
||||
data: {
|
||||
text: "What does it mean «block-styled editor»",
|
||||
level: 3
|
||||
|
@ -245,18 +250,21 @@
|
|||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "bwnFX5LoX7",
|
||||
data : {
|
||||
text : 'Workspace in classic editors is made of a single contenteditable element, used to create different HTML markups. Editor.js <mark class=\"cdx-marker\">workspace consists of separate Blocks: paragraphs, headings, images, lists, quotes, etc</mark>. Each of them is an independent contenteditable element (or more complex structure) provided by Plugin and united by Editor\'s Core.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "mTrPOHAQTe",
|
||||
data : {
|
||||
text : `There are dozens of <a href="https://github.com/editor-js">ready-to-use Blocks</a> and the <a href="https://editorjs.io/creating-a-block-tool">simple API</a> for creation any Block you need. For example, you can implement Blocks for Tweets, Instagram posts, surveys and polls, CTA-buttons and even games.`
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "header",
|
||||
id: "1sYMhUrznu",
|
||||
data: {
|
||||
text: "What does it mean clean data output",
|
||||
level: 3
|
||||
|
@ -264,34 +272,40 @@
|
|||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "jpd7WEXrJG",
|
||||
data : {
|
||||
text : 'Classic WYSIWYG-editors produce raw HTML-markup with both content data and content appearance. On the contrary, Editor.js outputs JSON object with data of each Block. You can see an example below'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "0lOGNUKxqt",
|
||||
data : {
|
||||
text : `Given data can be used as you want: render with HTML for <code class="inline-code">Web clients</code>, render natively for <code class="inline-code">mobile apps</code>, create markup for <code class="inline-code">Facebook Instant Articles</code> or <code class="inline-code">Google AMP</code>, generate an <code class="inline-code">audio version</code> and so on.`
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "WvX7kBjp0I",
|
||||
data : {
|
||||
text : 'Clean data is useful to sanitize, validate and process on the backend.'
|
||||
}
|
||||
},
|
||||
{
|
||||
type : 'delimiter',
|
||||
id: "H9LWKQ3NYd",
|
||||
data : {}
|
||||
},
|
||||
{
|
||||
type : 'paragraph',
|
||||
id: "h298akk2Ad",
|
||||
data : {
|
||||
text : 'We have been working on this project more than three years. Several large media projects help us to test and debug the Editor, to make its core more stable. At the same time we significantly improved the API. Now, it can be used to create any plugin for any task. Hope you enjoy. 😏'
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'image',
|
||||
id: "9802bjaAA2",
|
||||
data: {
|
||||
url: 'assets/codex2x.png',
|
||||
caption: '',
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
"svg": "svg-sprite-generate -d src/assets/ -o dist/sprite.svg",
|
||||
"pull_tools": "git submodule update --init --recursive",
|
||||
"checkout_tools": "git submodule foreach git pull origin master",
|
||||
"test:e2e": "cypress run"
|
||||
"test:e2e": "yarn build && cypress run"
|
||||
},
|
||||
"author": "CodeX",
|
||||
"license": "Apache-2.0",
|
||||
|
@ -87,6 +87,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"codex-notifier": "^1.1.2",
|
||||
"codex-tooltip": "^1.0.2"
|
||||
"codex-tooltip": "^1.0.2",
|
||||
"nanoid": "^3.1.22"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,14 @@ function BlockAPI(
|
|||
block: Block
|
||||
): void {
|
||||
const blockAPI: BlockAPIInterface = {
|
||||
/**
|
||||
* Block id
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
get id(): string {
|
||||
return block.id;
|
||||
},
|
||||
/**
|
||||
* Tool name
|
||||
*
|
||||
|
|
|
@ -23,6 +23,11 @@ import ToolsCollection from '../tools/collection';
|
|||
* Interface describes Block class constructor argument
|
||||
*/
|
||||
interface BlockConstructorOptions {
|
||||
/**
|
||||
* Block's id. Should be passed for existed block, and omitted for a new one.
|
||||
*/
|
||||
id?: string;
|
||||
|
||||
/**
|
||||
* Initial Block data
|
||||
*/
|
||||
|
@ -98,6 +103,11 @@ export default class Block {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Block unique identifier
|
||||
*/
|
||||
public id: string;
|
||||
|
||||
/**
|
||||
* Block Tool`s name
|
||||
*/
|
||||
|
@ -206,13 +216,14 @@ export default class Block {
|
|||
|
||||
/**
|
||||
* @param {object} options - block constructor options
|
||||
* @param {string} [options.id] - block's id. Will be generated if omitted.
|
||||
* @param {BlockToolData} options.data - Tool's initial data
|
||||
* @param {BlockToolConstructable} options.Tool — Tool's class
|
||||
* @param {ToolSettings} options.settings - default tool's config
|
||||
* @param {BlockToolConstructable} options.tool — block's tool
|
||||
* @param options.api - Editor API module for pass it to the Block Tunes
|
||||
* @param {boolean} options.readOnly - Read-Only flag
|
||||
*/
|
||||
constructor({
|
||||
id = _.generateBlockId(),
|
||||
data,
|
||||
tool,
|
||||
api,
|
||||
|
@ -220,6 +231,7 @@ export default class Block {
|
|||
tunesData,
|
||||
}: BlockConstructorOptions) {
|
||||
this.name = tool.name;
|
||||
this.id = id;
|
||||
this.settings = tool.settings;
|
||||
this.config = tool.settings.config || {};
|
||||
this.api = api;
|
||||
|
@ -567,6 +579,7 @@ export default class Block {
|
|||
measuringEnd = window.performance.now();
|
||||
|
||||
return {
|
||||
id: this.id,
|
||||
tool: this.name,
|
||||
data: finishedExtraction,
|
||||
tunes: tunesData,
|
||||
|
|
|
@ -23,6 +23,7 @@ export default class BlocksAPI extends Module {
|
|||
swap: (fromIndex: number, toIndex: number): void => this.swap(fromIndex, toIndex),
|
||||
move: (toIndex: number, fromIndex?: number): void => this.move(toIndex, fromIndex),
|
||||
getBlockByIndex: (index: number): BlockAPIInterface | void => this.getBlockByIndex(index),
|
||||
getById: (id: string): BlockAPIInterface | null => this.getById(id),
|
||||
getCurrentBlockIndex: (): number => this.getCurrentBlockIndex(),
|
||||
getBlocksCount: (): number => this.getBlocksCount(),
|
||||
stretchBlock: (index: number, status = true): void => this.stretchBlock(index, status),
|
||||
|
@ -66,6 +67,23 @@ export default class BlocksAPI extends Module {
|
|||
return new BlockAPI(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns BlockAPI object by Block id
|
||||
*
|
||||
* @param id - id of block to get
|
||||
*/
|
||||
public getById(id: string): BlockAPIInterface | null {
|
||||
const block = this.Editor.BlockManager.getBlockById(id);
|
||||
|
||||
if (block === undefined) {
|
||||
_.logLabeled('There is no block with id `' + id + '`', 'warn');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return new BlockAPI(block);
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Block Manager method that swap Blocks
|
||||
*
|
||||
|
|
|
@ -216,6 +216,7 @@ export default class BlockManager extends Module {
|
|||
*
|
||||
* @param {object} options - block creation options
|
||||
* @param {string} options.tool - tools passed in editor config {@link EditorConfig#tools}
|
||||
* @param {string} [options.id] - unique id for this block
|
||||
* @param {BlockToolData} [options.data] - constructor params
|
||||
*
|
||||
* @returns {Block}
|
||||
|
@ -223,11 +224,13 @@ export default class BlockManager extends Module {
|
|||
public composeBlock({
|
||||
tool: name,
|
||||
data = {},
|
||||
id = undefined,
|
||||
tunes: tunesData = {},
|
||||
}: {tool: string; data?: BlockToolData; tunes?: {[name: string]: BlockTuneData}}): Block {
|
||||
}: {tool: string; id?: string; data?: BlockToolData; tunes?: {[name: string]: BlockTuneData}}): Block {
|
||||
const readOnly = this.Editor.ReadOnly.isEnabled;
|
||||
const tool = this.Editor.Tools.blockTools.get(name);
|
||||
const block = new Block({
|
||||
id,
|
||||
data,
|
||||
tool,
|
||||
api: this.Editor.API,
|
||||
|
@ -246,15 +249,17 @@ export default class BlockManager extends Module {
|
|||
* Insert new block into _blocks
|
||||
*
|
||||
* @param {object} options - insert options
|
||||
* @param {string} options.tool - plugin name, by default method inserts the default block type
|
||||
* @param {object} options.data - plugin data
|
||||
* @param {number} options.index - index where to insert new Block
|
||||
* @param {boolean} options.needToFocus - flag shows if needed to update current Block index
|
||||
* @param {boolean} options.replace - flag shows if block by passed index should be replaced with inserted one
|
||||
* @param {string} [options.id] - block's unique id
|
||||
* @param {string} [options.tool] - plugin name, by default method inserts the default block type
|
||||
* @param {object} [options.data] - plugin data
|
||||
* @param {number} [options.index] - index where to insert new Block
|
||||
* @param {boolean} [options.needToFocus] - flag shows if needed to update current Block index
|
||||
* @param {boolean} [options.replace] - flag shows if block by passed index should be replaced with inserted one
|
||||
*
|
||||
* @returns {Block}
|
||||
*/
|
||||
public insert({
|
||||
id = undefined,
|
||||
tool = this.config.defaultBlock,
|
||||
data = {},
|
||||
index,
|
||||
|
@ -262,6 +267,7 @@ export default class BlockManager extends Module {
|
|||
replace = false,
|
||||
tunes = {},
|
||||
}: {
|
||||
id?: string;
|
||||
tool?: string;
|
||||
data?: BlockToolData;
|
||||
index?: number;
|
||||
|
@ -276,6 +282,7 @@ export default class BlockManager extends Module {
|
|||
}
|
||||
|
||||
const block = this.composeBlock({
|
||||
id,
|
||||
tool,
|
||||
data,
|
||||
tunes,
|
||||
|
@ -514,6 +521,17 @@ export default class BlockManager extends Module {
|
|||
return this._blocks[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Block by passed id
|
||||
*
|
||||
* @param id - id of block to get
|
||||
*
|
||||
* @returns {Block}
|
||||
*/
|
||||
public getBlockById(id): Block {
|
||||
return this._blocks.array.find(block => block.id === id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Block instance by html element
|
||||
*
|
||||
|
|
|
@ -739,7 +739,7 @@ export default class Paste extends Module {
|
|||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
private insertEditorJSData(blocks: Pick<SavedData, 'data' | 'tool'>[]): void {
|
||||
private insertEditorJSData(blocks: Pick<SavedData, 'id' | 'data' | 'tool'>[]): void {
|
||||
const { BlockManager, Caret, Tools } = this.Editor;
|
||||
const sanitizedBlocks = sanitizeBlocks(blocks, (name) =>
|
||||
Tools.blockTools.get(name).sanitizeConfig
|
||||
|
|
|
@ -23,12 +23,14 @@ export default class Renderer extends Module {
|
|||
*
|
||||
* blocks: [
|
||||
* {
|
||||
* id : 'oDe-EVrGWA',
|
||||
* type : 'paragraph',
|
||||
* data : {
|
||||
* text : 'Hello from Codex!'
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* id : 'Ld5BJjJCHs',
|
||||
* type : 'paragraph',
|
||||
* data : {
|
||||
* text : 'Leave feedback if you like it!'
|
||||
|
@ -64,11 +66,12 @@ export default class Renderer extends Module {
|
|||
*/
|
||||
public async insertBlock(item: OutputBlockData): Promise<void> {
|
||||
const { Tools, BlockManager } = this.Editor;
|
||||
const { type: tool, data, tunes } = item;
|
||||
const { type: tool, data, tunes, id } = item;
|
||||
|
||||
if (Tools.available.has(tool)) {
|
||||
try {
|
||||
BlockManager.insert({
|
||||
id,
|
||||
tool,
|
||||
data,
|
||||
tunes,
|
||||
|
@ -81,6 +84,7 @@ export default class Renderer extends Module {
|
|||
/** If Tool is unavailable, create stub Block for it */
|
||||
const stubData = {
|
||||
savedData: {
|
||||
id,
|
||||
type: tool,
|
||||
data,
|
||||
},
|
||||
|
@ -94,6 +98,7 @@ export default class Renderer extends Module {
|
|||
}
|
||||
|
||||
const stub = BlockManager.insert({
|
||||
id,
|
||||
tool: Tools.stubTool,
|
||||
data: stubData,
|
||||
});
|
||||
|
|
|
@ -81,7 +81,7 @@ export default class Saver extends Module {
|
|||
|
||||
_.log('[Editor.js saving]:', 'groupCollapsed');
|
||||
|
||||
allExtractedData.forEach(({ tool, data, tunes, time, isValid }) => {
|
||||
allExtractedData.forEach(({ id, tool, data, tunes, time, isValid }) => {
|
||||
totalTime += time;
|
||||
|
||||
/**
|
||||
|
@ -108,6 +108,7 @@ export default class Saver extends Module {
|
|||
}
|
||||
|
||||
const output = {
|
||||
id,
|
||||
type: tool,
|
||||
data,
|
||||
...!_.isEmpty(tunes) && {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* Class Util
|
||||
*/
|
||||
|
||||
import { nanoid } from 'nanoid';
|
||||
import Dom from './dom';
|
||||
|
||||
/**
|
||||
|
@ -607,6 +608,15 @@ export function getValidUrl(url: string): string {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a block id
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
export function generateBlockId(): string {
|
||||
return nanoid(10);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens new Tab with passed URL
|
||||
*
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* --------------------------------------------------
|
||||
*/
|
||||
|
||||
import type { EditorConfig } from './../../../types/index';
|
||||
import type { EditorConfig, OutputData } from './../../../types/index';
|
||||
import type EditorJS from '../../../types/index';
|
||||
import Chainable = Cypress.Chainable;
|
||||
|
||||
|
@ -114,3 +114,14 @@ Cypress.Commands.add('cut', { prevSubject: true }, async (subject) => {
|
|||
|
||||
return clipboardData;
|
||||
});
|
||||
|
||||
/**
|
||||
* Calls EditorJS API render method
|
||||
*
|
||||
* @param data — data to render
|
||||
*/
|
||||
Cypress.Commands.add('render', { prevSubject: true }, async (subject: EditorJS, data: OutputData): Promise<EditorJS> => {
|
||||
await subject.render(data);
|
||||
|
||||
return subject;
|
||||
});
|
||||
|
|
9
test/cypress/support/index.d.ts
vendored
9
test/cypress/support/index.d.ts
vendored
|
@ -2,7 +2,7 @@
|
|||
// load type definitions that come with Cypress module
|
||||
/// <reference types="cypress" />
|
||||
|
||||
import type { EditorConfig } from './../../../types/index';
|
||||
import type { EditorConfig, OutputData } from './../../../types/index';
|
||||
import type EditorJS from '../../../types/index'
|
||||
|
||||
declare global {
|
||||
|
@ -40,6 +40,13 @@ declare global {
|
|||
* cy.get('div').cut().then(data => {})
|
||||
*/
|
||||
cut(): Chainable<{ [type: string]: any }>;
|
||||
|
||||
/**
|
||||
* Calls EditorJS API render method
|
||||
*
|
||||
* @param data — data to render
|
||||
*/
|
||||
render(data: OutputData): Chainable<EditorJS>;
|
||||
}
|
||||
|
||||
interface ApplicationWindow {
|
||||
|
|
53
test/cypress/tests/api/blocks.spec.ts
Normal file
53
test/cypress/tests/api/blocks.spec.ts
Normal file
|
@ -0,0 +1,53 @@
|
|||
/**
|
||||
* There will be described test cases of 'blocks.*' API
|
||||
*/
|
||||
describe('api.blocks', () => {
|
||||
const firstBlock = {
|
||||
id: 'bwnFX5LoX7',
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: 'The first block content mock.',
|
||||
},
|
||||
};
|
||||
const editorDataMock = {
|
||||
blocks: [
|
||||
firstBlock,
|
||||
],
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
if (this && this.editorInstance) {
|
||||
this.editorInstance.destroy();
|
||||
} else {
|
||||
cy.createEditor({
|
||||
data: editorDataMock,
|
||||
}).as('editorInstance');
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* api.blocks.getById(id)
|
||||
*/
|
||||
describe('.getById()', () => {
|
||||
/**
|
||||
* Check that api.blocks.getByUd(id) returns the Block for existed id
|
||||
*/
|
||||
it('should return Block API for existed id', () => {
|
||||
cy.get('@editorInstance').then(async (editor: any) => {
|
||||
const block = editor.blocks.getById(firstBlock.id);
|
||||
|
||||
expect(block).not.to.be.undefined;
|
||||
expect(block.id).to.be.eq(firstBlock.id);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Check that api.blocks.getByUd(id) returns null for the not-existed id
|
||||
*/
|
||||
it('should return null for not-existed id', () => {
|
||||
cy.get('@editorInstance').then(async (editor: any) => {
|
||||
expect(editor.blocks.getById('not-existed-id')).to.be.null;
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
130
test/cypress/tests/block-ids.spec.ts
Normal file
130
test/cypress/tests/block-ids.spec.ts
Normal file
|
@ -0,0 +1,130 @@
|
|||
import Header from '../../../example/tools/header';
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
describe.only('Block ids', () => {
|
||||
beforeEach(() => {
|
||||
if (this && this.editorInstance) {
|
||||
this.editorInstance.destroy();
|
||||
} else {
|
||||
cy.createEditor({
|
||||
tools: {
|
||||
header: Header,
|
||||
},
|
||||
}).as('editorInstance');
|
||||
}
|
||||
});
|
||||
|
||||
it('Should generate unique block ids for new blocks', () => {
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.get('div.ce-block')
|
||||
.click()
|
||||
.type('First block ')
|
||||
.type('{enter}')
|
||||
.get('div.ce-block')
|
||||
.last()
|
||||
.type('Second block ')
|
||||
.type('{enter}');
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.get('div.ce-toolbar__plus')
|
||||
.click();
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.get('li.ce-toolbox__button[data-tool=header]')
|
||||
.click();
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.get('div.ce-block')
|
||||
.last()
|
||||
.click()
|
||||
.type('Header');
|
||||
|
||||
cy.get('@editorInstance')
|
||||
.then(async (editor: any) => {
|
||||
const data = await editor.save();
|
||||
|
||||
data.blocks.forEach(block => {
|
||||
expect(typeof block.id).to.eq('string');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve passed ids', () => {
|
||||
const blocks = [
|
||||
{
|
||||
id: nanoid(),
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: 'First block',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: 'Second block',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
cy.get('@editorInstance')
|
||||
.render({
|
||||
blocks,
|
||||
});
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.get('div.ce-block')
|
||||
.first()
|
||||
.click()
|
||||
.type('{movetoend} Some more text');
|
||||
|
||||
cy.get('@editorInstance')
|
||||
.then(async (editor: any) => {
|
||||
const data = await editor.save();
|
||||
|
||||
data.blocks.forEach((block, index) => {
|
||||
expect(block.id).to.eq(blocks[index].id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should preserve passed ids if blocks were added', () => {
|
||||
const blocks = [
|
||||
{
|
||||
id: nanoid(),
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: 'First block',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: nanoid(),
|
||||
type: 'paragraph',
|
||||
data: {
|
||||
text: 'Second block',
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
cy.get('@editorInstance')
|
||||
.render({
|
||||
blocks,
|
||||
});
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.get('div.ce-block')
|
||||
.first()
|
||||
.click()
|
||||
.type('{enter}')
|
||||
.next()
|
||||
.type('Middle block');
|
||||
|
||||
cy.get('@editorInstance')
|
||||
.then(async (editor: any) => {
|
||||
const data = await editor.save();
|
||||
|
||||
expect(data.blocks[0].id).to.eq(blocks[0].id);
|
||||
expect(data.blocks[2].id).to.eq(blocks[1].id);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -126,7 +126,8 @@ describe('Copy pasting from Editor', () => {
|
|||
});
|
||||
|
||||
cy.get('[data-cy=editorjs]')
|
||||
.get('img')
|
||||
// In Edge test are performed slower, so we need to increase timeout to wait until image is loaded on the page
|
||||
.get('img', { timeout: 10000 })
|
||||
.should('have.attr', 'src', 'https://codex.so/public/app/img/external/codex2x.png');
|
||||
});
|
||||
});
|
||||
|
|
5
types/api/block.d.ts
vendored
5
types/api/block.d.ts
vendored
|
@ -5,6 +5,11 @@ import {SavedData} from '../data-formats';
|
|||
* @interface BlockAPI Describes Block API methods and properties
|
||||
*/
|
||||
export interface BlockAPI {
|
||||
/**
|
||||
* Block unique identifier
|
||||
*/
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* Tool name
|
||||
*/
|
||||
|
|
6
types/api/blocks.d.ts
vendored
6
types/api/blocks.d.ts
vendored
|
@ -54,6 +54,12 @@ export interface Blocks {
|
|||
*/
|
||||
getBlockByIndex(index: number): BlockAPI | void;
|
||||
|
||||
/**
|
||||
* Returns Block API object by passed Block id
|
||||
* @param id - id of the block
|
||||
*/
|
||||
getById(id: string): BlockAPI | null;
|
||||
|
||||
/**
|
||||
* Returns current Block index
|
||||
* @returns {number}
|
||||
|
|
2
types/data-formats/block-data.d.ts
vendored
2
types/data-formats/block-data.d.ts
vendored
|
@ -4,6 +4,7 @@ import {BlockToolData} from '../tools';
|
|||
* Tool's saved data
|
||||
*/
|
||||
export interface SavedData {
|
||||
id: string;
|
||||
tool: string;
|
||||
data: BlockToolData;
|
||||
time: number;
|
||||
|
@ -13,6 +14,7 @@ export interface SavedData {
|
|||
* Tool's data after validation
|
||||
*/
|
||||
export interface ValidatedData {
|
||||
id?: string;
|
||||
tool?: string;
|
||||
data?: BlockToolData;
|
||||
time?: number;
|
||||
|
|
8
types/data-formats/output-data.d.ts
vendored
8
types/data-formats/output-data.d.ts
vendored
|
@ -1,5 +1,5 @@
|
|||
import {BlockToolData} from '../tools';
|
||||
import {BlockTuneData} from "../block-tunes/block-tune-data";
|
||||
import {BlockTuneData} from '../block-tunes/block-tune-data';
|
||||
|
||||
/**
|
||||
* Output of one Tool
|
||||
|
@ -8,6 +8,10 @@ import {BlockTuneData} from "../block-tunes/block-tune-data";
|
|||
* @template Data - the structure describing a data object supported by the tool
|
||||
*/
|
||||
export interface OutputBlockData<Type extends string = string, Data extends object = any> {
|
||||
/**
|
||||
* Unique Id of the block
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* Tool type
|
||||
*/
|
||||
|
@ -18,7 +22,7 @@ export interface OutputBlockData<Type extends string = string, Data extends obje
|
|||
data: BlockToolData<Data>;
|
||||
|
||||
/**
|
||||
* Block Tunes data
|
||||
* Block Tunes data
|
||||
*/
|
||||
tunes?: {[name: string]: BlockTuneData};
|
||||
}
|
||||
|
|
|
@ -6248,6 +6248,11 @@ nanoid@3.1.20:
|
|||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.20.tgz#badc263c6b1dcf14b71efaa85f6ab4c1d6cfc788"
|
||||
integrity sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==
|
||||
|
||||
nanoid@^3.1.22:
|
||||
version "3.1.22"
|
||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.22.tgz#b35f8fb7d151990a8aebd5aa5015c03cf726f844"
|
||||
integrity sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==
|
||||
|
||||
nanomatch@^1.2.9:
|
||||
version "1.2.13"
|
||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||
|
|
Loading…
Reference in a new issue