Merge branch 'next' into fix_split_custom_event

This commit is contained in:
Alexander Chernyaev 2023-04-06 13:23:04 +03:00 committed by GitHub
commit 9d8bf106ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
52 changed files with 666 additions and 527 deletions

2
.github/FUNDING.yml vendored
View file

@ -1,5 +1,5 @@
# These are supported funding model platforms
github: neSpecc
patreon: editorjs
open_collective: editorjs
custom: https://codex.so/donate

View file

@ -3,16 +3,13 @@ on: [pull_request]
jobs:
firefox:
runs-on: ubuntu-latest
container:
image: cypress/browsers:node14.17.0-chrome88-ff89
options: --user 1001
steps:
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- run: yarn ci:pull_paragraph
- uses: cypress-io/github-action@v2
- uses: cypress-io/github-action@v5
with:
config: video=false
browser: firefox
@ -23,22 +20,22 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- run: yarn ci:pull_paragraph
- uses: cypress-io/github-action@v2
- uses: cypress-io/github-action@v5
with:
config: video=false
browser: chrome
build: yarn build
edge:
runs-on: windows-latest
runs-on: ubuntu-latest
steps:
- uses: actions/setup-node@v3
with:
node-version: 16
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- run: yarn ci:pull_paragraph
- uses: cypress-io/github-action@v2
- uses: cypress-io/github-action@v5
with:
config: video=false
browser: edge

View file

@ -1,22 +1,6 @@
.idea
.github
docs
example
src
test
.babelrc
.editorconfig
.eslintignore
.eslintrc
.git
.gitmodules
.jshintrc
.postcssrc.yml
.stylelintrc
CODEOWNERS
cypress.json
tsconfig.json
tslint.json
webpack.config.js
yarn.lock
devserver.js
*
!/dist/**/*
!/types/**/*
!/LICENSE
!/README.md
!/package.json

19
cypress.config.ts Normal file
View file

@ -0,0 +1,19 @@
import { defineConfig } from 'cypress';
export default defineConfig({
env: {
NODE_ENV: 'test',
},
fixturesFolder: 'test/cypress/fixtures',
screenshotsFolder: 'test/cypress/screenshots',
videosFolder: 'test/cypress/videos',
e2e: {
// 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);
},
specPattern: 'test/cypress/tests/**/*.cy.{js,jsx,ts,tsx}',
supportFile: 'test/cypress/support/index.ts',
},
});

View file

@ -1,11 +0,0 @@
{
"env": {
"NODE_ENV": "test"
},
"fixturesFolder": "test/cypress/fixtures",
"integrationFolder": "test/cypress/tests",
"screenshotsFolder": "test/cypress/screenshots",
"videosFolder": "test/cypress/videos",
"supportFile": "test/cypress/support/index.ts",
"pluginsFile": "test/cypress/plugins/index.ts"
}

View file

