mirror of
https://github.com/codex-team/editor.js
synced 2024-05-06 00:23:23 +02:00
Release 2.14 (#799)
This commit is contained in:
parent
9e61e3ddea
commit
23a4b2ef93
|
@ -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
18
dist/editor.js
vendored
File diff suppressed because one or more lines are too long
|
@ -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
|
||||
|
|
|
@ -83,3 +83,23 @@ 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
|
|
@ -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",
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -103,6 +103,7 @@ export default class BlocksAPI extends Module {
|
|||
*/
|
||||
public clear(): void {
|
||||
this.Editor.BlockManager.clear(true);
|
||||
this.Editor.InlineToolbar.close();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) && 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.Tools.isInitial(currentBlock.tool)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentBlock.isEmpty) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -575,6 +575,11 @@ export default class BlockManager extends Module {
|
|||
if (needAddInitialBlock) {
|
||||
this.insert(this.config.initialBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add empty modifier
|
||||
*/
|
||||
this.Editor.UI.checkEmptiness();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
@ -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,11 +405,16 @@ 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
|
||||
*/
|
||||
if (Selection.anchorNode === this.nodes.redactor) {
|
||||
this.Editor.Caret.setToTheLastBlock();
|
||||
} else {
|
||||
this.Editor.BlockManager.setCurrentBlockByChildNode(Selection.anchorNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* All clicks on the redactor zone
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 698fdbe5a739043b69349e8ed8ff49788722feae
|
||||
Subproject commit fa20d187729c72d41129d2e4e89c3a6e989eed12
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
}
|
||||
|
||||
&--confirm {
|
||||
background-color: var(--color-confirm);
|
||||
background-color: var(--color-confirm) !important;
|
||||
color: #fff;
|
||||
|
||||
&:hover {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
2
types/configs/editor-config.d.ts
vendored
2
types/configs/editor-config.d.ts
vendored
|
@ -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
43
types/index.d.ts
vendored
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue