Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable cross-block mouse selection #737

Merged
merged 20 commits into from
Jun 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 14 additions & 28 deletions dist/editor.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
- `Improvements` — Toolbar looks better on mobile devices [#706](https://github.com/codex-team/editor.js/issues/706)
- `Fix` — Added `typeof` util method to check exact object type [#805](https://github.com/codex-team/editor.js/issues/805)
- `New` *Conversion Toolbar* — Ability to convert one block to another [#704](https://github.com/codex-team/editor.js/issues/704)
- `New` *Cross-block selection* — Ability to select multiple blocks by mouse [#703](https://github.com/codex-team/editor.js/issues/703)

### 2.14

Expand Down
9 changes: 9 additions & 0 deletions src/components/modules/blockEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,15 @@ export default class BlockEvents extends Module {
}, 30)();
}

/**
* Set up mouse selection handlers
*
* @param {MouseEvent} event
*/
public mouseDown(event: MouseEvent): void {
this.Editor.MouseSelection.watchSelection(event);
}

/**
* Open Toolbox to leaf Tools
* @param {KeyboardEvent} event
Expand Down
1 change: 1 addition & 0 deletions src/components/modules/blockManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,7 @@ export default class BlockManager extends Module {

Listeners.on(block.holder, 'keydown', (event) => BlockEvents.keydown(event as KeyboardEvent), true);
Listeners.on(block.holder, 'mouseup', (event) => BlockEvents.mouseUp(event));
Listeners.on(block.holder, 'mousedown', (event: MouseEvent) => BlockEvents.mouseDown(event));
Listeners.on(block.holder, 'keyup', (event) => BlockEvents.keyup(event));
Listeners.on(block.holder, 'dragover', (event) => BlockEvents.dragOver(event as DragEvent));
Listeners.on(block.holder, 'dragleave', (event) => BlockEvents.dragLeave(event as DragEvent));
Expand Down
4 changes: 3 additions & 1 deletion src/components/modules/blockSelection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,13 @@ export default class BlockSelection extends Module {
* Clear selection from Blocks
*/
public clearSelection(restoreSelection = false) {
const {RectangleSelection} = this.Editor;

this.needToSelectAll = false;
this.nativeInputSelected = false;
this.readyToBlockSelection = false;

if (!this.anyBlockSelected || this.Editor.RectangleSelection.isRectActivated()) {
if (!this.anyBlockSelected || RectangleSelection.isRectActivated()) {
this.Editor.RectangleSelection.clearSelection();
return;
}
Expand Down
15 changes: 8 additions & 7 deletions src/components/modules/listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,8 @@ export default class Listeners extends Module {
public off(
element: EventTarget,
eventType: string,
handler: (event: Event) => void,
options: boolean | AddEventListenerOptions = false,
handler?: (event: Event) => void,
options?: boolean | AddEventListenerOptions,
): void {
const existingListeners = this.findAll(element, eventType, handler);

Expand All @@ -100,19 +100,20 @@ export default class Listeners extends Module {

if (index > 0) {
this.allListeners.splice(index, 1);

listener.element.removeEventListener(listener.eventType, listener.handler, listener.options);
}
});

element.removeEventListener(eventType, handler, options);
}

/**
* @param {EventTarget} element
* @param {String} eventType
* @param {Function} handler
* @return {EventTarget|null}
* @return {ListenerData|null}
*/
public findOne(element: EventTarget, eventType: string, handler: (event: Event) => void): ListenerData {
public findOne(element: EventTarget, eventType?: string, handler?: (event: Event) => void): ListenerData {
const foundListeners = this.findAll(element, eventType, handler);

return foundListeners.length > 0 ? foundListeners[0] : null;
Expand All @@ -122,9 +123,9 @@ export default class Listeners extends Module {
* @param {EventTarget} element
* @param {String} eventType
* @param {Function} handler
* @return {Array}
* @return {ListenerData[]}
*/
public findAll(element: EventTarget, eventType: string, handler: (event: Event) => void): ListenerData[] {
public findAll(element: EventTarget, eventType?: string, handler?: (event: Event) => void): ListenerData[] {
let found;
const foundByEventTargets = element ? this.findByEventTarget(element) : [];

Expand Down
106 changes: 106 additions & 0 deletions src/components/modules/mouseSelection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вообще не понимаю что это за условие. Первый и последний блок ведь никак не связаны. Расскажи, плиз, что это за кейс


for (let i = Math.min(fIndex, lIndex); i <= Math.max(fIndex, lIndex); i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

и почему бы тут цикл не начинать с Math.min(fIndex, lIndex) + 1 до i < Math.max(fIndex, lIndex)

раз ты в условии пропускаешь первый и последний

Если я что-то не понимаю, добавь в комментах, плиз

const block = BlockManager.blocks[i];

if (
block !== this.firstSelectedBlock &&
block !== (shouldntSelectFirstBlock ? firstBlock : lastBlock)
) {
BlockManager.blocks[i].selected = !BlockManager.blocks[i].selected;
}
}
}
}
28 changes: 16 additions & 12 deletions src/components/modules/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,16 +390,23 @@ export default class UI extends Module {
BlockSettings.focusedButton.click();

/**
* Add animation on click
* Focused button can be deleted by click, for example with 'Remove Block' api
*/
BlockSettings.focusedButton.classList.add(BlockSettings.CSS.focusedButtonAnimated);

/**
* Remove animation class
*/
_.delay( () => {
BlockSettings.focusedButton.classList.remove(BlockSettings.CSS.focusedButtonAnimated);
}, 280)();
if (BlockSettings.focusedButton) {
/**
* Add animation on click
*/
BlockSettings.focusedButton.classList.add(BlockSettings.CSS.focusedButtonAnimated);

/**
* Remove animation class
*/
_.delay( () => {
if (BlockSettings.focusedButton) {
BlockSettings.focusedButton.classList.remove(BlockSettings.CSS.focusedButtonAnimated);
}
}, 280)();
}

/**
* Restoring focus on current Block
Expand Down Expand Up @@ -608,9 +615,6 @@ export default class UI extends Module {
this.Editor.Toolbar.plusButton.show();
}
}

/** Clear selection */
this.Editor.BlockSelection.clearSelection();
}

/**
Expand Down
8 changes: 8 additions & 0 deletions src/styles/block.css
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@
animation: selectionBounce 0.2s 1;
animation-fill-mode: forwards;

/**
* Workaround Safari case when user can select inline-fragment with cross-block-selection
*/
& [contenteditable] {
-webkit-user-select: none;
user-select: none;
}

img,
.ce-stub {
opacity: 0.55;
Expand Down
2 changes: 2 additions & 0 deletions src/types-internal/editor-modules.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +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 ConversionToolbar from '../components/modules/toolbar/conversion';

export interface EditorModules {
Expand Down Expand Up @@ -67,5 +68,6 @@ export interface EditorModules {
StylesAPI: StylesAPI;
ToolbarAPI: ToolbarAPI;
InlineToolbarAPI: InlineToolbarAPI;
MouseSelection: MouseSelection;
NotifierAPI: NotifierAPI;
}