Pass configuration to internal tools (#648)

* Pass configuration to internal tools

* Bump version
Add changelog

* Move tools validation to Tools module
Add class for wrapper when toolbox is opened

* Add emptiness check

* Update submodule
This commit is contained in:
George Berezhnoy 2019-03-18 19:22:22 +03:00 committed by GitHub
parent 1178a2f9e2
commit 6bd857d4f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 110 additions and 35 deletions

10
dist/editor.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,9 @@
# Changelog
### 2.11.11
- `New` — Add ability to pass configuration for internal Tools
### 2.11.10
- `Fix` - Fix editor view on mobile devices

View file

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

View file

@ -200,21 +200,6 @@ export default class Core {
if (!$.get(this.config.holderId)) {
throw Error(`element with ID «${this.config.holderId}» is missing. Pass correct holder's ID.`);
}
/**
* Check Tools for a class containing
*/
for (const toolName in this.config.tools) {
if (this.config.tools.hasOwnProperty(toolName)) {
const tool = this.config.tools[toolName];
if (!_.isFunction(tool) && !_.isFunction((tool as ToolSettings).class)) {
throw Error(
`Tool «${toolName}» must be a constructor function or an object with function in the «class» property`,
);
}
}
}
}
/**

View file

@ -128,6 +128,15 @@ export default class BlockManager extends Module {
return this._blocks.array;
}
/**
* Check if each Block is empty
*
* @returns {boolean}
*/
public get isEditorEmpty(): boolean {
return this.blocks.every((block) => block.isEmpty);
}
/**
* Index of current working block
*

View file

@ -32,6 +32,7 @@ export default class ModificationsObserver extends Module {
* @type {Function}
*/
private mutationDebouncer = _.debounce( () => {
this.checkEmptiness();
this.config.onChange();
}, ModificationsObserver.DebounceTimer);
@ -142,4 +143,13 @@ 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

@ -10,7 +10,6 @@ import $ from '../dom';
import SelectionUtils from '../selection';
import Block from '../block';
import UI from './ui';
import Timeout = NodeJS.Timeout;
export default class RectangleSelection extends Module {
@ -138,7 +137,7 @@ export default class RectangleSelection extends Module {
this.stackOfSelected = [];
const elemWhereSelectionStart = document.elementFromPoint(pageX - window.pageXOffset, pageY - window.pageYOffset);
if (!(elemWhereSelectionStart.closest('.' + UI.CSS.editorWrapper) &&
if (!(elemWhereSelectionStart.closest('.' + this.Editor.UI.CSS.editorWrapper) &&
!elemWhereSelectionStart.closest('.' + Block.CSS.content))) {
return;
}
@ -197,7 +196,9 @@ export default class RectangleSelection extends Module {
}
private genHTML() {
const container = this.Editor.UI.nodes.holder.querySelector('.' + UI.CSS.editorWrapper);
const {UI} = this.Editor;
const container = UI.nodes.holder.querySelector('.' + UI.CSS.editorWrapper);
const overlay = $.make('div', RectangleSelection.CSS.overlay, {});
const overlayContainer = $.make('div', RectangleSelection.CSS.overlayContainer, {});
const overlayRectangle = $.make('div', RectangleSelection.CSS.rect, {});

View file

@ -29,6 +29,7 @@ export default class Toolbox extends Module {
tooltip: 'ce-toolbox__tooltip',
tooltipShown: 'ce-toolbox__tooltip--shown',
tooltipShortcut: 'ce-toolbox__tooltip-shortcut',
openedToolbarHolderModifier: 'codex-editor--toolbox-opened',
};
}
@ -124,7 +125,9 @@ export default class Toolbox extends Module {
return;
}
this.Editor.UI.nodes.wrapper.classList.add(this.CSS.openedToolbarHolderModifier);
this.nodes.toolbox.classList.add(this.CSS.toolboxOpened);
this.opened = true;
}
@ -135,6 +138,8 @@ export default class Toolbox extends Module {
this.hideTooltip();
this.nodes.toolbox.classList.remove(this.CSS.toolboxOpened);
this.Editor.UI.nodes.wrapper.classList.remove(this.CSS.openedToolbarHolderModifier);
this.opened = false;
/** remove active item pointer */

View file

@ -195,10 +195,12 @@ export default class Tools extends Module {
* @return {Promise}
*/
public prepare() {
this.validateTools();
/**
* Assign internal tools
*/
Object.assign(this.config.tools, this.internalTools);
_.deepMerge(this.config.tools, this.internalTools);
if (!this.config.hasOwnProperty('tools') || Object.keys(this.config.tools).length === 0) {
throw Error('Can\'t start without tools');
@ -379,6 +381,30 @@ export default class Tools extends Module {
return toolPreparationList;
}
/**
* Validate Tools configuration objects and throw Error for user if it is invalid
*/
private validateTools() {
/**
* Check Tools for a class containing
*/
for (const toolName in this.config.tools) {
if (this.config.tools.hasOwnProperty(toolName)) {
if (toolName in this.internalTools) {
return;
}
const tool = this.config.tools[toolName];
if (!_.isFunction(tool) && !_.isFunction((tool as ToolSettings).class)) {
throw Error(
`Tool «${toolName}» must be a constructor function or an object with function in the «class» property`,
);
}
}
}
}
/**
* Returns internal tools
* Includes Bold, Italic, Link and Paragraph

View file

@ -38,9 +38,9 @@ export default class UI extends Module {
* Editor.js UI CSS class names
* @return {{editorWrapper: string, editorZone: string}}
*/
public static get CSS(): {
public get CSS(): {
editorWrapper: string, editorWrapperNarrow: string, editorZone: string, editorZoneHidden: string,
editorLoader: string,
editorLoader: string, editorEmpty: string,
} {
return {
editorWrapper : 'codex-editor',
@ -48,6 +48,7 @@ export default class UI extends Module {
editorZone : 'codex-editor__redactor',
editorZoneHidden : 'codex-editor__redactor--hidden',
editorLoader : 'codex-editor__loader',
editorEmpty : 'codex-editor--empty',
};
}
@ -70,9 +71,9 @@ export default class UI extends Module {
* Adds loader to editor while content is not ready
*/
public addLoader(): void {
this.nodes.loader = $.make('div', UI.CSS.editorLoader);
this.nodes.loader = $.make('div', this.CSS.editorLoader);
this.nodes.wrapper.prepend(this.nodes.loader);
this.nodes.redactor.classList.add(UI.CSS.editorZoneHidden);
this.nodes.redactor.classList.add(this.CSS.editorZoneHidden);
}
/**
@ -80,7 +81,7 @@ export default class UI extends Module {
*/
public removeLoader(): void {
this.nodes.loader.remove();
this.nodes.redactor.classList.remove(UI.CSS.editorZoneHidden);
this.nodes.redactor.classList.remove(this.CSS.editorZoneHidden);
}
/**
@ -142,14 +143,14 @@ export default class UI extends Module {
/**
* Create and save main UI elements
*/
this.nodes.wrapper = $.make('div', UI.CSS.editorWrapper);
this.nodes.redactor = $.make('div', UI.CSS.editorZone);
this.nodes.wrapper = $.make('div', this.CSS.editorWrapper);
this.nodes.redactor = $.make('div', this.CSS.editorZone);
/**
* If Editor has injected into the narrow container, enable Narrow Mode
*/
if (this.nodes.holder.offsetWidth < this.contentWidth) {
this.nodes.wrapper.classList.add(UI.CSS.editorWrapperNarrow);
this.nodes.wrapper.classList.add(this.CSS.editorWrapperNarrow);
}
this.nodes.wrapper.appendChild(this.nodes.redactor);
@ -216,7 +217,7 @@ export default class UI extends Module {
* @param {KeyboardEvent} event
*/
private defaultBehaviour(event: KeyboardEvent): void {
const keyDownOnEditor = (event.target as HTMLElement).closest(`.${UI.CSS.editorWrapper}`);
const keyDownOnEditor = (event.target as HTMLElement).closest(`.${this.CSS.editorWrapper}`);
const {currentBlock} = this.Editor.BlockManager;
const isMetaKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;

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

View file

@ -308,4 +308,34 @@ export default class Util {
public static capitalize(text: string): string {
return text[0].toUpperCase() + text.slice(1);
}
/**
* Merge to objects recursively
* @param {object} target
* @param {object[]} sources
* @return {object}
*/
public static deepMerge(target, ...sources) {
const isObject = (item) => item && typeof item === 'object' && !Array.isArray(item);
if (!sources.length) { return target; }
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) {
Object.assign(target, { [key]: {} });
}
Util.deepMerge(target[key], source[key]);
} else {
Object.assign(target, { [key]: source[key] });
}
}
}
return Util.deepMerge(target, ...sources);
}
}

View file

@ -115,6 +115,10 @@
opacity: 0.3;
}
.codex-editor--toolbox-opened [contentEditable=true][data-placeholder]:focus::before {
opacity: 0;
}
@keyframes editor-loader-spin {
0% {
transform: rotate(0deg);