editor.js/src/components/modules/blockSelection.ts
Peter Savchenko ac93017c70
Release 2.16 (#966)
* 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

* Minor Docs Fix according to main Readme (#916)

* Inline Toolbar now contains Conversion Toolbar (#932)

* Block lifecycle hooks (#906)

* [Fix] Arrow selection (#964)

* Fix arrow selection

* Add docs

* [issue-926]: fix dom iterator leafing when items are empty (#958)

* [issue-926]: fix dom iterator leafing when items are empty

* update Changelog

* Issue 869 (#963)

* Fix issue 943 (#965)

* [Draft] Feature/tooltip enhancements (#907)

* initial

* update

* make module standalone

* use tooltips as external module

* update

* build via prod mode

* add tooltips as external module

* add declaration file and options param

* add api tooltip

* update

* removed submodule

* removed due to the incorrect setip

* setup tooltips again

* wip

* update tooltip module

* toolbox, inline toolbar

* Tooltips in block tunes not uses shorthand

* shorthand in a plus and block settings

* fix doc

* Update tools-inline.md

* Delete tooltip.css

* Update CHANGELOG.md

* Update codex.tooltips

* Update api.md

* [issue-779]: Grammarly conflicts (#956)

* grammarly conflicts

* update

* upd bundle

* Submodule Header now on master

* Submodule Marker now on master

* Submodule Paragraph now on master

* Submodule InlineCode now on master

* Submodule Simple Image now on master

* [issue-868]: Deleting multiple blocks triggers back button in Firefox (#967)

* Deleting multiple blocks triggers back button in Firefox

@evgenusov

* Update editor.js

* Update CHANGELOG.md

* pass options on removeEventListener (#904)

* pass options on removeEventListener by removeAll

* rebuild

* Merge branch 'release/2.16' into pr/904

* Update CHANGELOG.md

* Update inline.ts

* [Fix] Selection rangecount (#968)

* Fix #952 (#969)

* Update codex.tooltips

* Selection bugfix (#970)

* Selection bugfix

* fix cross block selection

* close inline toolbar when blocks selected via shift

* remove inline toolbar closing on cross block selection mouse up due to the bug (#972)

* [Feature] Log levels (#971)

* Decrease margins (#973)

* Decrease margins

* Update editor.licenses.txt

* Update src/components/domIterator.ts

Co-Authored-By: Murod Khaydarov <murod.haydarov@gmail.com>

* [Fix] Fix delete blocks api method (#974)

* Update docs/usage.md

Co-Authored-By: Murod Khaydarov <murod.haydarov@gmail.com>

* rm unused

* Update yarn.lock file

* upd bundle, changelog
2019-11-30 23:42:39 +03:00

350 lines
8.3 KiB
TypeScript

/**
* @class BlockSelection
* @classdesc Manages Block selection with shortcut CMD+A
*
* @module BlockSelection
* @version 1.0.0
*/
import Module from '../__module';
import Block from '../block';
import * as _ from '../utils';
import $ from '../dom';
import SelectionUtils from '../selection';
export default class BlockSelection extends Module {
/**
* Sanitizer Config
* @return {SanitizerConfig}
*/
private get sanitizerConfig() {
return {
p: {},
h1: {},
h2: {},
h3: {},
h4: {},
h5: {},
h6: {},
ol: {},
ul: {},
li: {},
br: true,
img: {
src: true,
width: true,
height: true,
},
a: {
href: true,
},
b: {},
i: {},
u: {},
};
}
/**
* Flag that identifies all Blocks selection
* @return {boolean}
*/
public get allBlocksSelected(): boolean {
const {BlockManager} = this.Editor;
return BlockManager.blocks.every((block) => block.selected === true);
}
/**
* Set selected all blocks
* @param {boolean} state
*/
public set allBlocksSelected(state: boolean) {
const {BlockManager} = this.Editor;
BlockManager.blocks.forEach((block) => block.selected = state);
}
/**
* Flag that identifies any Block selection
* @return {boolean}
*/
public get anyBlockSelected(): boolean {
const {BlockManager} = this.Editor;
return BlockManager.blocks.some((block) => block.selected === true);
}
/**
* Return selected Blocks array
* @return {Block[]}
*/
public get selectedBlocks(): Block[] {
return this.Editor.BlockManager.blocks.filter((block: Block) => block.selected);
}
/**
* Flag used to define block selection
* First CMD+A defines it as true and then second CMD+A selects all Blocks
* @type {boolean}
*/
private needToSelectAll: boolean = false;
/**
* Flag used to define native input selection
* In this case we allow double CMD+A to select Block
* @type {boolean}
*/
private nativeInputSelected: boolean = false;
/**
* Flag identifies any input selection
* That means we can select whole Block
* @type {boolean}
*/
private readyToBlockSelection: boolean = false;
/**
* SelectionUtils instance
* @type {SelectionUtils}
*/
private selection: SelectionUtils;
/**
* Module Preparation
* Registers Shortcuts CMD+A and CMD+C
* to select all and copy them
*/
public prepare(): void {
const {Shortcuts} = this.Editor;
/** Selection shortcut */
Shortcuts.add({
name: 'CMD+A',
handler: (event) => {
const {BlockManager} = this.Editor;
/**
* When one page consist of two or more EditorJS instances
* Shortcut module tries to handle all events. Thats why Editor's selection works inside the target Editor, but
* for others error occurs because nothing to select.
*
* Prevent such actions if focus is not inside the Editor
*/
if (!BlockManager.currentBlock) {
return;
}
this.handleCommandA(event);
},
});
this.selection = new SelectionUtils();
}
/**
* Remove selection of Block
* @param {number?} index - Block index according to the BlockManager's indexes
*/
public unSelectBlockByIndex(index?) {
const {BlockManager} = this.Editor;
let block;
if (isNaN(index)) {
block = BlockManager.currentBlock;
} else {
block = BlockManager.getBlockByIndex(index);
}
block.selected = false;
}
/**
* Clear selection from Blocks
*
* @param {Event} reason - event caused clear of selection
* @param {boolean} restoreSelection - if true, restore saved selection
*/
public clearSelection(reason?: Event, restoreSelection = false) {
const {BlockManager, Caret, RectangleSelection} = this.Editor;
this.needToSelectAll = false;
this.nativeInputSelected = false;
this.readyToBlockSelection = false;
/**
* If reason caused clear of the selection was printable key and any block is selected,
* remove selected blocks and insert pressed key
*/
if (this.anyBlockSelected && reason && reason instanceof KeyboardEvent && _.isPrintableKey(reason.keyCode)) {
const indexToInsert = BlockManager.removeSelectedBlocks();
BlockManager.insertInitialBlockAtIndex(indexToInsert, true);
Caret.setToBlock(BlockManager.currentBlock);
_.delay(() => {
Caret.insertContentAtCaretPosition(reason.key);
}, 20)();
}
this.Editor.CrossBlockSelection.clear(reason);
if (!this.anyBlockSelected || RectangleSelection.isRectActivated()) {
this.Editor.RectangleSelection.clearSelection();
return;
}
/**
* Restore selection when Block is already selected
* but someone tries to write something.
*/
if (restoreSelection) {
this.selection.restore();
}
/** Now all blocks cleared */
this.allBlocksSelected = false;
}
/**
* Reduce each Block and copy its content
*/
public copySelectedBlocks(): void {
const fakeClipboard = $.make('div');
this.selectedBlocks.forEach((block) => {
/**
* Make <p> tag that holds clean HTML
*/
const cleanHTML = this.Editor.Sanitizer.clean(block.holder.innerHTML, this.sanitizerConfig);
const fragment = $.make('p');
fragment.innerHTML = cleanHTML;
fakeClipboard.appendChild(fragment);
});
_.copyTextToClipboard(fakeClipboard.innerHTML);
}
/**
* select Block
* @param {number?} index - Block index according to the BlockManager's indexes
*/
public selectBlockByIndex(index?) {
const {BlockManager} = this.Editor;
/**
* Remove previous focused Block's state
*/
BlockManager.clearFocused();
let block;
if (isNaN(index)) {
block = BlockManager.currentBlock;
} else {
block = BlockManager.getBlockByIndex(index);
}
/** Save selection */
this.selection.save();
SelectionUtils.get()
.removeAllRanges();
block.selected = true;
/** close InlineToolbar when we selected any Block */
this.Editor.InlineToolbar.close();
}
/**
* First CMD+A selects all input content by native behaviour,
* next CMD+A keypress selects all blocks
*
* @param {KeyboardEvent} event
*/
private handleCommandA(event: KeyboardEvent): void {
this.Editor.RectangleSelection.clearSelection();
/** allow default selection on native inputs */
if ($.isNativeInput(event.target) && !this.readyToBlockSelection) {
this.readyToBlockSelection = true;
return;
}
const workingBlock = this.Editor.BlockManager.getBlock(event.target as HTMLElement);
const inputs = workingBlock.inputs;
/**
* If Block has more than one editable element allow native selection
* Second cmd+a will select whole Block
*/
if (inputs.length > 1 && !this.readyToBlockSelection) {
this.readyToBlockSelection = true;
return;
}
if (inputs.length === 1 && !this.needToSelectAll) {
this.needToSelectAll = true;
return;
}
if (this.needToSelectAll) {
/**
* Prevent default selection
*/
event.preventDefault();
this.selectAllBlocks();
/**
* Disable any selection after all Blocks selected
*/
this.needToSelectAll = false;
this.readyToBlockSelection = false;
/**
* Close ConversionToolbar when all Blocks selected
*/
this.Editor.ConversionToolbar.close();
} else if (this.readyToBlockSelection) {
/**
* prevent default selection when we use custom selection
*/
event.preventDefault();
/**
* select working Block
*/
this.selectBlockByIndex();
/**
* Enable all Blocks selection if current Block is selected
*/
this.needToSelectAll = true;
}
}
/**
* Select All Blocks
* Each Block has selected setter that makes Block copyable
*/
private selectAllBlocks() {
/**
* Save selection
* Will be restored when closeSelection fired
*/
this.selection.save();
/**
* Remove Ranges from Selection
*/
SelectionUtils.get()
.removeAllRanges();
this.allBlocksSelected = true;
/** close InlineToolbar if we selected all Blocks */
this.Editor.InlineToolbar.close();
}
}