Release 2.14 (#799)

This commit is contained in:
George Berezhnoy 2019-06-12 20:38:16 +03:00 committed by GitHub
parent 9e61e3ddea
commit 23a4b2ef93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 559 additions and 141 deletions

View File

@ -6,4 +6,4 @@ indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
trim_trailing_whitespace = true

View File

@ -85,7 +85,7 @@ Shortcut | Action | Restrictions
Also we support shortcuts on the all type of Tools. Specify a shortcut with the Tools configuration. For example:
```js
var editor = EditorJS({
var editor = new EditorJS({
//...
tools: {
header: {
@ -113,7 +113,7 @@ There are few steps to run Editor.js on your site.
## Load Editor's core
Firstly you need to get Editor.js itself. It is a [minified script](build/editor.js) with Editor's core and some default must-have tools.
Firstly you need to get Editor.js itself. It is a [minified script](dist/editor.js) with Editor's core and some default must-have tools.
Choose the most usable method of getting Editor for you.
@ -196,7 +196,7 @@ var editor = new EditorJS({
/**
* Create a holder for the Editor and pass its ID
*/
holderId : 'editorjs',
holder : 'editorjs',
/**
* Available Tools list.

18
dist/editor.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,5 +1,14 @@
# Changelog
### 2.14
- `Fix` *Config* — User config now has higher priority than internal settings [#771](https://github.com/codex-team/editor.js/issues/771)
- `New` — Ability to work with Block Actions and Inline Toolbar from the keyboard by Tab. [#705](https://github.com/codex-team/editor.js/issues/705)
- `Fix` — Fix error thrown by click on the empty editor after `blocks.clear()` method calling [#761](https://github.com/codex-team/editor.js/issues/761)
- `Fix` — Fix placeholder property appearance. Now you can assign it via `placeholder` property of EditorConfig. [#714](https://github.com/codex-team/editor.js/issues/714)
- `Fix` — Add API shorthands to TS types [#788](https://github.com/codex-team/editor.js/issues/788)
### 2.13
- `Improvements` *BlockSelection* — Block Selection allows to select single editable element via CMD+A

View File

@ -82,4 +82,24 @@ var editor = new EditorJS({
var editor2 = new EditorJS({
holder: 'codex-editor' // like document.getElementById('codex-editor')
})
```
```
## Placeholder
By default Editor\`s placeholder is empty.
You can pass your own placeholder via `placeholder` field:
```js
var editor = new EditorJS({
//...
placeholder: 'My awesome placeholder'
//...
});
```
If you are using your custom `Initial Block`, `placeholder` property is passed in `config` to your Tool constructor.

@ -1 +1 @@
Subproject commit a99abc4f84c8dc44b86f037f74e971374d28e22f
Subproject commit 0083d3bcbce82fd2063a15e3914158cb4bf8da7e

@ -1 +1 @@
Subproject commit 54c70585457f110cf7d9567a4bb741d8e98d1189
Subproject commit 37ad8316c138f55b5c210a0c8bb6b83db483187b

@ -1 +1 @@
Subproject commit fc365f869c12824a42d5081a3751741180a99e85
Subproject commit 70876fde289e9f8baa81ee4b5c8c3dc036ac7035

@ -1 +1 @@
Subproject commit 470a64c8ba8a65f3e291d77bc9e2d53a48591f87
Subproject commit 33ffba6f9104d163c69c963ca06fed329a819238

@ -1 +1 @@
Subproject commit da8d692adb3d1e3af10a192a9500e44bd4cc158b
Subproject commit 019fe76e6065d9d59be6c47b8f4a4706f285eee1

@ -1 +1 @@
Subproject commit 7f2e71470910291ba3ebd2fc013fbef33d054aa3
Subproject commit c1db3e8b40e98c36fedb1af0b574e5a161e2a471

@ -1 +1 @@
Subproject commit d72369b6d78e92610681f2e874f6f17cb3a6df27
Subproject commit da9f8a109706d4b4593e19afbfc05614466eb987

@ -1 +1 @@
Subproject commit 3d432a0279321566a23c4d6f27c913128b4c367e
Subproject commit 293109d03d9ff3cdecc52ec959866662d80dd0ce

View File

@ -1,6 +1,6 @@
{
"name": "@editorjs/editorjs",
"version": "2.13.0",
"version": "2.14.0",
"description": "Editor.js — Native JS, based on API and Open Source",
"main": "dist/editor.js",
"types": "./types/index.d.ts",

View File

@ -1,6 +1,6 @@
import $ from './dom';
import _ from './utils';
import {EditorConfig, OutputData, SanitizerConfig, ToolSettings} from '../../types';
import {EditorConfig, OutputData, SanitizerConfig} from '../../types';
import {EditorModules} from '../types-internal/editor-modules';
/**
@ -163,7 +163,7 @@ export default class Core {
data : {},
};
this.config.placeholder = this.config.placeholder || 'write your story...';
this.config.placeholder = this.config.placeholder || false;
this.config.sanitizer = this.config.sanitizer || {
p: true,
b: true,

View File

@ -520,6 +520,85 @@ export default class Dom {
}
/**
* Leafs nodes inside the target list from active element
*
* @param {HTMLElement[]} nodeList - target list of nodes
* @param {number} activeIndex index of active node. By default it must be -1
* @param {string} direction - leaf direction. Can be 'left' or 'right'
* @param {string} activeCSSClass - css class that will be added
*
* @return {Number} index of active node
*/
public static leafNodesAndReturnIndex(
nodeList: HTMLElement[],
activeIndex: number,
direction: string,
activeCSSClass: string,
): number {
/**
* If activeButtonIndex === -1 then we have no chosen Tool in Toolbox
*/
if (activeIndex === -1) {
/**
* Normalize "previous" Tool index depending on direction.
* We need to do this to highlight "first" Tool correctly
*
* Order of Tools: [0] [1] ... [n - 1]
* [0 = n] because of: n % n = 0 % n
*
* Direction 'right': for [0] the [n - 1] is a previous index
* [n - 1] -> [0]
*
* Direction 'left': for [n - 1] the [0] is a previous index
* [n - 1] <- [0]
*
* @type {number}
*/
activeIndex = direction === 'right' ? -1 : 0;
} else {
/**
* If we have chosen Tool then remove highlighting
*/
nodeList[activeIndex].classList.remove(activeCSSClass);
}
/**
* Count index for next Tool
*/
if (direction === 'right') {
/**
* If we go right then choose next (+1) Tool
* @type {number}
*/
activeIndex = (activeIndex + 1) % nodeList.length;
} else {
/**
* If we go left then choose previous (-1) Tool
* Before counting module we need to add length before because of "The JavaScript Modulo Bug"
* @type {number}
*/
activeIndex = (nodeList.length + activeIndex - 1) % nodeList.length;
}
if (Dom.isNativeInput(nodeList[activeIndex])) {
/**
* Focus input
*/
nodeList[activeIndex].focus();
}
/**
* Highlight new chosen Tool
*/
nodeList[activeIndex].classList.add(activeCSSClass);
/**
* Return Active index
*/
return activeIndex;
}
/*
* Helper for get holder from {string} or return HTMLElement
* @param element
*/

View File

@ -103,6 +103,7 @@ export default class BlocksAPI extends Module {
*/
public clear(): void {
this.Editor.BlockManager.clear(true);
this.Editor.InlineToolbar.close();
}
/**

View File

@ -3,8 +3,10 @@
*/
import Module from '../__module';
import _ from '../utils';
import SelectionUtils from '../selection';
export default class BlockEvents extends Module {
/**
* All keydowns on Block
* @param {KeyboardEvent} event - keydown
@ -62,7 +64,12 @@ export default class BlockEvents extends Module {
return;
}
this.Editor.Toolbar.close();
/**
* Close Toolbar on any keypress except TAB, because TAB leafs Tools
*/
if (event.keyCode !== _.keyCodes.TAB) {
this.Editor.Toolbar.close();
}
const cmdKey = event.ctrlKey || event.metaKey;
const altKey = event.altKey;
@ -92,6 +99,11 @@ export default class BlockEvents extends Module {
*/
public keyup(event): void {
this.Editor.InlineToolbar.handleShowingEvent(event);
/**
* Check if editor is empty on each keyup and add special css class to wrapper
*/
this.Editor.UI.checkEmptiness();
}
/**
@ -110,6 +122,10 @@ export default class BlockEvents extends Module {
const {currentBlock} = this.Editor.BlockManager;
if (!currentBlock) {
return;
}
/** Prevent Default behaviour */
event.preventDefault();
event.stopPropagation();
@ -119,31 +135,77 @@ export default class BlockEvents extends Module {
direction = shiftKey ? 'left' : 'right';
/**
* Don't show Plus and Toolbox near not-inital Tools
* For empty Blocks we show Plus button via Toobox only for initial Blocks
*/
if (!this.Editor.Tools.isInitial(currentBlock.tool)) {
return;
}
if (currentBlock.isEmpty) {
if (this.Editor.Tools.isInitial(currentBlock.tool) && currentBlock.isEmpty) {
/**
* Work with Toolbox
* ------------------
*
* If Toolbox is not open, then just open it and show plus button
* Next Tab press will leaf Toolbox Tools
*/
if (!this.Editor.Toolbar.opened) {
this.Editor.Toolbar.open(false , false);
this.Editor.Toolbar.plusButton.show();
} else {
this.Editor.Toolbox.leaf(direction);
}
this.Editor.Toolbox.open();
}
} else if (!currentBlock.isEmpty && !SelectionUtils.isCollapsed) {
/**
* Work with Inline Tools
* -----------------------
*
* If InlineToolbar is not open, just open it and focus first button
* Next Tab press will leaf InlineToolbar Tools
*/
if (this.Editor.InlineToolbar.opened) {
this.Editor.InlineToolbar.leaf(direction);
}
} else {
/**
* Open Toolbar and show BlockSettings
*/
if (!this.Editor.Toolbar.opened) {
this.Editor.BlockManager.currentBlock.focused = true;
this.Editor.Toolbar.open(true, false);
this.Editor.Toolbar.plusButton.hide();
}
if (this.Editor.Toolbox.opened) {
this.Editor.Toolbox.leaf(direction);
/**
* Work with Block Tunes
* ----------------------
*
* If BlockSettings is not open, then open BlockSettings
* Next Tab press will leaf Settings Buttons
*/
if (!this.Editor.BlockSettings.opened) {
this.Editor.BlockSettings.open();
}
this.Editor.BlockSettings.leaf(direction);
}
}
/**
* Escape pressed
* @param event
* If some of Toolbar components are opened, then close it otherwise close Toolbar
*
* @param {Event} event
*/
public escapePressed(event): void { }
public escapePressed(event): void {
if (this.Editor.Toolbox.opened) {
this.Editor.Toolbox.close();
} else if (this.Editor.BlockSettings.opened) {
this.Editor.BlockSettings.close();
} else if (this.Editor.InlineToolbar.opened) {
this.Editor.InlineToolbar.close();
} else {
this.Editor.Toolbar.close();
}
}
/**
* Add drop target styles
@ -231,7 +293,10 @@ export default class BlockEvents extends Module {
* 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.
*/
if (tool && tool[this.Editor.Tools.apiSettings.IS_ENABLED_LINE_BREAKS]) {
if (tool
&& tool[this.Editor.Tools.apiSettings.IS_ENABLED_LINE_BREAKS]
&& !this.Editor.BlockSettings.opened
&& !this.Editor.InlineToolbar.opened) {
return;
}
@ -239,10 +304,20 @@ export default class BlockEvents extends Module {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
this.Editor.Toolbox.toolButtonActivate(event, this.Editor.Toolbox.getActiveTool);
return;
}
if (this.Editor.InlineToolbar.opened && this.Editor.InlineToolbar.focusedButton) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
this.Editor.InlineToolbar.focusedButton.click();
return;
}
/**
* Allow to create linebreaks by Shift+Enter
*/
@ -441,8 +516,15 @@ export default class BlockEvents extends Module {
*/
private needToolbarClosing(event) {
const toolboxItemSelected = (event.keyCode === _.keyCodes.ENTER && this.Editor.Toolbox.opened),
flippingToolboxItems = event.keyCode === _.keyCodes.TAB;
blockSettingsItemSelected = (event.keyCode === _.keyCodes.ENTER && this.Editor.BlockSettings.opened),
flippingToolbarItems = event.keyCode === _.keyCodes.TAB;
return !(event.shiftKey || flippingToolboxItems || toolboxItemSelected);
/**
* Do not close Toolbar in cases:
* 1. ShiftKey pressed (or combination with shiftKey)
* 2. When Toolbar is opened and Tab leafs its Tools
* 3. When Toolbar's component is opened and some its item selected
*/
return !(event.shiftKey || flippingToolbarItems || toolboxItemSelected || blockSettingsItemSelected);
}
}

View File

@ -575,6 +575,11 @@ export default class BlockManager extends Module {
if (needAddInitialBlock) {
this.insert(this.config.initialBlock);
}
/**
* Add empty modifier
*/
this.Editor.UI.checkEmptiness();
}
/**

View File

@ -32,7 +32,6 @@ export default class ModificationsObserver extends Module {
* @type {Function}
*/
private mutationDebouncer = _.debounce( () => {
this.checkEmptiness();
this.config.onChange();
}, ModificationsObserver.DebounceTimer);
@ -143,13 +142,4 @@ export default class ModificationsObserver extends Module {
this.mutationDebouncer();
}
}
/**
* Check if Editor is empty and set CSS class to wrapper
*/
private checkEmptiness(): void {
const {BlockManager, UI} = this.Editor;
UI.nodes.wrapper.classList.toggle(UI.CSS.editorEmpty, BlockManager.isEditorEmpty);
}
}

View File

@ -42,10 +42,14 @@ export default class Renderer extends Module {
* Make plugin blocks from array of plugin`s data
* @param {RendererBlocks[]} blocks
*/
public render(blocks: BlockToolData[]): Promise<void> {
public async render(blocks: BlockToolData[]): Promise<void> {
const chainData = blocks.map((block) => ({function: () => this.insertBlock(block)}));
return _.sequence(chainData as ChainData[]);
const sequence = await _.sequence(chainData as ChainData[]);
this.Editor.UI.checkEmptiness();
return sequence;
}
/**

View File

@ -29,7 +29,7 @@ export default class BlockSettings extends Module {
* Block Settings CSS
* @return {{wrapper, wrapperOpened, toolSettings, defaultSettings, button}}
*/
private static get CSS() {
public get CSS() {
return {
// Settings Panel
wrapper: 'ce-settings',
@ -38,6 +38,9 @@ export default class BlockSettings extends Module {
defaultSettings: 'ce-settings__default-zone',
button: 'ce-settings__button',
focusedButton : 'ce-settings__button--focused',
focusedButtonAnimated: 'ce-settings__button--focused-animated',
};
}
@ -46,7 +49,7 @@ export default class BlockSettings extends Module {
* @returns {boolean}
*/
public get opened(): boolean {
return this.nodes.wrapper.classList.contains(BlockSettings.CSS.wrapperOpened);
return this.nodes.wrapper.classList.contains(this.CSS.wrapperOpened);
}
/**
@ -58,6 +61,16 @@ export default class BlockSettings extends Module {
defaultSettings: null,
};
/**
* List of buttons
*/
private buttons: HTMLElement[] = [];
/**
* Index of active button
*/
private focusedButtonIndex: number = -1;
/**
* Panel with block settings with 2 sections:
* - Tool's Settings
@ -66,10 +79,10 @@ export default class BlockSettings extends Module {
* @return {Element}
*/
public make(): void {
this.nodes.wrapper = $.make('div', BlockSettings.CSS.wrapper);
this.nodes.wrapper = $.make('div', this.CSS.wrapper);
this.nodes.toolSettings = $.make('div', BlockSettings.CSS.toolSettings);
this.nodes.defaultSettings = $.make('div', BlockSettings.CSS.defaultSettings);
this.nodes.toolSettings = $.make('div', this.CSS.toolSettings);
this.nodes.defaultSettings = $.make('div', this.CSS.defaultSettings);
$.append(this.nodes.wrapper, [this.nodes.toolSettings, this.nodes.defaultSettings]);
}
@ -78,7 +91,7 @@ export default class BlockSettings extends Module {
* Open Block Settings pane
*/
public open(): void {
this.nodes.wrapper.classList.add(BlockSettings.CSS.wrapperOpened);
this.nodes.wrapper.classList.add(this.CSS.wrapperOpened);
/**
* Fill Tool's settings
@ -98,7 +111,7 @@ export default class BlockSettings extends Module {
* Close Block Settings pane
*/
public close(): void {
this.nodes.wrapper.classList.remove(BlockSettings.CSS.wrapperOpened);
this.nodes.wrapper.classList.remove(this.CSS.wrapperOpened);
/** Clear settings */
this.nodes.toolSettings.innerHTML = '';
@ -106,8 +119,66 @@ export default class BlockSettings extends Module {
/** Tell to subscribers that block settings is closed */
this.Editor.Events.emit(this.events.closed);
/** Clear cached buttons */
this.buttons = [];
/** Clear focus on active button */
this.focusedButtonIndex = -1;
}
/**
* Returns Tools Settings and Default Settings
* @return {HTMLElement[]}
*/
public get blockTunesButtons(): HTMLElement[] {
/**
* Return from cache
* if exists
*/
if (this.buttons.length !== 0) {
return this.buttons;
}
const toolSettings = this.nodes.toolSettings.querySelectorAll(`.${this.Editor.StylesAPI.classes.settingsButton}`);
const defaultSettings = this.nodes.defaultSettings.querySelectorAll(`.${this.CSS.button}`);
toolSettings.forEach((item, index) => {
this.buttons.push((item as HTMLElement));
if (item.classList.contains(this.CSS.focusedButton)) {
this.focusedButtonIndex = index;
}
});
defaultSettings.forEach((item) => {
this.buttons.push((item as HTMLElement));
});
return this.buttons;
}
/**
* Leaf Block Tunes
* @param {string} direction
*/
public leaf(direction: string = 'right'): void {
this.focusedButtonIndex = $.leafNodesAndReturnIndex(
this.blockTunesButtons, this.focusedButtonIndex, direction, this.CSS.focusedButton,
);
}
/**
* Returns active button HTML element
* @return {HTMLElement}
*/
public get focusedButton(): HTMLElement {
if (this.focusedButtonIndex === -1) {
return null;
}
return (this.buttons[this.focusedButtonIndex] as HTMLElement);
}
/**
* Add Tool's settings
*/

View File

@ -28,8 +28,15 @@ export default class InlineToolbar extends Module {
inlineToolButton: 'ce-inline-tool',
inlineToolButtonLast: 'ce-inline-tool--last',
inputField: 'cdx-input',
focusedButton: 'ce-inline-tool--focused',
};
/**
* State of inline toolbar
* @type {boolean}
*/
public opened: boolean = false;
/**
* Inline Toolbar elements
*/
@ -53,6 +60,25 @@ export default class InlineToolbar extends Module {
*/
private toolsInstances: Map<string, InlineTool>;
/**
* Buttons List
* @type {NodeList}
*/
private buttonsList: NodeList = null;
/**
* Visible Buttons
* Some Blocks might disable inline tools
* @type {HTMLElement[]}
*/
private visibleButtonsList: HTMLElement[] = [];
/**
* Focused button index
* @type {number}
*/
private focusedButtonIndex: number = -1;
/**
* Inline Toolbar Tools
*
@ -85,7 +111,8 @@ export default class InlineToolbar extends Module {
this.Editor.Listeners.on(this.nodes.wrapper, 'mousedown', (event) => {
const isClickedOnActionsWrapper = (event.target as Element).closest(`.${this.CSS.actionsWrapper}`);
// If click is on actions wrapper, do not prevent default behaviour because actions might include interactive elements
// If click is on actions wrapper,
// do not prevent default behaviour because actions might include interactive elements
if (!isClickedOnActionsWrapper) {
event.preventDefault();
}
@ -155,6 +182,47 @@ export default class InlineToolbar extends Module {
this.nodes.wrapper.style.top = Math.floor(newCoords.y) + 'px';
}
/**
* Leaf Inline Tools
* @param {string} direction
*/
public leaf(direction: string = 'right'): void {
this.visibleButtonsList = (Array.from(this.buttonsList)
.filter((tool) => !(tool as HTMLElement).hidden) as HTMLElement[]);
if (this.visibleButtonsList.length === 0) {
return;
}
this.focusedButtonIndex = $.leafNodesAndReturnIndex(
this.visibleButtonsList, this.focusedButtonIndex, direction, this.CSS.focusedButton,
);
}
/**
* Drops focused button index
*/
public dropFocusedButtonIndex(): void {
if (this.focusedButtonIndex === -1) {
return;
}
this.visibleButtonsList[this.focusedButtonIndex].classList.remove(this.CSS.focusedButton);
this.focusedButtonIndex = -1;
}
/**
* Returns Focused button Node
* @return {HTMLElement}
*/
public get focusedButton(): HTMLElement {
if (this.focusedButtonIndex === -1) {
return null;
}
return this.visibleButtonsList[this.focusedButtonIndex];
}
/**
* Hides Inline Toolbar
*/
@ -165,6 +233,13 @@ export default class InlineToolbar extends Module {
toolInstance.clear();
}
});
this.opened = false;
if (this.focusedButtonIndex !== -1) {
this.visibleButtonsList[this.focusedButtonIndex].classList.remove(this.CSS.focusedButton);
this.focusedButtonIndex = -1;
}
}
/**
@ -196,6 +271,9 @@ export default class InlineToolbar extends Module {
toolInstance.clear();
}
});
this.buttonsList = this.nodes.buttons.querySelectorAll(`.${this.CSS.inlineToolButton}`);
this.opened = true;
}
/**
@ -220,7 +298,9 @@ export default class InlineToolbar extends Module {
return false;
}
const target = currentSelection.anchorNode.parentElement;
const target = !$.isElement(currentSelection.anchorNode )
? currentSelection.anchorNode.parentElement
: currentSelection.anchorNode;
if (currentSelection && tagsConflictsWithSelection.includes(target.tagName)) {
return false;

View File

@ -142,12 +142,14 @@ export default class Toolbox extends Module {
this.opened = false;
/** remove active item pointer */
this.activeButtonIndex = -1;
const activeButton = this.nodes.toolbox.querySelector(`.${this.CSS.toolboxButtonActive}`);
/**
* Remove active item pointer
*/
if (this.activeButtonIndex !== -1) {
(this.nodes.toolbox.childNodes[this.activeButtonIndex] as HTMLElement)
.classList.remove(this.CSS.toolboxButtonActive);
if (activeButton) {
activeButton.classList.remove(this.CSS.toolboxButtonActive);
this.activeButtonIndex = -1;
}
}
@ -168,57 +170,10 @@ export default class Toolbox extends Module {
* @param {String} direction - leaf direction, right is default
*/
public leaf(direction: string = Toolbox.LEAF_DIRECTIONS.RIGHT): void {
const childNodes = this.nodes.toolbox.childNodes;
/**
* If activeButtonIndex === -1 then we have no chosen Tool in Toolbox
*/
if (this.activeButtonIndex === -1) {
/**
* Normalize "previous" Tool index depending on direction.
* We need to do this to highlight "first" Tool correctly
*
* Order of Tools: [0] [1] ... [n - 1]
* [0 = n] because of: n % n = 0 % n
*
* Direction 'right': for [0] the [n - 1] is a previous index
* [n - 1] -> [0]
*
* Direction 'left': for [n - 1] the [0] is a previous index
* [n - 1] <- [0]
*
* @type {number}
*/
this.activeButtonIndex = direction === Toolbox.LEAF_DIRECTIONS.RIGHT ? -1 : 0;
} else {
/**
* If we have chosen Tool then remove highlighting
*/
(childNodes[this.activeButtonIndex] as HTMLElement).classList.remove(this.CSS.toolboxButtonActive);
}
/**
* Count index for next Tool
*/
if (direction === Toolbox.LEAF_DIRECTIONS.RIGHT) {
/**
* If we go right then choose next (+1) Tool
* @type {number}
*/
this.activeButtonIndex = (this.activeButtonIndex + 1) % childNodes.length;
} else {
/**
* If we go left then choose previous (-1) Tool
* Before counting module we need to add length before because of "The JavaScript Modulo Bug"
* @type {number}
*/
this.activeButtonIndex = (childNodes.length + this.activeButtonIndex - 1) % childNodes.length;
}
/**
* Highlight new chosen Tool
*/
(childNodes[this.activeButtonIndex] as HTMLElement).classList.add(this.CSS.toolboxButtonActive);
const childNodes = (Array.from(this.nodes.toolbox.childNodes) as HTMLElement[]);
this.activeButtonIndex = $.leafNodesAndReturnIndex(
childNodes, this.activeButtonIndex, direction, this.CSS.toolboxButtonActive,
);
}
/**

View File

@ -207,7 +207,7 @@ export default class Tools extends Module {
/**
* Assign internal tools
*/
_.deepMerge(this.config.tools, this.internalTools);
this.config.tools = _.deepMerge({}, this.internalTools, this.config.tools);
if (!this.config.hasOwnProperty('tools') || Object.keys(this.config.tools).length === 0) {
throw Error('Can\'t start without tools');
@ -302,14 +302,19 @@ export default class Tools extends Module {
/**
* Configuration to be passed to the Tool's constructor
*/
const config = this.toolsSettings[tool][this.apiSettings.CONFIG];
const config = this.toolsSettings[tool][this.apiSettings.CONFIG] || {};
// Pass placeholder to initial Block config
if (tool === this.config.initialBlock && !config.placeholder) {
config.placeholder = this.config.placeholder;
}
/**
* @type {{api: API, config: ({}), data: BlockToolData}}
*/
const constructorOptions = {
api: this.Editor.API.methods,
config: config || {},
config,
data,
};

View File

@ -118,6 +118,15 @@ export default class UI extends Module {
await this.bindEvents();
}
/**
* Check if Editor is empty and set CSS class to wrapper
*/
public checkEmptiness(): void {
const {BlockManager} = this.Editor;
this.nodes.wrapper.classList.toggle(this.CSS.editorEmpty, BlockManager.isEditorEmpty);
}
/**
* Clean editor`s UI
*/
@ -268,9 +277,51 @@ export default class UI extends Module {
* @param event
*/
private enterPressed(event: KeyboardEvent): void {
const {BlockManager, BlockSelection, Caret} = this.Editor;
const {BlockManager, BlockSelection, Caret, BlockSettings} = this.Editor;
const hasPointerToBlock = BlockManager.currentBlockIndex >= 0;
/**
* If Block Settings is opened and have some active button
* Enter press is fired as out of the Block and that's why
* we handle it here
*/
if (BlockSettings.opened && BlockSettings.focusedButton) {
event.preventDefault();
event.stopPropagation();
event.stopImmediatePropagation();
/** Click on settings button */
BlockSettings.focusedButton.click();
/**
* Add animation on click
*/
BlockSettings.focusedButton.classList.add(BlockSettings.CSS.focusedButtonAnimated);
/**
* Remove animation class
*/
_.delay( () => {
BlockSettings.focusedButton.classList.remove(BlockSettings.CSS.focusedButtonAnimated);
}, 280)();
/**
* Restoring focus on current Block
*
* After changing Block state (when settings clicked, for example)
* Block's content points to the Node that is not in DOM, that's why we can not
* set caret and leaf next (via Tab)
*
* For that set cursor via Caret module to the current Block's content
* after some timeout
*/
_.delay( () => {
Caret.setToBlock(BlockManager.currentBlock);
}, 10)();
return;
}
if (BlockSelection.anyBlockSelected) {
const selectionPositionIndex = BlockManager.removeSelectedBlocks();
Caret.setToBlock(BlockManager.insertAtIndex(selectionPositionIndex, true), Caret.positions.START);
@ -354,9 +405,14 @@ export default class UI extends Module {
if (Selection.isAtEditor) {
/**
* Focus clicked Block
* Focus clicked Block.
* Workaround case when user clicks on the bottom of editor
*/
this.Editor.BlockManager.setCurrentBlockByChildNode(Selection.anchorNode);
if (Selection.anchorNode === this.nodes.redactor) {
this.Editor.Caret.setToTheLastBlock();
} else {
this.Editor.BlockManager.setCurrentBlockByChildNode(Selection.anchorNode);
}
}
}

@ -1 +1 @@
Subproject commit 698fdbe5a739043b69349e8ed8ff49788722feae
Subproject commit fa20d187729c72d41129d2e4e89c3a6e989eed12

View File

@ -78,3 +78,25 @@
}
}
@keyframes buttonClicked {
from,
20%,
40%,
60%,
80%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
}
0% {
transform: scale3d(0.95, 0.95, 0.95);
}
60% {
transform: scale3d(1.02, 1.02, 1.02);
}
80% {
transform: scale3d(1, 1, 1);
}
}

View File

@ -72,7 +72,7 @@
}
&--confirm {
background-color: var(--color-confirm);
background-color: var(--color-confirm) !important;
color: #fff;
&:hover {

View File

@ -99,22 +99,8 @@
background-color: var(--selectionColor);
}
/**
* Add placeholder to content editable elements with data attribute
* data-placeholder="Hello world!"
*/
[contentEditable=true][data-placeholder]:empty::before{
content: attr(data-placeholder);
color: var(--grayText);
font-weight: normal;
}
[contentEditable=true][data-placeholder]:empty:focus::before {
opacity: 0.3;
}
.codex-editor--toolbox-opened [contentEditable=true][data-placeholder]:focus::before {
opacity: 0;
opacity: 0 !important;
}
@keyframes editor-loader-spin {

View File

@ -127,5 +127,15 @@
&--active {
color: var(--color-active-icon);
}
&--focused {
box-shadow: inset 0 0 0px 1px rgba(7, 161, 227, 0.08);
background: rgba(34, 186, 255, 0.08) !important;
&-animated {
animation-name: buttonClicked;
animation-duration: 250ms;
}
}
};
}

View File

@ -29,7 +29,7 @@ export interface EditorConfig {
/**
* First Block placeholder
*/
placeholder?: string;
placeholder?: string|false;
/**
* Define default sanitizer configuration

43
types/index.d.ts vendored
View File

@ -6,6 +6,7 @@
import {EditorConfig} from './configs';
import {Blocks, Caret, Events, Listeners, Notifier, Sanitizer, Saver, Selection, Styles, Toolbar, InlineToolbar} from './api';
import {OutputData} from "./data-formats/output-data";
/**
* Interfaces used for development
@ -74,6 +75,48 @@ declare class EditorJS {
public inlineToolbar: InlineToolbar;
constructor(configuration?: EditorConfig|string);
/**
* API shorthands
*/
/**
* @see Saver.save
*/
save(): Promise<OutputData>;
/**
* @see Blocks.clear
*/
clear(): void;
/**
* @see Blocks.render
*/
render(data: OutputData): Promise<void>;
/**
* @see Caret.focus
*/
focus(atEnd?: boolean): boolean;
/**
* @see Events.on
*/
on(eventName: string, callback: (data?: any) => void): void;
/**
* @see Events.off
*/
off(eventName: string, callback: (data?: any) => void): void;
/**
* @see Events.emit
*/
emit(eventName: string, data: any): void;
/**
* Destroy Editor instance and related DOM elements
*/
public destroy(): void;
}