editor.js/src/components/modules/ui.ts
Peter Savchenko 0600233e63
Release/2.15.1 (#888)
* Feature: mobile ux improvements (#802)

* Inline Toolbar now works on mobile devices

* Some styles updates

* Inline Toolbar now detects left and rigth sides of content

* listen selection change only on touch devices

* improve toolbar paddings

* invalidate content rectangle cache on resize

* Toolbar now works on mobile devices

* prod bundle

* add changelog

* upd texts

* do not hide toolbar on window resize

* rm sever

* Added typeof method (#812)

* Add typeof method to check exact type of object #805

* Update changelog

* [Feature/issue-704]: Conversion toolbar (#787)

* [issue-748]: update Readme (#773)

* Do not start multi-block selection on UI elements (#662)

* Do not start multi-block selection on UI elements

* Do not prevent mousedown event on inline toolbar actions

* Remove log

* Add comment

* Add link to issue

closes #646

* Fix loss of pointer (#666)

* Fix loss of pointer when click is outside of the editor but selection is inside

* Remove log

* Update shortcuts module (#685)

* Fixed possible grammatical typo (#681)

Thanks

* Update shortcuts module

* update changelog

* update

* Remove margin top for inline-link icon (#690)

* Remove margin top for inline-link icon

resolves #674

* Update CHANGELOG.md

* Remove unused style

* Pull fresh tools

* Remove changelog contents from readme (#700)

* #665 API to open and close inline-toolbar (#711)

* API to open and close inline-toolbar

* Fixed documentation

* renamed inline -> inline-toolbar

* removed dist

* reset editor.js

* added editor.js bundle

* Fixed build error

* Null checks on toolbar/inline@open

* updated bundle

* Improve some comments

* Updatd api.md CHANGELOG.md

* Change feature to new instead of improvement

* Allow holderId work with ref on dom element (#710)

* done

* update types

* attempt to simplify code

* remove useless helper

* revert holderId logic and add holder property

* Apply suggestions from code review

Co-Authored-By: dimensi <eddimensi@gmail.com>

* update holder type on string | HTMLElement

* fix typo

* add deprecated notice and fix typos

* fix wrong compare

* fix comments

* swap console.log on _.log

* update types for editor config

* update examples

* update docs

* update build

* Activating Open Collective (#736)

Hi, I'm making updates for Open Collective. Either you or a supporter signed this repo up for Open Collective. This pull request adds backers and sponsors from your Open Collective https://opencollective.com/editorjs❤️

It adds two badges at the top to show the latest number of backers and sponsors. It also adds placeholders so that the avatar/logo of new backers/sponsors can automatically be shown without having to update your README.md. [more info](https://github.com/opencollective/opencollective/wiki/Github-banner). See how it looks on this [repo](https://github.com/apex/apex#backers).

You can also add a postinstall script to let people know after npm|yarn install that you are welcoming donations (optional). [More info](https://github.com/OpenCollective/opencollective-cli)
You can also add a "Donate" button to your website and automatically show your backers and sponsors there with our widgets. Have a look here: https://opencollective.com/widgets

P.S: As with any pull request, feel free to comment or suggest changes. The only thing "required" are the placeholders on the README because we believe it's important to acknowledge the people in your community that are contributing (financially or with code!).

Thank you for your great contribution to the open source community. You are awesome! 🙌
And welcome to the open collective community! 😊

Come chat with us in the #opensource channel on https://slack.opencollective.com - great place to ask questions and share best practices with other open source sustainers!

* Do not install editor.js as dev-dependency (#731)

Resolves #730

* Move codex-notifier to dependencies for typescript declarations (#728)

* Close inline toolbar after creating new link by pressing ENTER (#722)

* Method to clear current selection and close inline toolbar

* clearSelection with optional collapsed range

* refactored selection.ts

* removed experimental function

* Update src/components/selection.ts

Co-Authored-By: tanmayv <12tanmayvijay@gmail.com>

* update version, add changelog

* Link Logo Image to homepage (#738)

* Update README.md (#744)

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

Co-Authored-By: neSpecc <specc.dev@gmail.com>

* Config minHeight option that allows to customize bottom zone (#745)

* issue-739: allow Block's editable element selection (#747)

* issue-739: allow Block's input selection

* little improvements

* update Changelog and cache inputs

* small fix

* delete map file

* fix inputs.count condition

* Fix typo in example paragraph (#749)

* Fix typo

* Update example-dev.html

* minor release

* update link to the minified script

* bump version

* Prefer user settings during tools merge (#774)

* Prefer user settings during tools merge

* issue-705: Leaf BlockSettings and InlineTools via keyboard (#723)

* Do not start multi-block selection on UI elements (#662)

* Do not start multi-block selection on UI elements

* Do not prevent mousedown event on inline toolbar actions

* Remove log

* Add comment

* Add link to issue

closes #646

* Fix loss of pointer (#666)

* Fix loss of pointer when click is outside of the editor but selection is inside

* Remove log

* Update shortcuts module (#685)

* Fixed possible grammatical typo (#681)

Thanks

* Update shortcuts module

* update changelog

* update

* Remove margin top for inline-link icon (#690)

* Remove margin top for inline-link icon

resolves #674

* Update CHANGELOG.md

* Remove unused style

* Pull fresh tools

* Remove changelog contents from readme (#700)

* #665 API to open and close inline-toolbar (#711)

* API to open and close inline-toolbar

* Fixed documentation

* renamed inline -> inline-toolbar

* removed dist

* reset editor.js

* added editor.js bundle

* Fixed build error

* Null checks on toolbar/inline@open

* updated bundle

* Improve some comments

* Updatd api.md CHANGELOG.md

* Change feature to new instead of improvement

* leaf buttons: initial

* leaf inline toolbar buttons

* Allow holderId work with ref on dom element (#710)

* done

* update types

* attempt to simplify code

* remove useless helper

* revert holderId logic and add holder property

* Apply suggestions from code review

Co-Authored-By: dimensi <eddimensi@gmail.com>

* update holder type on string | HTMLElement

* fix typo

* add deprecated notice and fix typos

* fix wrong compare

* fix comments

* swap console.log on _.log

* update types for editor config

* update examples

* update docs

* update build

* leaf inline tools and drop index after click

* leaf toolbox and clear active button after activation

* debugging blockSettings

* Activating Open Collective (#736)

Hi, I'm making updates for Open Collective. Either you or a supporter signed this repo up for Open Collective. This pull request adds backers and sponsors from your Open Collective https://opencollective.com/editorjs❤️

It adds two badges at the top to show the latest number of backers and sponsors. It also adds placeholders so that the avatar/logo of new backers/sponsors can automatically be shown without having to update your README.md. [more info](https://github.com/opencollective/opencollective/wiki/Github-banner). See how it looks on this [repo](https://github.com/apex/apex#backers).

You can also add a postinstall script to let people know after npm|yarn install that you are welcoming donations (optional). [More info](https://github.com/OpenCollective/opencollective-cli)
You can also add a "Donate" button to your website and automatically show your backers and sponsors there with our widgets. Have a look here: https://opencollective.com/widgets

P.S: As with any pull request, feel free to comment or suggest changes. The only thing "required" are the placeholders on the README because we believe it's important to acknowledge the people in your community that are contributing (financially or with code!).

Thank you for your great contribution to the open source community. You are awesome! 🙌
And welcome to the open collective community! 😊

Come chat with us in the #opensource channel on https://slack.opencollective.com - great place to ask questions and share best practices with other open source sustainers!

* Do not install editor.js as dev-dependency (#731)

Resolves #730

* Move codex-notifier to dependencies for typescript declarations (#728)

* Close inline toolbar after creating new link by pressing ENTER (#722)

* Method to clear current selection and close inline toolbar

* clearSelection with optional collapsed range

* refactored selection.ts

* removed experimental function

* Update src/components/selection.ts

Co-Authored-By: tanmayv <12tanmayvijay@gmail.com>

* update version, add changelog

* Link Logo Image to homepage (#738)

* Update README.md (#744)

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

* Update README.md

Co-Authored-By: neSpecc <specc.dev@gmail.com>

* Config minHeight option that allows to customize bottom zone (#745)

* issue-739: allow Block's editable element selection (#747)

* issue-739: allow Block's input selection

* little improvements

* update Changelog and cache inputs

* small fix

* delete map file

* fix inputs.count condition

* Fix typo in example paragraph (#749)

* Fix typo

* Update example-dev.html

* minor release

* done

* requested changes

* production build

* update package.json

* some improvements

* ready for testing

* update

* ready

* requested changes

* updates

* use setToBlock instead of focus

* active -> focused

* update

* refactor types

* fix inline tools flipping

* inhancements

* rm check for focus at the handleShowingEvent

* fix IT closing after second enter

* add animation to settings buttons

* Click animation

* Add changelog

* do not patch version

* conversion toolbar

* positioning and flipping initial

* conversion toolbar is ready

* save changes

* update

* bump version

* save changes

* ready

* open conversion toolbar via keyup

* save changes

* ready to be reviewed

* restore some useless changes

* several improvements

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* ready

* Update src/components/modules/toolbar/conversion.ts

Co-Authored-By: Peter Savchenko <specc.dev@gmail.com>

* requested changes

* update

* update comment

* define types

* simplify keydowns

* Update blockEvents.ts

* Don't use handleShowingEvent to close Inline/Conversion Toolbars

* battle with events

* bundle

* bundle

* rm console.trace

* bundle

* small improvements

* improve types description

* add docs, improve types

* improve anchor links

* fix move

* fix toolbar movement

* conversion toolbar fix movement

* improve margins

* bundle

* add changelog

* rm commented code

* tools goes to master

* Enable cross-block mouse selection (#737)

* Add support of different link types (#817)

* [Fix]: issue with leading new lines (#816)

* [Fix] Tool setting types (#825)

* [Feature]: insert() API method (#814)

* [Fix]: Handle native inputs changes (#815)

* [Feature] Keyboard cbs (#824)

* Updated Readme.md (#811)

* Update CHANGELOG.md

* Add option to disable paste handling (#813)

* Fix: hide Inlie Toolbar when user strart typing

* Enable cbs only for left mouse button

* Change changelog order

* Update submodules

* Update bundle

* 2.16.0

* [Refactor] Separate internal and external settings (#845)

* Enable flipping tools via standalone class (#830)

* Enable flipping tools via standalone class

* use flipper to refactor (#842)

* use flipper to refactor

* save changes

* update

* fix flipper on inline toolbar

* ready for testing

* requested changes

* update doc

* updates

* destroy flippers

* some requested changes

* update

* update

* ready

* update

* last changes

* update docs

* Hghl active button of CT, simplify activate/deactivate

* separate dom iterator

* unhardcode directions

* fixed a link in readme.md (#856)

* Fix Block selection via CMD+A (#829)

* Fix Block selection via CMD+A

* Delete editor.js.map

* update

* update

* Update CHANGELOG.md

* Improve style of selected blocks (#858)

* Cross-block-selection style improved

* Update CHANGELOG.md

* Fix case when property 'observer' in modificationObserver is not defined (#866)

* Bump lodash.template from 4.4.0 to 4.5.0 (#885)

Bumps [lodash.template](https://github.com/lodash/lodash) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.4.0...4.5.0)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump eslint-utils from 1.3.1 to 1.4.2 (#886)

Bumps [eslint-utils](https://github.com/mysticatea/eslint-utils) from 1.3.1 to 1.4.2.
- [Release notes](https://github.com/mysticatea/eslint-utils/releases)
- [Commits](https://github.com/mysticatea/eslint-utils/compare/v1.3.1...v1.4.2)

Signed-off-by: dependabot[bot] <support@github.com>

* Bump mixin-deep from 1.3.1 to 1.3.2 (#887)

Bumps [mixin-deep](https://github.com/jonschlinkert/mixin-deep) from 1.3.1 to 1.3.2.
- [Release notes](https://github.com/jonschlinkert/mixin-deep/releases)
- [Commits](https://github.com/jonschlinkert/mixin-deep/compare/1.3.1...1.3.2)

Signed-off-by: dependabot[bot] <support@github.com>

* update bundle and readme

* Update README.md

* upd codeowners, fix funding

* rebuild

* Update CHANGELOG.md

* Fix flipper tab after enter behaviour (#889)

* Fix flipper tab after enter behaviour

* Fix: case when fillper opens to wrong block and disallow navigation by up/down

* Update src/components/flipper.ts

Co-Authored-By: Murod Khaydarov <murod.haydarov@gmail.com>
2019-09-09 13:03:18 +03:00

612 lines
16 KiB
TypeScript

/**
* Prebuilded sprite of SVG icons
*/
import sprite from '../../../dist/sprite.svg';
/**
* Module UI
*
* @type {UI}
*/
import Module from '../__module';
import $ from '../dom';
import _ from '../utils';
import Selection from '../selection';
import Block from '../block';
/**
* @class
*
* @classdesc Makes Editor.js UI:
* <codex-editor>
* <ce-redactor />
* <ce-toolbar />
* <ce-inline-toolbar />
* </codex-editor>
*
* @typedef {UI} UI
* @property {EditorConfig} config - editor configuration {@link EditorJS#configuration}
* @property {Object} Editor - available editor modules {@link EditorJS#moduleInstances}
* @property {Object} nodes -
* @property {Element} nodes.holder - element where we need to append redactor
* @property {Element} nodes.wrapper - <codex-editor>
* @property {Element} nodes.redactor - <ce-redactor>
*/
export default class UI extends Module {
/**
* Editor.js UI CSS class names
* @return {{editorWrapper: string, editorZone: string}}
*/
public get CSS(): {
editorWrapper: string, editorWrapperNarrow: string, editorZone: string, editorZoneHidden: string,
editorLoader: string, editorEmpty: string,
} {
return {
editorWrapper : 'codex-editor',
editorWrapperNarrow : 'codex-editor--narrow',
editorZone : 'codex-editor__redactor',
editorZoneHidden : 'codex-editor__redactor--hidden',
editorLoader : 'codex-editor__loader',
editorEmpty : 'codex-editor--empty',
};
}
/**
* Return Width of center column of Editor
* @return {DOMRect}
*/
public get contentRect(): DOMRect {
if (this.contentRectCache) {
return this.contentRectCache;
}
const someBlock = this.nodes.wrapper.querySelector(`.${Block.CSS.content}`);
/**
* When Editor is not ready, there is no Blocks, so return the default value
*/
if (!someBlock) {
return {
width: 650,
left: 0,
right: 0,
} as DOMRect;
}
this.contentRectCache = someBlock.getBoundingClientRect() as DOMRect;
return this.contentRectCache;
}
/**
* Flag that became true on mobile viewport
* @type {boolean}
*/
public isMobile: boolean = false;
/**
* HTML Elements used for UI
*/
public nodes: { [key: string]: HTMLElement } = {
holder: null,
wrapper: null,
redactor: null,
};
/**
* Cache for center column rectangle info
* Invalidates on window resize
* @type {DOMRect}
*/
private contentRectCache: DOMRect = undefined;
/**
* Handle window resize only when it finished
* @type {() => void}
*/
private resizeDebouncer: () => void = _.debounce(() => {
this.windowResize();
}, 200);
/**
* Adds loader to editor while content is not ready
*/
public addLoader(): void {
this.nodes.loader = $.make('div', this.CSS.editorLoader);
this.nodes.wrapper.prepend(this.nodes.loader);
this.nodes.redactor.classList.add(this.CSS.editorZoneHidden);
}
/**
* Removes loader when content has loaded
*/
public removeLoader(): void {
this.nodes.loader.remove();
this.nodes.redactor.classList.remove(this.CSS.editorZoneHidden);
}
/**
* Making main interface
*/
public async prepare(): Promise<void> {
/**
* Detect mobile version
*/
this.checkIsMobile();
/**
* Make main UI elements
*/
await this.make();
/**
* Loader for rendering process
*/
this.addLoader();
/**
* Append SVG sprite
*/
await this.appendSVGSprite();
/**
* Make toolbar
*/
await this.Editor.Toolbar.make();
/**
* Make the Inline toolbar
*/
await this.Editor.InlineToolbar.make();
/**
* Make the Converter tool holder
*/
await this.Editor.ConversionToolbar.make();
/**
* Load and append CSS
*/
await this.loadStyles();
/**
* Bind events for the UI elements
*/
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);
}
/**
* Check if one of Toolbar is opened
* Used to prevent global keydowns (for example, Enter) conflicts with Enter-on-toolbar
* @return {boolean}
*/
public get someToolbarOpened() {
const { Toolbox, BlockSettings, InlineToolbar, ConversionToolbar } = this.Editor;
return BlockSettings.opened || InlineToolbar.opened || ConversionToolbar.opened || Toolbox.opened;
}
/**
* Clean editor`s UI
*/
public destroy(): void {
this.nodes.holder.innerHTML = '';
}
/**
* Check for mobile mode and cache a result
*/
private checkIsMobile() {
this.isMobile = window.innerWidth < 650;
}
/**
* Makes Editor.js interface
* @return {Promise<void>}
*/
private async make(): Promise<void> {
/**
* Element where we need to append Editor.js
* @type {Element}
*/
this.nodes.holder = $.getHolder(this.config.holder);
/**
* Create and save main UI elements
*/
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.contentRect.width) {
this.nodes.wrapper.classList.add(this.CSS.editorWrapperNarrow);
}
/**
* Set customizable bottom zone height
*/
this.nodes.redactor.style.paddingBottom = this.config.minHeight + 'px';
this.nodes.wrapper.appendChild(this.nodes.redactor);
this.nodes.holder.appendChild(this.nodes.wrapper);
}
/**
* Appends CSS
*/
private loadStyles(): void {
/**
* Load CSS
*/
const styles = require('../../styles/main.css');
/**
* Make tag
*/
const tag = $.make('style', null, {
textContent: styles.toString(),
});
/**
* Append styles at the top of HEAD tag
*/
$.prepend(document.head, tag);
}
/**
* Bind events on the Editor.js interface
*/
private bindEvents(): void {
this.Editor.Listeners.on(
this.nodes.redactor,
'click',
(event) => this.redactorClicked(event as MouseEvent),
false,
);
this.Editor.Listeners.on(document, 'keydown', (event) => this.documentKeydown(event as KeyboardEvent), true);
this.Editor.Listeners.on(document, 'click', (event) => this.documentClicked(event as MouseEvent), true);
/**
* Handle selection change on mobile devices for the Inline Toolbar support
*/
if (_.isTouchSupported()) {
this.Editor.Listeners.on(document, 'selectionchange', (event) => {
this.selectionChanged(event as Event);
}, true);
}
this.Editor.Listeners.on(window, 'resize', () => {
this.resizeDebouncer();
}, {
passive: true,
});
}
/**
* Resize window handler
*/
private windowResize(): void {
/**
* Invalidate content zone size cached, because it may be changed
*/
this.contentRectCache = null;
/**
* Detect mobile version
*/
this.checkIsMobile();
}
/**
* All keydowns on document
* @param {Event} event
*/
private documentKeydown(event: KeyboardEvent): void {
switch (event.keyCode) {
case _.keyCodes.ENTER:
this.enterPressed(event);
break;
case _.keyCodes.BACKSPACE:
this.backspacePressed(event);
break;
default:
this.defaultBehaviour(event);
break;
}
}
/**
* Ignore all other document's keydown events
* @param {KeyboardEvent} event
*/
private defaultBehaviour(event: KeyboardEvent): void {
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;
/**
* Ignore keydowns on editor and meta keys
*/
if (keyDownOnEditor || (currentBlock && isMetaKey)) {
return;
}
/**
* Remove all highlights and remove caret
*/
this.Editor.BlockManager.dropPointer();
/**
* Close Toolbar
*/
this.Editor.Toolbar.close();
}
/**
* @param {KeyboardEvent} event
*/
private backspacePressed(event: KeyboardEvent): void {
const {BlockManager, BlockSelection, Caret} = this.Editor;
if (BlockSelection.anyBlockSelected) {
const selectionPositionIndex = BlockManager.removeSelectedBlocks();
Caret.setToBlock(BlockManager.insertInitialBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
/** Clear selection */
BlockSelection.clearSelection(event);
/**
* Stop propagations
* Manipulation with BlockSelections is handled in global backspacePress because they may occur
* with CMD+A or RectangleSelection and they can be handled on document event
*/
event.stopPropagation();
event.stopImmediatePropagation();
}
}
/**
* Enter pressed on document
* @param event
*/
private enterPressed(event: KeyboardEvent): void {
const { BlockManager, BlockSelection, Caret } = this.Editor;
const hasPointerToBlock = BlockManager.currentBlockIndex >= 0;
if (BlockSelection.anyBlockSelected) {
const selectionPositionIndex = BlockManager.removeSelectedBlocks();
Caret.setToBlock(BlockManager.insertInitialBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
/** Clear selection */
BlockSelection.clearSelection(event);
/**
* Stop propagations
* Manipulation with BlockSelections is handled in global enterPress because they may occur
* with CMD+A or RectangleSelection
*/
event.preventDefault();
event.stopImmediatePropagation();
event.stopPropagation();
return;
}
/**
* If Caret is not set anywhere, event target on Enter is always Element that we handle
* In our case it is document.body
*
* So, BlockManager points some Block and Enter press is on Body
* We can create a new block
*/
if (!this.someToolbarOpened && hasPointerToBlock && (event.target as HTMLElement).tagName === 'BODY') {
/**
* Insert initial typed Block
*/
const newBlock = this.Editor.BlockManager.insert();
this.Editor.Caret.setToBlock(newBlock);
/**
* And highlight
*/
this.Editor.BlockManager.highlightCurrentNode();
/**
* Move toolbar and show plus button because new Block is empty
*/
this.Editor.Toolbar.move();
this.Editor.Toolbar.plusButton.show();
}
this.Editor.BlockSelection.clearSelection(event);
}
/**
* All clicks on document
* @param {MouseEvent} event - Click
*/
private documentClicked(event: MouseEvent): void {
/**
* Sometimes we emulate click on some UI elements, for example by Enter on Block Settings button
* We don't need to handle such events, because they handled in other place.
*/
if (!event.isTrusted) {
return;
}
/**
* Close Inline Toolbar when nothing selected
* Do not fire check on clicks at the Inline Toolbar buttons
*/
const target = event.target as HTMLElement;
const clickedInsideOfEditor = this.nodes.holder.contains(target) || Selection.isAtEditor;
if (!clickedInsideOfEditor) {
/**
* Clear highlightings and pointer on BlockManager
*
* Current page might contain several instances
* Click between instances MUST clear focus, pointers and close toolbars
*/
this.Editor.BlockManager.dropPointer();
this.Editor.InlineToolbar.close();
this.Editor.Toolbar.close();
this.Editor.BlockSelection.clearSelection(event);
this.Editor.ConversionToolbar.close();
}
if (Selection.isAtEditor) {
/**
* 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
*
* @param {MouseEvent} event
*
* @description
* 1. Save clicked Block as a current {@link BlockManager#currentNode}
* it uses for the following:
* - add CSS modifier for the selected Block
* - on Enter press, we make a new Block under that
*
* 2. Move and show the Toolbar
*
* 3. Set a Caret
*
* 4. By clicks on the Editor's bottom zone:
* - if last Block is empty, set a Caret to this
* - otherwise, add a new empty Block and set a Caret to that
*
* 5. Hide the Inline Toolbar
*
* @see selectClickedBlock
*
*/
private redactorClicked(event: MouseEvent): void {
if (!Selection.isCollapsed) {
return;
}
let clickedNode = event.target as HTMLElement;
/**
* If click was fired is on Editor`s wrapper, try to get clicked node by elementFromPoint method
*/
if (clickedNode === this.nodes.redactor) {
clickedNode = document.elementFromPoint(event.clientX, event.clientY) as HTMLElement;
}
/**
* Select clicked Block as Current
*/
try {
/**
* Renew Current Block
*/
this.Editor.BlockManager.setCurrentBlockByChildNode(clickedNode);
/**
* Highlight Current Node
*/
this.Editor.BlockManager.highlightCurrentNode();
} catch (e) {
/**
* If clicked outside first-level Blocks and it is not RectSelection, set Caret to the last empty Block
*/
if (!this.Editor.RectangleSelection.isRectActivated()) {
this.Editor.Caret.setToTheLastBlock();
}
}
event.stopImmediatePropagation();
event.stopPropagation();
/**
* Move and open toolbar
*/
this.Editor.Toolbar.open();
/**
* Hide the Plus Button
*/
this.Editor.Toolbar.plusButton.hide();
if (!this.Editor.BlockManager.currentBlock) {
this.Editor.BlockManager.insert();
}
/**
* Show the Plus Button if:
* - Block is an initial-block (Text)
* - Block is empty
*/
const isInitialBlock = this.Editor.Tools.isInitial(this.Editor.BlockManager.currentBlock.tool);
if (isInitialBlock) {
/**
* Check isEmpty only for paragraphs to prevent unnecessary tree-walking on Tools with many nodes (for ex. Table)
*/
const isEmptyBlock = this.Editor.BlockManager.currentBlock.isEmpty;
if (isEmptyBlock) {
this.Editor.Toolbar.plusButton.show();
}
}
}
/**
* Handle selection changes on mobile devices
* Uses for showing the Inline Toolbar
* @param {Event} event
*/
private selectionChanged(event: Event): void {
const focusedElement = Selection.anchorElement as Element;
/**
* Event can be fired on clicks at the Editor elements, for example, at the Inline Toolbar
* We need to skip such firings
*/
if (!focusedElement || !focusedElement.closest(`.${Block.CSS.content}`)) {
return;
}
this.Editor.InlineToolbar.tryToShow();
}
/**
* Append prebuilt sprite with SVG icons
*/
private appendSVGSprite(): void {
const spriteHolder = $.make('div');
spriteHolder.hidden = true;
spriteHolder.style.display = 'none';
spriteHolder.innerHTML = sprite;
$.append(this.nodes.wrapper, spriteHolder);
}
}