@ -5,8 +5,14 @@
- `Refactoring` — Popover class refactored.
- `Improvement`*Toolbox* — Number of `close()` method calls optimized.
- `Improvement` — The `onChange` callback won't be triggered only if all mutations contain nodes with the `data-mutation-free` attributes.
- `Fix` — Resolve compiler error from importing the BlockToolData type
- `Fix` — Resolved a problem when document was being scrolled to the beginning after moving up a Block above the viewport
- `Fix` — Resolve compiler error from importing the BlockToolData type.
- `Fix` — Resolved a problem when document was being scrolled to the beginning after moving up a Block above the viewport.
- `Improvement` — Package size reduced by removing redundant files.
- `Fix`- Several bugs caused by random browser extensions.
- `Improvement`*Dependencies* — Upgrade TypeScript to v5.
- `Fix`*ToolsAPI*`pasteConfig` getter with `false` value could be used to disable paste handling by Editor.js core. Could be useful if your tool has its own paste handler.
- `Improvement`*Dependencies* — Upgrade Cypress to v12, upgrade related libraries to latest versions.
- `CI` — Use Ubuntu container for Edge tests runner.
### 2.26.5
@ -47,6 +53,7 @@
- `Improvement`*CodeStyle* — [CodeX ESLint Config](https://github.com/codex-team/eslint-config) has bee updated. All ESLint/Spelling issues resolved
- `Improvement`*ToolsAPI* — The `icon` property of the `toolbox` getter became optional.
### 2.25.0
- `New`*Tools API* — Introducing new feature — toolbox now can have multiple entries for one tool! <br>

View file

@ -180,7 +180,7 @@ this.api.notifier.show({
});
```
![](https://capella.pics/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg)
![](assets/14fcdbe4-d6eb-41d4-b66e-e0e86ccf1a4b.jpg)
Check out [`codex-notifier` package page](https://github.com/codex-team/js-notifier) on GitHub to find docs, params and examples.
@ -203,8 +203,6 @@ After executing the `destroy` method, editor inctance becomes an empty object. T
Methods for showing Tooltip helper near your elements. Parameters are the same as in [CodeX Tooltips](http://github.com/codex-team/codex.tooltips) lib.
![](https://capella.pics/00e7094a-fdb9-429b-8015-9c56f19b4ef5.jpg)
#### Show
Method shows tooltip with custom content on passed element

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

View file

@ -33,7 +33,7 @@ There is a [workflow](.github/workflows/publish-package-to-npm.yml) that fired o
Use target version changelog as a description.
![](https://capella.pics/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg)
![](assets/57267bab-f2f0-411b-a9d1-69abee6abab5.jpg)
Then you can publish the release and wait for package publishing via action.
@ -44,7 +44,7 @@ This package version will be published to NPM with default `latest` tag.
If you want to publish release candidate version, use suffix `-rc.*` for package
version in package.json file and in tag on releases page. Workflow will detect it and mark a release as "pre-release".
![](https://capella.pics/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg)
![](assets/796de9eb-bbe0-485c-bc8f-9a4cb76641b7.jpg)
This package version will be published to NPM with `next` tag.

View file

@ -129,8 +129,6 @@ Read more about Sanitizer configuration at the [Tools#sanitize](tools.md#sanitiz
You can pass your Tool's title via `title` static getter. It can be used, for example, in the Tooltip with
icon description that appears by hover.
![](https://capella.pics/00e7094a-fdb9-429b-8015-9c56f19b4ef5.jpg)
```ts
export default class BoldInlineTool implements InlineTool {
/**

View file

@ -410,7 +410,7 @@ static get sanitize() {
Editor.js has a Conversion Toolbar that allows user to convert one Block to another.
![](https://capella.pics/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg)
![](assets/6c1f708b-a30c-4ffd-a427-5b59a1a472e0.jpg)
1. You can add ability to your Tool to be converted. Specify «export» property of `conversionConfig`.
2. You can add ability to convert other Tools to your Tool. Specify «import» property of `conversionConfig`.

View file

@ -17,12 +17,12 @@ So how to use the Editor after [Installation](installation.md).
- Select text fragment and apply a style or insert a link from the Inline Toolbar
![](https://capella.pics/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg)
![](assets/7ccbcfcd-1c49-4674-bea7-71021468a1bd.jpg)
- Use «three-dots» button on the right to open Block Settings. From here, you can move and delete a Block
or apply Tool's settings, if it provided. For example, set a Heading level or List style.
![](https://capella.pics/01a55381-46cd-47c7-b92e-34765434f2ca.jpg)
![](assets/01a55381-46cd-47c7-b92e-34765434f2ca.jpg)
## Shortcuts

@ -1 +1 @@
Subproject commit 4a94a1592a500ebb6cc570fa1d6216a149b541a0
Subproject commit 3cc506758440ac3f1bc83008a6ef75813b6386c3

View file

@ -1,6 +1,6 @@
{
"name": "@editorjs/editorjs",
"version": "2.27.0-rc.1",
"version": "2.27.0-rc.4",
"description": "Editor.js — Native JS, based on API and Open Source",
"main": "dist/editor.js",
"types": "./types/index.d.ts",
@ -48,8 +48,8 @@
"@babel/register": "^7.9.0",
"@babel/runtime": "^7.9.2",
"@codexteam/shortcuts": "^1.1.1",
"@cypress/code-coverage": "^3.9.2",
"@cypress/webpack-preprocessor": "^5.6.0",
"@cypress/code-coverage": "^3.10.1",
"@cypress/webpack-preprocessor": "^5.17.0",
"@editorjs/code": "^2.7.0",
"@editorjs/delimiter": "^1.2.0",
"@editorjs/header": "^2.7.0",
@ -64,8 +64,8 @@
"core-js": "3.6.5",
"css-loader": "^3.5.3",
"cssnano": "^4.1.10",
"cypress": "^6.8.0",
"cypress-intellij-reporter": "^0.0.6",
"cypress": "^12.9.0",
"cypress-intellij-reporter": "^0.0.7",
"eslint": "^8.28.0",
"eslint-config-codex": "^1.7.1",
"eslint-loader": "^4.0.2",
@ -83,9 +83,9 @@
"rimraf": "^3.0.2",
"stylelint": "^13.3.3",
"terser-webpack-plugin": "^2.3.6",
"ts-loader": "^7.0.1",
"ts-loader": "^8.4.0",
"tslint": "^6.1.1",
"typescript": "3.8.3",
"typescript": "^5.0.2",
"webpack": "^4.43.0",
"webpack-cli": "^3.3.11"
},

View file

@ -153,6 +153,11 @@ export default class Block extends EventsDispatcher<BlockEvents> {
*/
private cachedInputs: HTMLElement[] = [];
/**
* We'll store a reference to the tool's rendered element to access it later
*/
private toolRenderedElement: HTMLElement | null = null;
/**
* Tool class instance
*/
@ -553,23 +558,7 @@ export default class Block extends EventsDispatcher<BlockEvents> {
* @returns {HTMLElement}
*/
public get pluginsContent(): HTMLElement {
const blockContentNodes = this.holder.querySelector(`.${Block.CSS.content}`);
if (blockContentNodes && blockContentNodes.childNodes.length) {
/**
* Editors Block content can contain different Nodes from extensions
* We use DOM isExtensionNode to ignore such Nodes and return first Block that does not match filtering list
*/
for (let child = blockContentNodes.childNodes.length - 1; child >= 0; child--) {
const contentNode = blockContentNodes.childNodes[child];
if (!$.isExtensionNode(contentNode)) {
return contentNode as HTMLElement;
}
}
}
return null;
return this.toolRenderedElement;
}
/**
@ -825,7 +814,12 @@ export default class Block extends EventsDispatcher<BlockEvents> {
contentNode = $.make('div', Block.CSS.content),
pluginsContent = this.toolInstance.render();
contentNode.appendChild(pluginsContent);
/**
* Saving a reference to plugin's content element for guaranteed accessing it later
*/
this.toolRenderedElement = pluginsContent;
contentNode.appendChild(this.toolRenderedElement);
/**
* Block Tunes might wrap Block's content node to provide any UI changes

View file

@ -550,20 +550,6 @@ export default class Dom {
return element;
}
/**
* Method checks passed Node if it is some extension Node
*
* @param {Node} node - any node
* @returns {boolean}
*/
public static isExtensionNode(node: Node): boolean {
const extensions = [
'GRAMMARLY-EXTENSION',
];
return node && extensions.includes(node.nodeName);
}
/**
* Returns true if element is anchor (is A tag)
*

View file

@ -346,6 +346,10 @@ export default class Paste extends Module {
* @param tool - BlockTool object
*/
private getTagsConfig(tool: BlockTool): void {
if (tool.pasteConfig === false) {
return;
}
const tagsOrSanitizeConfigs = tool.pasteConfig.tags || [];
const toolTags = [];
@ -387,6 +391,10 @@ export default class Paste extends Module {
* @param tool - BlockTool object
*/
private getFilesConfig(tool: BlockTool): void {
if (tool.pasteConfig === false) {
return;
}
const { files = {} } = tool.pasteConfig;
let { extensions, mimeTypes } = files;
@ -428,7 +436,11 @@ export default class Paste extends Module {
* @param tool - BlockTool object
*/
private getPatternsConfig(tool: BlockTool): void {
if (!tool.pasteConfig.patterns || _.isEmpty(tool.pasteConfig.patterns)) {
if (
tool.pasteConfig === false ||
!tool.pasteConfig.patterns ||
_.isEmpty(tool.pasteConfig.patterns)
) {
return;
}
@ -602,7 +614,10 @@ export default class Paste extends Module {
break;
}
const { tags: tagsOrSanitizeConfigs } = tool.pasteConfig;
/**
* Returns empty array if there is no paste config
*/
const { tags: tagsOrSanitizeConfigs } = tool.pasteConfig || { tags: [] };
/**
* Reduce the tags or sanitize configs to a single array of sanitize config.

View file

@ -158,7 +158,7 @@ export default class BlockTool extends BaseTool<IBlockTool> {
* Returns Tool paste configuration
*/
public get pasteConfig(): PasteConfig {
return this.constructable[InternalBlockToolSettings.PasteConfig] || {};
return this.constructable[InternalBlockToolSettings.PasteConfig] ?? {};
}
/**

View file

@ -70,7 +70,7 @@ Cypress.Commands.add('paste', {
* Usage:
* cy.get('div').copy().then(data => {})
*/
Cypress.Commands.add('copy', { prevSubject: true }, async (subject) => {
Cypress.Commands.add('copy', { prevSubject: true }, (subject) => {
const clipboardData: {[type: string]: any} = {};
const copyEvent = Object.assign(new Event('copy', {
@ -87,7 +87,7 @@ Cypress.Commands.add('copy', { prevSubject: true }, async (subject) => {
subject[0].dispatchEvent(copyEvent);
return clipboardData;
return cy.wrap(clipboardData);
});
/**
@ -96,7 +96,7 @@ Cypress.Commands.add('copy', { prevSubject: true }, async (subject) => {
* Usage:
* cy.get('div').cut().then(data => {})
*/
Cypress.Commands.add('cut', { prevSubject: true }, async (subject) => {
Cypress.Commands.add('cut', { prevSubject: true }, (subject) => {
const clipboardData: {[type: string]: any} = {};
const copyEvent = Object.assign(new Event('cut', {
@ -113,7 +113,7 @@ Cypress.Commands.add('cut', { prevSubject: true }, async (subject) => {
subject[0].dispatchEvent(copyEvent);
return clipboardData;
return cy.wrap(clipboardData);
});
/**
@ -121,10 +121,10 @@ Cypress.Commands.add('cut', { prevSubject: true }, async (subject) => {
*
* @param data data to render
*/
Cypress.Commands.add('render', { prevSubject: true }, async (subject: EditorJS, data: OutputData): Promise<EditorJS> => {
await subject.render(data);
Cypress.Commands.add('render', { prevSubject: true }, (subject: EditorJS, data: OutputData) => {
subject.render(data);
return subject;
return cy.wrap(subject);
});

View file

@ -31,7 +31,7 @@ declare global {
* @usage
* cy.get('div').copy().then(data => {})
*/
copy(): Chainable<{ [type: string]: any }>;
copy(): Chainable<Subject>;
/**
* Cut command to dispatch cut event on subject
@ -39,14 +39,14 @@ declare global {
* @usage
* cy.get('div').cut().then(data => {})
*/
cut(): Chainable<{ [type: string]: any }>;
cut(): Chainable<Subject>;
/**
* Calls EditorJS API render method
*
* @param data data to render
*/
render(data: OutputData): Chainable<EditorJS>;
render(data: OutputData): Chainable<Subject>;
/**
* Select passed text in element

View file

@ -1,6 +1,8 @@
import Header from '@editorjs/header';
import Image from '@editorjs/simple-image';
import * as _ from '../../../src/components/utils';
import EditorJS, { BlockTool, BlockToolData } from '../../../types';
import $ from '../../../src/components/dom';
describe('Copy pasting from Editor', function () {
beforeEach(function () {
@ -13,7 +15,7 @@ describe('Copy pasting from Editor', function () {
});
afterEach(function () {
if (this.editorInstance) {
if (this.editorInstance && this.editorInstance.destroy) {
this.editorInstance.destroy();
}
});
@ -139,6 +141,72 @@ describe('Copy pasting from Editor', function () {
.get('img', { timeout: 10000 })
.should('have.attr', 'src', 'https://codex.so/public/app/img/external/codex2x.png');
});
it('should not prevent default behaviour if block\'s paste config equals false', function () {
/**
* Destroy default Editor to render custom one with different tools
*/
cy.get('@editorInstance')
.then((editorInstance: unknown) => (editorInstance as EditorJS).destroy());
const onPasteStub = cy.stub().as('onPaste');
/**
* Tool with disabled preventing default behavior of onPaste event
*/
class BlockToolWithPasteHandler implements BlockTool {
public static pasteConfig = false;
/**
* Render block
*/
public render(): HTMLElement {
const block = $.make('div', 'ce-block-with-disabled-prevent-default', {
contentEditable: 'true',
});
block.addEventListener('paste', onPasteStub);
return block;
}
/**
* Save data method
*/
public save(): BlockToolData {
return {};
}
}
cy.createEditor({
tools: {
blockToolWithPasteHandler: BlockToolWithPasteHandler,
},
}).as('editorInstanceWithBlockToolWithPasteHandler');
cy.get('@editorInstanceWithBlockToolWithPasteHandler')
.render({
blocks: [
{
type: 'blockToolWithPasteHandler',
data: {},
},
],
});
cy.get('@editorInstanceWithBlockToolWithPasteHandler')
.get('div.ce-block-with-disabled-prevent-default')
.click()
.paste({
// eslint-disable-next-line @typescript-eslint/naming-convention
'text/plain': 'Hello',
});
cy.get('@onPaste')
.should('have.been.calledWithMatch', {
defaultPrevented: false,
});
});
});
context('copying', function () {

View file

@ -0,0 +1,37 @@
import type EditorJS from '../../../../types/index';
describe('Saver module', function () {
describe('save()', function () {
it('should correctly save block if there are some 3rd party (eg. browser extensions) nodes inserted into the layout', function () {
cy.createEditor({
data: {
blocks: [
{
type: 'paragraph',
data: {
text: 'The block with some text',
},
},
],
},
}).then((editor: EditorJS) => {
/**
* Add some node just like browser extensions doing
*/
const extensionNode = document.createElement('extension-node');
cy.get('[data-cy=editorjs]')
.find('.ce-block__content')
.then((blockContent) => {
blockContent.append(extensionNode);
})
.then(async () => {
const savedData = await editor.save();
expect(savedData.blocks.length).to.equal(1);
expect(savedData.blocks[0].data.text).to.equal('The block with some text');
});
});
});
});
});

View file

@ -253,10 +253,36 @@ describe('BlockTool', () => {
expect(tool.conversionConfig).to.be.deep.eq(options.constructable.conversionConfig);
});
it('.pasteConfig should return correct value', () => {
const tool = new BlockTool(options as any);
describe('.pasteConfig', () => {
it('should return correct value', () => {
const tool = new BlockTool(options as any);
expect(tool.pasteConfig).to.be.deep.eq(options.constructable.pasteConfig);
expect(tool.pasteConfig).to.be.deep.eq(options.constructable.pasteConfig);
});
it('should return false if `false` value was provided', () => {
const optionsWithDisabledPaste = {
...options,
constructable: class extends (options.constructable as any) {
public static pasteConfig = false;
},
};
const tool = new BlockTool(optionsWithDisabledPaste as any);
expect(tool.pasteConfig).to.be.deep.eq(optionsWithDisabledPaste.constructable.pasteConfig);
});
it('should return empty object if getter isn\'t provided', () => {
const optionsWithoutPasteConfig = {
...options,
constructable: class extends (options.constructable as any) {
public static pasteConfig = undefined;
},
};
const tool = new BlockTool(optionsWithoutPasteConfig as any);
expect(tool.pasteConfig).to.be.deep.eq({});
});
});
context('.enabledInlineTools', () => {

View file

@ -1,9 +1,9 @@
import { SanitizerConfig } from "./sanitizer-config";
import { SanitizerConfig } from './sanitizer-config';
/**
* Tool onPaste configuration object
*/
export interface PasteConfig {
interface PasteConfigSpecified {
/**
* Array of tags Tool can substitute.
*
@ -22,7 +22,7 @@ export interface PasteConfig {
* Object of string patterns Tool can substitute.
* Key is your internal key and value is RegExp
*
* @type {{[key: string]: Regexp}}
* @type {{[key: string]: RegExp}}
*/
patterns?: {[key: string]: RegExp};
@ -31,3 +31,8 @@ export interface PasteConfig {
*/
files?: {extensions?: string[], mimeTypes?: string[]};
}
/**
* Alias for PasteConfig with false
*/
export type PasteConfig = PasteConfigSpecified | false;

836
yarn.lock

File diff suppressed because it is too large Load diff