mirror of
https://github.com/codex-team/editor.js
synced 2026-03-16 15:45:47 +01:00
[Feature] Keyboard cbs (#824)
This commit is contained in:
parent
8b328ed7f9
commit
2243f55b04
8 changed files with 271 additions and 168 deletions
6
dist/editor.js
vendored
6
dist/editor.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -82,7 +82,7 @@ export default class BlockEvents extends Module {
|
|||
|
||||
if (!isShortcut) {
|
||||
this.Editor.BlockManager.clearFocused();
|
||||
this.Editor.BlockSelection.clearSelection();
|
||||
this.Editor.BlockSelection.clearSelection(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -93,6 +93,14 @@ export default class BlockEvents extends Module {
|
|||
* - shows conversion toolbar with 85% of block selection
|
||||
*/
|
||||
public keyup(event): void {
|
||||
|
||||
/**
|
||||
* If shift key was pressed some special shortcut is used (eg. cross block selection via shift + arrows)
|
||||
*/
|
||||
if (event.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { InlineToolbar, ConversionToolbar, UI, BlockManager } = this.Editor;
|
||||
const block = BlockManager.getBlock(event.target);
|
||||
|
||||
|
|
@ -157,7 +165,7 @@ export default class BlockEvents extends Module {
|
|||
* @param {MouseEvent} event
|
||||
*/
|
||||
public mouseDown(event: MouseEvent): void {
|
||||
this.Editor.MouseSelection.watchSelection(event);
|
||||
this.Editor.CrossBlockSelection.watchSelection(event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -168,7 +176,7 @@ export default class BlockEvents extends Module {
|
|||
/**
|
||||
* Clear blocks selection by tab
|
||||
*/
|
||||
this.Editor.BlockSelection.clearSelection();
|
||||
this.Editor.BlockSelection.clearSelection(event);
|
||||
|
||||
const { BlockManager, Tools, ConversionToolbar, InlineToolbar } = this.Editor;
|
||||
const currentBlock = BlockManager.currentBlock;
|
||||
|
|
@ -213,7 +221,7 @@ export default class BlockEvents extends Module {
|
|||
/**
|
||||
* Clear blocks selection by ESC
|
||||
*/
|
||||
this.Editor.BlockSelection.clearSelection();
|
||||
this.Editor.BlockSelection.clearSelection(event);
|
||||
|
||||
if (this.Editor.Toolbox.opened) {
|
||||
this.Editor.Toolbox.close();
|
||||
|
|
@ -296,7 +304,7 @@ export default class BlockEvents extends Module {
|
|||
Caret.setToBlock(BlockManager.insertInitialBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
|
||||
|
||||
/** Clear selection */
|
||||
BlockSelection.clearSelection();
|
||||
BlockSelection.clearSelection(event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -416,7 +424,7 @@ export default class BlockEvents extends Module {
|
|||
this.Editor.Toolbar.close();
|
||||
|
||||
/** Clear selection */
|
||||
BlockSelection.clearSelection();
|
||||
BlockSelection.clearSelection(event);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -492,6 +500,13 @@ export default class BlockEvents extends Module {
|
|||
* Handle right and down keyboard keys
|
||||
*/
|
||||
private arrowRightAndDown(event: KeyboardEvent): void {
|
||||
const shouldEnableCBS = this.Editor.Caret.isAtEnd || this.Editor.BlockSelection.anyBlockSelected;
|
||||
|
||||
if (event.shiftKey && event.keyCode === _.keyCodes.DOWN && shouldEnableCBS) {
|
||||
this.Editor.CrossBlockSelection.toggleBlockSelectedState();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.Editor.Caret.navigateNext()) {
|
||||
/**
|
||||
* Default behaviour moves cursor by 1 character, we need to prevent it
|
||||
|
|
@ -512,13 +527,20 @@ export default class BlockEvents extends Module {
|
|||
/**
|
||||
* Clear blocks selection by arrows
|
||||
*/
|
||||
this.Editor.BlockSelection.clearSelection();
|
||||
this.Editor.BlockSelection.clearSelection(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle left and up keyboard keys
|
||||
*/
|
||||
private arrowLeftAndUp(event: KeyboardEvent): void {
|
||||
const shouldEnableCBS = this.Editor.Caret.isAtStart || this.Editor.BlockSelection.anyBlockSelected;
|
||||
|
||||
if (event.shiftKey && event.keyCode === _.keyCodes.UP && shouldEnableCBS) {
|
||||
this.Editor.CrossBlockSelection.toggleBlockSelectedState(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.Editor.Caret.navigatePrevious()) {
|
||||
/**
|
||||
* Default behaviour moves cursor by 1 character, we need to prevent it
|
||||
|
|
@ -539,7 +561,7 @@ export default class BlockEvents extends Module {
|
|||
/**
|
||||
* Clear blocks selection by arrows
|
||||
*/
|
||||
this.Editor.BlockSelection.clearSelection();
|
||||
this.Editor.BlockSelection.clearSelection(event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ export default class BlockSelection extends Module {
|
|||
* @return {Block[]}
|
||||
*/
|
||||
public get selectedBlocks(): Block[] {
|
||||
return this.Editor.BlockManager.blocks.filter((block) => block.selected);
|
||||
return this.Editor.BlockManager.blocks.filter((block: Block) => block.selected);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -161,14 +161,33 @@ export default class BlockSelection extends Module {
|
|||
|
||||
/**
|
||||
* Clear selection from Blocks
|
||||
*
|
||||
* @param {Event} reason - event caused clear of selection
|
||||
* @param {boolean} restoreSelection - if true, restore saved selection
|
||||
*/
|
||||
public clearSelection(restoreSelection = false) {
|
||||
const {RectangleSelection} = this.Editor;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -48,16 +48,9 @@ export default class Caret extends Module {
|
|||
* @return {boolean}
|
||||
*/
|
||||
public get isAtStart(): boolean {
|
||||
/**
|
||||
* Don't handle ranges
|
||||
*/
|
||||
if (!Selection.isCollapsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const selection = Selection.get();
|
||||
const firstNode = $.getDeepestNode(this.Editor.BlockManager.currentBlock.currentInput);
|
||||
let anchorNode = selection.anchorNode;
|
||||
let focusNode = selection.focusNode;
|
||||
|
||||
/** In case lastNode is native input */
|
||||
if ($.isNativeInput(firstNode)) {
|
||||
|
|
@ -75,7 +68,7 @@ export default class Caret extends Module {
|
|||
* @type {number}
|
||||
*/
|
||||
|
||||
let firstLetterPosition = anchorNode.textContent.search(/\S/);
|
||||
let firstLetterPosition = focusNode.textContent.search(/\S/);
|
||||
|
||||
if (firstLetterPosition === -1) { // empty text
|
||||
firstLetterPosition = 0;
|
||||
|
|
@ -88,16 +81,16 @@ export default class Caret extends Module {
|
|||
* In this case, anchor node has ELEMENT_NODE node type.
|
||||
* Anchor offset shows amount of children between start of the element and caret position.
|
||||
*
|
||||
* So we use child with anchorOffset index as new anchorNode.
|
||||
* So we use child with focusOffset index as new anchorNode.
|
||||
*/
|
||||
let anchorOffset = selection.anchorOffset;
|
||||
if (anchorNode.nodeType !== Node.TEXT_NODE && anchorNode.childNodes.length) {
|
||||
if (anchorNode.childNodes[anchorOffset]) {
|
||||
anchorNode = anchorNode.childNodes[anchorOffset];
|
||||
anchorOffset = 0;
|
||||
let focusOffset = selection.focusOffset;
|
||||
if (focusNode.nodeType !== Node.TEXT_NODE && focusNode.childNodes.length) {
|
||||
if (focusNode.childNodes[focusOffset]) {
|
||||
focusNode = focusNode.childNodes[focusOffset];
|
||||
focusOffset = 0;
|
||||
} else {
|
||||
anchorNode = anchorNode.childNodes[anchorOffset - 1];
|
||||
anchorOffset = anchorNode.textContent.length;
|
||||
focusNode = focusNode.childNodes[focusOffset - 1];
|
||||
focusOffset = focusNode.textContent.length;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,11 +98,11 @@ export default class Caret extends Module {
|
|||
* In case of
|
||||
* <div contenteditable>
|
||||
* <p><b></b></p> <-- first (and deepest) node is <b></b>
|
||||
* |adaddad <-- anchor node
|
||||
* |adaddad <-- focus node
|
||||
* </div>
|
||||
*/
|
||||
if ($.isLineBreakTag(firstNode as HTMLElement) || $.isEmpty(firstNode)) {
|
||||
const leftSiblings = this.getHigherLevelSiblings(anchorNode as HTMLElement, 'left');
|
||||
const leftSiblings = this.getHigherLevelSiblings(focusNode as HTMLElement, 'left');
|
||||
const nothingAtLeft = leftSiblings.every((node) => {
|
||||
/**
|
||||
* Workaround case when block starts with several <br>'s (created by SHIFT+ENTER)
|
||||
|
|
@ -126,7 +119,7 @@ export default class Caret extends Module {
|
|||
return $.isEmpty(node) && !isLineBreak;
|
||||
});
|
||||
|
||||
if (nothingAtLeft && anchorOffset === firstLetterPosition) {
|
||||
if (nothingAtLeft && focusOffset === firstLetterPosition) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -135,7 +128,7 @@ export default class Caret extends Module {
|
|||
* We use <= comparison for case:
|
||||
* "| Hello" <--- selection.anchorOffset is 0, but firstLetterPosition is 1
|
||||
*/
|
||||
return firstNode === null || anchorNode === firstNode && anchorOffset <= firstLetterPosition;
|
||||
return firstNode === null || focusNode === firstNode && focusOffset <= firstLetterPosition;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -143,15 +136,8 @@ export default class Caret extends Module {
|
|||
* @return {boolean}
|
||||
*/
|
||||
public get isAtEnd(): boolean {
|
||||
/**
|
||||
* Don't handle ranges
|
||||
*/
|
||||
if (!Selection.isCollapsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const selection = Selection.get();
|
||||
let anchorNode = selection.anchorNode;
|
||||
let focusNode = selection.focusNode;
|
||||
|
||||
const lastNode = $.getDeepestNode(this.Editor.BlockManager.currentBlock.currentInput, true);
|
||||
|
||||
|
|
@ -161,7 +147,7 @@ export default class Caret extends Module {
|
|||
}
|
||||
|
||||
/** Case when selection have been cleared programmatically, for example after CBS */
|
||||
if (!selection.anchorNode) {
|
||||
if (!selection.focusNode) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -172,16 +158,16 @@ export default class Caret extends Module {
|
|||
* In this case, anchor node has ELEMENT_NODE node type.
|
||||
* Anchor offset shows amount of children between start of the element and caret position.
|
||||
*
|
||||
* So we use child with anchorOffset - 1 as new anchorNode.
|
||||
* So we use child with anchofocusOffset - 1 as new focusNode.
|
||||
*/
|
||||
let anchorOffset = selection.anchorOffset;
|
||||
if (anchorNode.nodeType !== Node.TEXT_NODE && anchorNode.childNodes.length) {
|
||||
if (anchorNode.childNodes[anchorOffset - 1]) {
|
||||
anchorNode = anchorNode.childNodes[anchorOffset - 1];
|
||||
anchorOffset = anchorNode.textContent.length;
|
||||
let focusOffset = selection.focusOffset;
|
||||
if (focusNode.nodeType !== Node.TEXT_NODE && focusNode.childNodes.length) {
|
||||
if (focusNode.childNodes[focusOffset - 1]) {
|
||||
focusNode = focusNode.childNodes[focusOffset - 1];
|
||||
focusOffset = focusNode.textContent.length;
|
||||
} else {
|
||||
anchorNode = anchorNode.childNodes[0];
|
||||
anchorOffset = 0;
|
||||
focusNode = focusNode.childNodes[0];
|
||||
focusOffset = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -193,8 +179,7 @@ export default class Caret extends Module {
|
|||
* </div>
|
||||
*/
|
||||
if ($.isLineBreakTag(lastNode as HTMLElement) || $.isEmpty(lastNode)) {
|
||||
const rightSiblings = this.getHigherLevelSiblings(anchorNode as HTMLElement, 'right');
|
||||
|
||||
const rightSiblings = this.getHigherLevelSiblings(focusNode as HTMLElement, 'right');
|
||||
const nothingAtRight = rightSiblings.every((node, i) => {
|
||||
/**
|
||||
* If last right sibling is BR isEmpty returns false, but there actually nothing at right
|
||||
|
|
@ -204,7 +189,7 @@ export default class Caret extends Module {
|
|||
return (isLastBR) || $.isEmpty(node) && !$.isLineBreakTag(node);
|
||||
});
|
||||
|
||||
if (nothingAtRight && anchorOffset === anchorNode.textContent.length) {
|
||||
if (nothingAtRight && focusOffset === focusNode.textContent.length) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -221,7 +206,7 @@ export default class Caret extends Module {
|
|||
* We use >= comparison for case:
|
||||
* "Hello |" <--- selection.anchorOffset is 7, but rightTrimmedText is 6
|
||||
*/
|
||||
return anchorNode === lastNode && anchorOffset >= rightTrimmedText.length;
|
||||
return focusNode === lastNode && focusOffset >= rightTrimmedText.length;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
183
src/components/modules/crossBlockSelection.ts
Normal file
183
src/components/modules/crossBlockSelection.ts
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
import Module from '../__module';
|
||||
import Block from '../block';
|
||||
import SelectionUtils from '../selection';
|
||||
import _ from '../utils';
|
||||
|
||||
export default class CrossBlockSelection extends Module {
|
||||
/**
|
||||
* Block where selection is started
|
||||
*/
|
||||
private firstSelectedBlock: Block;
|
||||
|
||||
/**
|
||||
* Last selected Block
|
||||
*/
|
||||
private lastSelectedBlock: Block;
|
||||
|
||||
/**
|
||||
* Sets up listeners
|
||||
*
|
||||
* @param {MouseEvent} event - mouse down event
|
||||
*/
|
||||
public watchSelection(event: MouseEvent): void {
|
||||
const {BlockManager, UI, Listeners} = this.Editor;
|
||||
|
||||
this.firstSelectedBlock = BlockManager.getBlock(event.target as HTMLElement);
|
||||
this.lastSelectedBlock = this.firstSelectedBlock;
|
||||
|
||||
Listeners.on(document, 'mouseover', this.onMouseOver);
|
||||
Listeners.on(document, 'mouseup', this.onMouseUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change selection state of the next Block
|
||||
* Used for CBS via Shift + arrow keys
|
||||
*
|
||||
* @param {boolean} next - if true, toggle next block. Previous otherwise
|
||||
*/
|
||||
public toggleBlockSelectedState(next: boolean = true): void {
|
||||
const {BlockManager} = this.Editor;
|
||||
|
||||
if (!this.lastSelectedBlock) {
|
||||
this.lastSelectedBlock = this.firstSelectedBlock = BlockManager.currentBlock;
|
||||
}
|
||||
|
||||
if (this.firstSelectedBlock === this.lastSelectedBlock) {
|
||||
this.firstSelectedBlock.selected = true;
|
||||
SelectionUtils.get().removeAllRanges();
|
||||
}
|
||||
|
||||
const nextBlockIndex = BlockManager.blocks.indexOf(this.lastSelectedBlock) + (next ? 1 : -1);
|
||||
const nextBlock = BlockManager.blocks[nextBlockIndex];
|
||||
|
||||
if (!nextBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.lastSelectedBlock.selected !== nextBlock.selected) {
|
||||
nextBlock.selected = true;
|
||||
} else {
|
||||
this.lastSelectedBlock.selected = false;
|
||||
}
|
||||
|
||||
this.lastSelectedBlock = nextBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear saved state
|
||||
*
|
||||
* @param {Event} reason - event caused clear of selection
|
||||
*/
|
||||
public clear(reason?: Event) {
|
||||
const {BlockManager, BlockSelection, Caret} = this.Editor;
|
||||
const fIndex = BlockManager.blocks.indexOf(this.firstSelectedBlock);
|
||||
const lIndex = BlockManager.blocks.indexOf(this.lastSelectedBlock);
|
||||
|
||||
if (BlockSelection.anyBlockSelected && fIndex > -1 && lIndex > -1) {
|
||||
if (reason && reason instanceof KeyboardEvent) {
|
||||
/**
|
||||
* Set caret depending on pressed key if pressed key is an arrow.
|
||||
*/
|
||||
switch (reason.keyCode) {
|
||||
case _.keyCodes.DOWN:
|
||||
case _.keyCodes.RIGHT:
|
||||
Caret.setToBlock(BlockManager.blocks[Math.max(fIndex, lIndex)], Caret.positions.END);
|
||||
break;
|
||||
|
||||
case _.keyCodes.UP:
|
||||
case _.keyCodes.LEFT:
|
||||
Caret.setToBlock(BlockManager.blocks[Math.min(fIndex, lIndex)], Caret.positions.START);
|
||||
break;
|
||||
default:
|
||||
Caret.setToBlock(BlockManager.blocks[Math.max(fIndex, lIndex)], Caret.positions.END);
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* By default set caret at the end of the last selected block
|
||||
*/
|
||||
Caret.setToBlock(BlockManager.blocks[Math.max(fIndex, lIndex)], Caret.positions.END);
|
||||
}
|
||||
}
|
||||
|
||||
this.firstSelectedBlock = this.lastSelectedBlock = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse up event handler.
|
||||
* Removes the listeners
|
||||
*/
|
||||
private onMouseUp = (): void => {
|
||||
const {Listeners} = this.Editor;
|
||||
|
||||
Listeners.off(document, 'mouseover', this.onMouseOver);
|
||||
Listeners.off(document, 'mouseup', this.onMouseUp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse over event handler
|
||||
* Gets target and related blocks and change selected state for blocks in between
|
||||
*
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
private onMouseOver = (event: MouseEvent): void => {
|
||||
const {BlockManager} = this.Editor;
|
||||
|
||||
const relatedBlock = BlockManager.getBlockByChildNode(event.relatedTarget as Node) || this.lastSelectedBlock;
|
||||
const targetBlock = BlockManager.getBlockByChildNode(event.target as Node);
|
||||
|
||||
if (!relatedBlock || !targetBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetBlock === relatedBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (relatedBlock === this.firstSelectedBlock) {
|
||||
SelectionUtils.get().removeAllRanges();
|
||||
|
||||
relatedBlock.selected = true;
|
||||
targetBlock.selected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetBlock === this.firstSelectedBlock) {
|
||||
relatedBlock.selected = false;
|
||||
targetBlock.selected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleBlocksSelectedState(relatedBlock, targetBlock);
|
||||
this.lastSelectedBlock = targetBlock;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change blocks selection state between passed two blocks.
|
||||
*
|
||||
* @param {Block} firstBlock
|
||||
* @param {Block} lastBlock
|
||||
*/
|
||||
private toggleBlocksSelectedState(firstBlock: Block, lastBlock: Block): void {
|
||||
const {BlockManager} = this.Editor;
|
||||
const fIndex = BlockManager.blocks.indexOf(firstBlock);
|
||||
const lIndex = BlockManager.blocks.indexOf(lastBlock);
|
||||
|
||||
/**
|
||||
* If first and last block have the different selection state
|
||||
* it means we should't toggle selection of the first selected block.
|
||||
* In the other case we shouldn't toggle the last selected block.
|
||||
*/
|
||||
const shouldntSelectFirstBlock = firstBlock.selected !== lastBlock.selected;
|
||||
|
||||
for (let i = Math.min(fIndex, lIndex); i <= Math.max(fIndex, lIndex); i++) {
|
||||
const block = BlockManager.blocks[i];
|
||||
|
||||
if (
|
||||
block !== this.firstSelectedBlock &&
|
||||
block !== (shouldntSelectFirstBlock ? firstBlock : lastBlock)
|
||||
) {
|
||||
BlockManager.blocks[i].selected = !BlockManager.blocks[i].selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,106 +0,0 @@
|
|||
import Module from '../__module';
|
||||
import Block from '../block';
|
||||
import SelectionUtils from '../selection';
|
||||
|
||||
export default class MouseSelection extends Module {
|
||||
/**
|
||||
* Block where selection is started
|
||||
*/
|
||||
private firstSelectedBlock: Block;
|
||||
|
||||
/**
|
||||
* Sets up listeners
|
||||
*
|
||||
* @param {MouseEvent} event - mouse down event
|
||||
*/
|
||||
public watchSelection(event: MouseEvent): void {
|
||||
const {BlockManager, UI, Listeners} = this.Editor;
|
||||
|
||||
this.firstSelectedBlock = BlockManager.getBlock(event.target as HTMLElement);
|
||||
|
||||
Listeners.on(UI.nodes.redactor, 'mouseover', (mouseOverEvent) => {
|
||||
this.onMouseOver(mouseOverEvent as MouseEvent);
|
||||
});
|
||||
|
||||
Listeners.on(UI.nodes.redactor, 'mouseup', () => {
|
||||
this.onMouseUp();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse up event handler.
|
||||
* Removes the listeners because selection is finished
|
||||
*/
|
||||
private onMouseUp(): void {
|
||||
const {Listeners, UI} = this.Editor;
|
||||
|
||||
Listeners.off(UI.nodes.redactor, 'mouseover');
|
||||
Listeners.off(UI.nodes.redactor, 'mouseup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Mouse over event handler
|
||||
* Gets target and related blocks and change selected state for blocks in between
|
||||
*
|
||||
* @param {MouseEvent} event
|
||||
*/
|
||||
private onMouseOver(event: MouseEvent): void {
|
||||
const {BlockManager} = this.Editor;
|
||||
const relatedBlock = BlockManager.getBlockByChildNode(event.relatedTarget as Node);
|
||||
const targetBlock = BlockManager.getBlockByChildNode(event.target as Node);
|
||||
|
||||
if (!relatedBlock || !targetBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetBlock === relatedBlock) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (relatedBlock === this.firstSelectedBlock) {
|
||||
SelectionUtils.get().removeAllRanges();
|
||||
|
||||
relatedBlock.selected = true;
|
||||
targetBlock.selected = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetBlock === this.firstSelectedBlock) {
|
||||
relatedBlock.selected = false;
|
||||
targetBlock.selected = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this.toggleBlocksSelection(relatedBlock, targetBlock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change blocks selection state between passed two blocks.
|
||||
*
|
||||
* @param {Block} firstBlock
|
||||
* @param {Block} lastBlock
|
||||
*/
|
||||
private toggleBlocksSelection(firstBlock: Block, lastBlock: Block): void {
|
||||
const {BlockManager} = this.Editor;
|
||||
const fIndex = BlockManager.blocks.indexOf(firstBlock);
|
||||
const lIndex = BlockManager.blocks.indexOf(lastBlock);
|
||||
|
||||
/**
|
||||
* If first and last block have the different selection state
|
||||
* it means we should't toggle selection of the first selected block.
|
||||
* In the other case we shouldn't toggle the last selected block.
|
||||
*/
|
||||
const shouldntSelectFirstBlock = firstBlock.selected !== lastBlock.selected;
|
||||
|
||||
for (let i = Math.min(fIndex, lIndex); i <= Math.max(fIndex, lIndex); i++) {
|
||||
const block = BlockManager.blocks[i];
|
||||
|
||||
if (
|
||||
block !== this.firstSelectedBlock &&
|
||||
block !== (shouldntSelectFirstBlock ? firstBlock : lastBlock)
|
||||
) {
|
||||
BlockManager.blocks[i].selected = !BlockManager.blocks[i].selected;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -356,7 +356,7 @@ export default class UI extends Module {
|
|||
Caret.setToBlock(BlockManager.insertInitialBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
|
||||
|
||||
/** Clear selection */
|
||||
BlockSelection.clearSelection();
|
||||
BlockSelection.clearSelection(event);
|
||||
|
||||
/**
|
||||
* Stop propagations
|
||||
|
|
@ -439,7 +439,7 @@ export default class UI extends Module {
|
|||
Caret.setToBlock(BlockManager.insertInitialBlockAtIndex(selectionPositionIndex, true), Caret.positions.START);
|
||||
|
||||
/** Clear selection */
|
||||
BlockSelection.clearSelection();
|
||||
BlockSelection.clearSelection(event);
|
||||
|
||||
/**
|
||||
* Stop propagations
|
||||
|
|
@ -479,7 +479,7 @@ export default class UI extends Module {
|
|||
this.Editor.Toolbar.plusButton.show();
|
||||
}
|
||||
|
||||
this.Editor.BlockSelection.clearSelection();
|
||||
this.Editor.BlockSelection.clearSelection(event);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -504,7 +504,7 @@ export default class UI extends Module {
|
|||
this.Editor.BlockManager.dropPointer();
|
||||
this.Editor.InlineToolbar.close();
|
||||
this.Editor.Toolbar.close();
|
||||
this.Editor.BlockSelection.clearSelection();
|
||||
this.Editor.BlockSelection.clearSelection(event);
|
||||
this.Editor.ConversionToolbar.close();
|
||||
}
|
||||
|
||||
|
|
|
|||
4
src/types-internal/editor-modules.d.ts
vendored
4
src/types-internal/editor-modules.d.ts
vendored
|
|
@ -31,7 +31,7 @@ import Saver from '../components/modules/saver';
|
|||
import BlockSelection from '../components/modules/blockSelection';
|
||||
import RectangleSelection from '../components/modules/RectangleSelection';
|
||||
import InlineToolbarAPI from '../components/modules/api/inlineToolbar';
|
||||
import MouseSelection from '../components/modules/mouseSelection';
|
||||
import CrossBlockSelection from '../components/modules/crossBlockSelection';
|
||||
import ConversionToolbar from '../components/modules/toolbar/conversion';
|
||||
|
||||
export interface EditorModules {
|
||||
|
|
@ -68,6 +68,6 @@ export interface EditorModules {
|
|||
StylesAPI: StylesAPI;
|
||||
ToolbarAPI: ToolbarAPI;
|
||||
InlineToolbarAPI: InlineToolbarAPI;
|
||||
MouseSelection: MouseSelection;
|
||||
CrossBlockSelection: CrossBlockSelection;
|
||||
NotifierAPI: NotifierAPI;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue