Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,8 @@
"unknown": true,
"requestAnimationFrame": true,
"navigator": true
},
"rules": {
"jsdoc/require-returns-type": "off"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}
}
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
- `New` – *Menu Config* – New item type – HTML
– `Refactoring` – Switched to Vite as Cypress bundler
– `New` – *Menu Config* – Default and HTML items now support hints
– `Fix` — Deleting whitespaces at the start/end of the block

### 2.29.1

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@editorjs/editorjs",
"version": "2.30.0-rc.10",
"version": "2.30.0-rc.11",
"description": "Editor.js — Native JS, based on API and Open Source",
"main": "dist/editorjs.umd.js",
"module": "dist/editorjs.mjs",
Expand Down
33 changes: 12 additions & 21 deletions src/components/block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {

/**
* Cached inputs
*
* @type {HTMLElement[]}
*/
private cachedInputs: HTMLElement[] = [];

Expand Down Expand Up @@ -269,8 +267,6 @@ export default class Block extends EventsDispatcher<BlockEvents> {

/**
* Find and return all editable elements (contenteditable and native inputs) in the Tool HTML
*
* @returns {HTMLElement[]}
*/
public get inputs(): HTMLElement[] {
/**
Expand Down Expand Up @@ -299,19 +295,18 @@ export default class Block extends EventsDispatcher<BlockEvents> {

/**
* Return current Tool`s input
*
* @returns {HTMLElement}
* If Block doesn't contain inputs, return undefined
*/
public get currentInput(): HTMLElement | Node {
public get currentInput(): HTMLElement | undefined {
return this.inputs[this.inputIndex];
}

/**
* Set input index to the passed element
*
* @param {HTMLElement | Node} element - HTML Element to set as current input
* @param element - HTML Element to set as current input
*/
public set currentInput(element: HTMLElement | Node) {
public set currentInput(element: HTMLElement) {
const index = this.inputs.findIndex((input) => input === element || input.contains(element));

if (index !== -1) {
Expand All @@ -321,39 +316,35 @@ export default class Block extends EventsDispatcher<BlockEvents> {

/**
* Return first Tool`s input
*
* @returns {HTMLElement}
* If Block doesn't contain inputs, return undefined
*/
public get firstInput(): HTMLElement {
public get firstInput(): HTMLElement | undefined {
return this.inputs[0];
}

/**
* Return first Tool`s input
*
* @returns {HTMLElement}
* If Block doesn't contain inputs, return undefined
*/
public get lastInput(): HTMLElement {
public get lastInput(): HTMLElement | undefined {
const inputs = this.inputs;

return inputs[inputs.length - 1];
}

/**
* Return next Tool`s input or undefined if it doesn't exist
*
* @returns {HTMLElement}
* If Block doesn't contain inputs, return undefined
*/
public get nextInput(): HTMLElement {
public get nextInput(): HTMLElement | undefined {
return this.inputs[this.inputIndex + 1];
}

/**
* Return previous Tool`s input or undefined if it doesn't exist
*
* @returns {HTMLElement}
* If Block doesn't contain inputs, return undefined
*/
public get previousInput(): HTMLElement {
public get previousInput(): HTMLElement | undefined {
return this.inputs[this.inputIndex - 1];
}

Expand Down
31 changes: 29 additions & 2 deletions src/components/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as _ from './utils';

/**
* DOM manipulations helper
*
* @todo get rid of class and make separate utility functions
*/
export default class Dom {
/**
Expand Down Expand Up @@ -211,9 +213,10 @@ export default class Dom {
* @param {Node} node - root Node. From this vertex we start Deep-first search
* {@link https://en.wikipedia.org/wiki/Depth-first_search}
* @param {boolean} [atLast] - find last text node
* @returns {Node} - it can be text Node or Element Node, so that caret will able to work with it
* @returns - it can be text Node or Element Node, so that caret will able to work with it
* Can return null if node is Document or DocumentFragment, or node is not attached to the DOM
*/
public static getDeepestNode(node: Node, atLast = false): Node {
public static getDeepestNode(node: Node, atLast = false): Node | null {
/**
* Current function have two directions:
* - starts from first child and every time gets first or nextSibling in special cases
Expand Down Expand Up @@ -590,3 +593,27 @@ export default class Dom {
};
}
}

/**
* Determine whether a passed text content is a collapsed whitespace.
*
* In HTML, whitespaces at the start and end of elements and outside elements are ignored.
* There are two types of whitespaces in HTML:
* - Visible (&nbsp;)
* - Invisible (regular trailing spaces, tabs, etc)
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
* @see https://www.w3.org/TR/css-text-3/#white-space-processing
* @param textContent — any string, for ex a textContent of a node
* @returns True if passed text content is whitespace which is collapsed (invisible) in browser
*/
export function isCollapsedWhitespaces(textContent: string): boolean {
/**
* Throughout, whitespace is defined as one of the characters
* "\t" TAB \u0009
* "\n" LF \u000A
* "\r" CR \u000D
* " " SPC \u0020
*/
return !/[^\t\n\r ]/.test(textContent);
}
35 changes: 24 additions & 11 deletions src/components/modules/blockEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import SelectionUtils from '../selection';
import Flipper from '../flipper';
import type Block from '../block';
import { areBlocksMergeable } from '../utils/blocks';
import * as caretUtils from '../utils/caret';

/**
*
Expand Down Expand Up @@ -270,6 +271,10 @@ export default class BlockEvents extends Module {
const { BlockManager, UI } = this.Editor;
const currentBlock = BlockManager.currentBlock;

if (currentBlock === undefined) {
return;
}

/**
* Don't handle Enter keydowns when Tool sets enableLineBreaks to true.
* Uses for Tools like <code> where line breaks should be handled by default behaviour.
Expand Down Expand Up @@ -297,34 +302,34 @@ export default class BlockEvents extends Module {
return;
}

let newCurrent = this.Editor.BlockManager.currentBlock;
let blockToFocus = currentBlock;

/**
* If enter has been pressed at the start of the text, just insert paragraph Block above
*/
if (this.Editor.Caret.isAtStart && !this.Editor.BlockManager.currentBlock.hasMedia) {
if (currentBlock.currentInput !== undefined && caretUtils.isCaretAtStartOfInput(currentBlock.currentInput) && !currentBlock.hasMedia) {
this.Editor.BlockManager.insertDefaultBlockAtIndex(this.Editor.BlockManager.currentBlockIndex);

/**
* If caret is at very end of the block, just append the new block without splitting
* to prevent unnecessary dom mutation observing
*/
} else if (this.Editor.Caret.isAtEnd) {
newCurrent = this.Editor.BlockManager.insertDefaultBlockAtIndex(this.Editor.BlockManager.currentBlockIndex + 1);
} else if (currentBlock.currentInput && caretUtils.isCaretAtEndOfInput(currentBlock.currentInput)) {
blockToFocus = this.Editor.BlockManager.insertDefaultBlockAtIndex(this.Editor.BlockManager.currentBlockIndex + 1);
} else {
/**
* Split the Current Block into two blocks
* Renew local current node after split
*/
newCurrent = this.Editor.BlockManager.split();
blockToFocus = this.Editor.BlockManager.split();
}

this.Editor.Caret.setToBlock(newCurrent);
this.Editor.Caret.setToBlock(blockToFocus);

/**
* Show Toolbar
*/
this.Editor.Toolbar.moveAndOpen(newCurrent);
this.Editor.Toolbar.moveAndOpen(blockToFocus);

event.preventDefault();
}
Expand All @@ -338,6 +343,10 @@ export default class BlockEvents extends Module {
const { BlockManager, Caret } = this.Editor;
const { currentBlock, previousBlock } = BlockManager;

if (currentBlock === undefined) {
return;
}

/**
* If some fragment is selected, leave native behaviour
*/
Expand All @@ -348,7 +357,7 @@ export default class BlockEvents extends Module {
/**
* If caret is not at the start, leave native behaviour
*/
if (!Caret.isAtStart) {
if (!currentBlock.currentInput || !caretUtils.isCaretAtStartOfInput(currentBlock.currentInput)) {
return;
}
/**
Expand Down Expand Up @@ -431,7 +440,7 @@ export default class BlockEvents extends Module {
/**
* If caret is not at the end, leave native behaviour
*/
if (!Caret.isAtEnd) {
if (!caretUtils.isCaretAtEndOfInput(currentBlock.currentInput)) {
return;
}

Expand Down Expand Up @@ -534,7 +543,9 @@ export default class BlockEvents extends Module {
*/
this.Editor.Toolbar.close();

const shouldEnableCBS = this.Editor.Caret.isAtEnd || this.Editor.BlockSelection.anyBlockSelected;
const { currentBlock } = this.Editor.BlockManager;
const caretAtEnd = currentBlock?.currentInput !== undefined ? caretUtils.isCaretAtEndOfInput(currentBlock.currentInput) : undefined;
const shouldEnableCBS = caretAtEnd || this.Editor.BlockSelection.anyBlockSelected;

if (event.shiftKey && event.keyCode === _.keyCodes.DOWN && shouldEnableCBS) {
this.Editor.CrossBlockSelection.toggleBlockSelectedState();
Expand Down Expand Up @@ -594,7 +605,9 @@ export default class BlockEvents extends Module {
*/
this.Editor.Toolbar.close();

const shouldEnableCBS = this.Editor.Caret.isAtStart || this.Editor.BlockSelection.anyBlockSelected;
const { currentBlock } = this.Editor.BlockManager;
const caretAtStart = currentBlock?.currentInput !== undefined ? caretUtils.isCaretAtStartOfInput(currentBlock.currentInput) : undefined;
const shouldEnableCBS = caretAtStart || this.Editor.BlockSelection.anyBlockSelected;

if (event.shiftKey && event.keyCode === _.keyCodes.UP && shouldEnableCBS) {
this.Editor.CrossBlockSelection.toggleBlockSelectedState(false);
Expand Down
Loading