Mouse selection (#574)

* Mouse selection
This commit is contained in:
horoyami 2019-02-19 11:49:19 +03:00 committed by GitHub
parent e4b0ca9b71
commit beeeef0914
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 631 additions and 120 deletions

File diff suppressed because one or more lines are too long

10
dist/codex-editor.js vendored

File diff suppressed because one or more lines are too long

View file

@ -76,32 +76,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
regenerator-runtime
MIT
@babel/register
MIT
MIT License
Copyright (c) 2014-2018 Sebastian McKenzie <sebmck@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
codex-notifier
MIT
MIT License
@ -127,6 +101,30 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
css-loader
MIT
Copyright JS Foundation and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
html-janitor
Apache License
Version 2.0, January 2004
@ -380,13 +378,15 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
css-loader
@babel/register
MIT
Copyright JS Foundation and other contributors
MIT License
Copyright (c) 2014-2018 Sebastian McKenzie <sebmck@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
@ -395,10 +395,10 @@ the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -1,5 +1,9 @@
# Changelog
### 2.9.0
- `Imporvements` - Selection with the mouse is available
### 2.8.1
- `Fix` *Caret* - Fix "History back" call on backspace in Firefox

View file

@ -1,6 +1,6 @@
{
"name": "codex.editor",
"version": "2.8.1",
"version": "2.9.0",
"description": "CodeX Editor. Native JS, based on API and Open Source",
"main": "dist/codex-editor.js",
"types": "./types/index.d.ts",

View file

@ -249,6 +249,7 @@ export default class Core {
'DragNDrop',
'ModificationsObserver',
'BlockSelection',
'RectangleSelection',
];
await modulesToPrepare.reduce(

View file

@ -1,6 +1,6 @@
/**
* @class BlockSelection
* @classdesc Manages Block selection with shortcut CMD+A and with mouse
* @classdesc Manages Block selection with shortcut CMD+A
*
* @module BlockSelection
* @version 1.0.0
@ -10,6 +10,7 @@ import _ from '../utils';
import $ from '../dom';
import SelectionUtils from '../selection';
import Block from '../block';
export default class BlockSelection extends Module {
@ -44,6 +45,36 @@ export default class BlockSelection extends Module {
};
}
/**
* 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);
}
/**
* Flag used to define block selection
* First CMD+A defines it as true and then second CMD+A selects all Blocks
@ -64,43 +95,13 @@ export default class BlockSelection extends Module {
*/
private selection: SelectionUtils;
/**
* 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);
}
/**
* Module Preparation
* Registers Shortcuts CMD+A and CMD+C
* to select all and copy them
*/
public prepare(): void {
const { Shortcuts } = this.Editor;
const {Shortcuts} = this.Editor;
/** Selection shortcut */
Shortcuts.add({
@ -113,6 +114,24 @@ export default class BlockSelection extends Module {
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
*/
@ -120,12 +139,13 @@ export default class BlockSelection extends Module {
this.needToSelectAll = false;
this.nativeInputSelected = false;
if (!this.anyBlockSelected) {
if (!this.anyBlockSelected || this.Editor.RectangleSelection.isRectActivated()) {
this.Editor.RectangleSelection.clearSelection();
return;
}
/**
* restore selection when Block is already selected
* Restore selection when Block is already selected
* but someone tries to write something.
*/
if (restoreSelection) {
@ -140,11 +160,11 @@ export default class BlockSelection extends Module {
* Reduce each Block and copy its content
*/
public copySelectedBlocks(): void {
const { BlockManager, Sanitizer } = this.Editor;
const {BlockManager, Sanitizer} = this.Editor;
const fakeClipboard = $.make('div');
BlockManager.blocks.filter( (block) => block.selected )
.forEach( (block) => {
BlockManager.blocks.filter((block) => block.selected)
.forEach((block) => {
/**
* Make <p> tag that holds clean HTML
*/
@ -158,6 +178,34 @@ export default class BlockSelection extends Module {
_.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;
}
/**
* First CMD+A Selects current focused blocks,
* and consequent second CMD+A keypress selects all blocks
@ -165,6 +213,8 @@ export default class BlockSelection extends Module {
* @param {keydown} event
*/
private handleCommandA(event): void {
this.Editor.RectangleSelection.clearSelection();
/** allow default selection on native inputs */
if ($.isNativeInput(event.target) && !this.nativeInputSelected) {
this.nativeInputSelected = true;
@ -190,32 +240,4 @@ export default class BlockSelection extends Module {
private selectAllBlocks() {
this.allBlocksSelected = true;
}
/**
* select Block
* @param {number?} index - Block index according to the BlockManager's indexes
*/
private 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;
}
}

View file

@ -0,0 +1,433 @@
/**
* @class RectangleSelection
* @classdesc Manages Block selection with mouse
*
* @module RectangleSelection
* @version 1.0.0
*/
import Module from '../__module';
import $ from '../dom';
import SelectionUtils from '../selection';
import Block from '../block';
import UI from './ui';
export default class RectangleSelection extends Module {
/**
* CSS classes for the Block
* @return {{wrapper: string, content: string}}
*/
static get CSS() {
return {
overlay: 'codex-editor-overlay',
overlayContainer: 'codex-editor-overlay__container',
rect: 'codex-editor-overlay__rectangle',
topScrollZone: 'codex-editor-overlay__scroll-zone--top',
bottomScrollZone: 'codex-editor-overlay__scroll-zone--bottom'
};
}
/**
* Using the selection rectangle
* @type {boolean}
*/
private isRectSelectionActivated: boolean = false;
/**
* Speed of Scrolling
*/
private readonly SCROLL_SPEED: number = 3;
/**
* Height of scroll zone on boundary of screen
*/
private readonly HEIGHT_OF_SCROLL_ZONE = 25;
/**
* Scroll zone type indicators
*/
private readonly BOTTOM_SCROLL_ZONE = 1;
private readonly TOP_SCROLL_ZONE = 2;
/**
* Mouse is clamped
*/
private mousedown: boolean = false;
/**
* Mouse is in scroll zone
*/
private inScrollZone: number | null = null;
/**
* Coords of rect
*/
private startX: number = 0;
private startY: number = 0;
private mouseX: number = 0;
private mouseY: number = 0;
/**
* Selected blocks
*/
private stackOfSelected: number[] = [];
/**
* Does the rectangle intersect blocks
*/
private rectCrossesBlocks: boolean;
/**
* Selection rectangle
*/
private overlayRectangle: HTMLDivElement;
/**
* Coords of redactor
*/
private left;
private top;
/**
* Module Preparation
* Creating rect and hang handlers
*/
public prepare(): void {
const {Listeners} = this.Editor;
const {overlayTopScrollZone, overlayBottomScrollZone, container, overlay} = this.genHTML();
Listeners.on(overlayBottomScrollZone, 'mouseenter', (event) => {
this.inScrollZone = this.BOTTOM_SCROLL_ZONE;
this.scrollVertical(this.SCROLL_SPEED);
});
Listeners.on(overlayTopScrollZone, 'mouseenter', (event) => {
this.inScrollZone = this.TOP_SCROLL_ZONE;
this.scrollVertical(-this.SCROLL_SPEED);
});
Listeners.on(overlayBottomScrollZone, 'mouseleave', (event) => {
this.inScrollZone = null;
});
Listeners.on(overlayTopScrollZone, 'mouseleave', (event) => {
this.inScrollZone = null;
});
Listeners.on(container, 'mousedown', (event: MouseEvent) => {
this.startSelection(event.pageX, event.pageY);
}, false);
Listeners.on(document.body, 'mousemove', (event) => {
this.changingRectangle(event);
}, false);
Listeners.on(document.body, 'mouseleave', (event) => {
this.clearSelection();
this.endSelection();
});
Listeners.on(window, 'scroll', (event) => {
this.changingRectangle(event);
}, false);
Listeners.on(document.body, 'mouseup', (event) => {
this.endSelection();
}, false);
}
/**
* Init rect params
* @param {number} pageX - X coord of mouse
* @param {number} pageY - Y coord of mouse
*/
public startSelection(pageX, pageY) {
this.Editor.BlockSelection.allBlocksSelected = false;
this.clearSelection();
this.stackOfSelected = [];
const elemWhereSelectionStart = document.elementFromPoint(pageX - window.pageXOffset, pageY - window.pageYOffset);
if (!(elemWhereSelectionStart.closest('.' + UI.CSS.editorWrapper) &&
!elemWhereSelectionStart.closest('.' + Block.CSS.content))) {
return;
}
this.mousedown = true;
this.startX = pageX;
this.startY = pageY;
const container = document.querySelector('.' + UI.CSS.editorWrapper);
}
/**
* Clear all params to end selection
*/
public endSelection() {
this.mousedown = false;
this.startX = 0;
this.startY = 0;
this.overlayRectangle.style.display = 'none';
}
/**
* is RectSelection Activated
*/
public isRectActivated() {
return this.isRectSelectionActivated;
}
/**
* Mark that selection is end
*/
public clearSelection() {
this.isRectSelectionActivated = false;
}
private genHTML() {
const container = document.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, {});
const overlayTopScrollZone = $.make('div', RectangleSelection.CSS.topScrollZone, {});
const overlayBottomScrollZone = $.make('div', RectangleSelection.CSS.bottomScrollZone, {});
overlayContainer.appendChild(overlayRectangle);
overlay.appendChild(overlayContainer);
document.body.appendChild(overlayTopScrollZone);
document.body.appendChild(overlayBottomScrollZone);
container.appendChild(overlay);
this.overlayRectangle = overlayRectangle as HTMLDivElement;
return {
overlayBottomScrollZone,
overlayTopScrollZone,
container,
overlay,
};
}
/**
* Activates scrolling if blockSelection is active and mouse is in scroll zone
* @param {number} speed - speed of scrolling
*/
private scrollVertical(speed) {
if (!(this.inScrollZone && this.mousedown)) {
return;
}
this.mouseY += speed;
window.scrollBy(0, speed);
setTimeout(() => {
this.scrollVertical(speed);
}, 0);
}
/**
* Handles the change in the rectangle and its effect
* @param {MouseEvent} event
*/
private changingRectangle(event) {
if (!this.mousedown) {
return;
}
if (event.pageY !== undefined) {
this.mouseX = event.pageX;
this.mouseY = event.pageY;
}
const {rightPos, leftPos, index} = this.genInfoForMouseSelection();
// There is not new block in selection
const rectIsOnRighSideOfredactor = this.startX > rightPos && this.mouseX > rightPos;
const rectISOnLeftSideOfRedactor = this.startX < leftPos && this.mouseX < leftPos;
this.rectCrossesBlocks = !(rectIsOnRighSideOfredactor || rectISOnLeftSideOfRedactor);
if (!this.isRectSelectionActivated) {
this.rectCrossesBlocks = false;
this.isRectSelectionActivated = true;
this.shrinkRectangleToPoint();
this.overlayRectangle.style.display = 'block';
}
this.updateRectangleSize();
if (index === undefined) {
return;
}
this.trySelectNextBlock(index);
// For case, when rect is out from blocks
this.inverseSelection();
SelectionUtils.get().removeAllRanges();
event.preventDefault();
}
/**
* Shrink rect to singular point
*/
private shrinkRectangleToPoint() {
this.overlayRectangle.style.left = `${this.startX - window.pageXOffset}px`;
this.overlayRectangle.style.top = `${this.startY - window.pageYOffset}px`;
this.overlayRectangle.style.bottom = `calc(100% - ${this.startY - window.pageYOffset}px`;
this.overlayRectangle.style.right = `calc(100% - ${this.startX - window.pageXOffset}px`;
}
/**
* Select or unselect all of blocks in array if rect is out or in selectable area
*/
private inverseSelection() {
const firstBlockInStack = this.Editor.BlockManager.getBlockByIndex(this.stackOfSelected[0]);
const isSelecteMode = firstBlockInStack.selected;
if (this.rectCrossesBlocks && !isSelecteMode) {
for (let i = 0; i < this.stackOfSelected.length; i++) {
this.Editor.BlockSelection.selectBlockByIndex(this.stackOfSelected[i]);
}
}
if (!this.rectCrossesBlocks && isSelecteMode) {
for (let i = 0; i < this.stackOfSelected.length; i++) {
this.Editor.BlockSelection.unSelectBlockByIndex(this.stackOfSelected[i]);
}
}
}
/**
* Updates size of rectangle
*/
private updateRectangleSize() {
// Depending on the position of the mouse relative to the starting point,
// change this.e distance from the desired edge of the screen*/
if (this.mouseY >= this.startY) {
this.overlayRectangle.style.top = `${this.startY - window.pageYOffset}px`;
this.overlayRectangle.style.bottom = `calc(100% - ${this.mouseY - window.pageYOffset}px`;
} else {
this.overlayRectangle.style.bottom = `calc(100% - ${this.startY - window.pageYOffset}px`;
this.overlayRectangle.style.top = `${this.mouseY - window.pageYOffset}px`;
}
if (this.mouseX >= this.startX) {
this.overlayRectangle.style.left = `${this.startX - window.pageXOffset}px`;
this.overlayRectangle.style.right = `calc(100% - ${this.mouseX - window.pageXOffset}px`;
} else {
this.overlayRectangle.style.right = `calc(100% - ${this.startX - window.pageXOffset}px`;
this.overlayRectangle.style.left = `${this.mouseX - window.pageXOffset}px`;
}
}
/**
* Collects information needed to determine the behavior of the rectangle
* @return {number} index - index next Block, leftPos - start of left border of Block, rightPos - right border
*/
private genInfoForMouseSelection() {
const widthOfRedactor = document.body.offsetWidth;
const centerOfRedactor = widthOfRedactor / 2;
const Y = this.getHorizontalMousePosition();
const elementUnderMouse = document.elementFromPoint(centerOfRedactor, Y);
const blockInCurrentPos = this.Editor.BlockManager.getBlockByChildNode(elementUnderMouse);
let index;
if (blockInCurrentPos !== undefined) {
index = this.Editor.BlockManager.blocks.findIndex((block) => block.holder === blockInCurrentPos.holder);
}
const contentElement = this.Editor.BlockManager.lastBlock.holder.querySelector('.' + Block.CSS.content);
const centerOfBlock = Number.parseInt(window.getComputedStyle(contentElement).width, 10) / 2;
const leftPos = centerOfRedactor - centerOfBlock;
const rightPos = centerOfRedactor + centerOfBlock;
return {
index,
leftPos,
rightPos,
};
}
/**
* Get mouse Y coord with accounting Scroll zone
*/
private getHorizontalMousePosition() {
let value = this.mouseY - window.pageYOffset;
// To look at the item below the zone
if (this.inScrollZone === this.TOP_SCROLL_ZONE) {
value += this.HEIGHT_OF_SCROLL_ZONE;
}
if (this.inScrollZone === this.BOTTOM_SCROLL_ZONE) {
value -= this.HEIGHT_OF_SCROLL_ZONE;
}
return value;
}
/**
* Select block with index index
* @param index - index of block in redactor
*/
private addBlockInSelection(index) {
if (this.rectCrossesBlocks) {
this.Editor.BlockSelection.selectBlockByIndex(index);
}
this.stackOfSelected.push(index);
}
/**
* Adds a block to the selection and determines which blocks should be selected
* @param {object} index - index of new block in the reactor
*/
private trySelectNextBlock(index) {
const sameBlock = this.stackOfSelected[this.stackOfSelected.length - 1] === index;
const sizeStack = this.stackOfSelected.length;
const down = 1, up = -1, undef = 0;
if (sameBlock) {
return;
}
const blockNumbersIncrease = this.stackOfSelected[sizeStack - 1] - this.stackOfSelected[sizeStack - 2] > 0;
const direction = sizeStack <= 1 ? undef : blockNumbersIncrease ? down : up;
const selectionInDownDurection = index > this.stackOfSelected[sizeStack - 1] && direction === down;
const selectionInUpDirection = index < this.stackOfSelected[sizeStack - 1] && direction === up;
const generalSelection = selectionInDownDurection || selectionInUpDirection || direction === undef;
const reduction = !generalSelection;
// When the selection is too fast, some blocks do not have time to be noticed. Fix it.
if (!reduction && (index > this.stackOfSelected[sizeStack - 1] || this.stackOfSelected[sizeStack - 1] === undefined)) {
let i = this.stackOfSelected[sizeStack - 1] + 1 || index;
for (i; i <= index; i++) {
this.addBlockInSelection(i);
}
return;
}
// for both directions
if (!reduction && (index < this.stackOfSelected[sizeStack - 1])) {
for (let i = this.stackOfSelected[sizeStack - 1] - 1; i >= index; i--) {
this.addBlockInSelection(i);
}
return;
}
if (!reduction) {
return;
}
let i = sizeStack - 1;
let cmp;
// cmp for different directions
if (index > this.stackOfSelected[sizeStack - 1]) {
cmp = () => index > this.stackOfSelected[i];
} else {
cmp = () => index < this.stackOfSelected[i];
}
// Remove blocks missed due to speed.
// cmp checks if we have removed all the necessary blocks
while (cmp()) {
if (this.rectCrossesBlocks) {
this.Editor.BlockSelection.unSelectBlockByIndex(this.stackOfSelected[i]);
}
this.stackOfSelected.pop();
i--;
}
return;
}
}

View file

@ -38,7 +38,7 @@ export default class UI extends Module {
* CodeX Editor UI CSS class names
* @return {{editorWrapper: string, editorZone: string}}
*/
private get CSS(): {
public static get CSS(): {
editorWrapper: string, editorWrapperNarrow: string, editorZone: string, editorZoneHidden: string,
editorLoader: string,
} {
@ -60,7 +60,7 @@ export default class UI extends Module {
/**
* HTML Elements used for UI
*/
public nodes: {[key: string]: HTMLElement} = {
public nodes: { [key: string]: HTMLElement } = {
holder: null,
wrapper: null,
redactor: null,
@ -70,9 +70,9 @@ export default class UI extends Module {
* Adds loader to editor while content is not ready
*/
public addLoader(): void {
this.nodes.loader = $.make('div', this.CSS.editorLoader);
this.nodes.loader = $.make('div', UI.CSS.editorLoader);
this.nodes.wrapper.prepend(this.nodes.loader);
this.nodes.redactor.classList.add(this.CSS.editorZoneHidden);
this.nodes.redactor.classList.add(UI.CSS.editorZoneHidden);
}
/**
@ -80,7 +80,7 @@ export default class UI extends Module {
*/
public removeLoader(): void {
this.nodes.loader.remove();
this.nodes.redactor.classList.remove(this.CSS.editorZoneHidden);
this.nodes.redactor.classList.remove(UI.CSS.editorZoneHidden);
}
/**
@ -142,14 +142,14 @@ export default class UI extends Module {
/**
* Create and save main UI elements
*/
this.nodes.wrapper = $.make('div', this.CSS.editorWrapper);
this.nodes.redactor = $.make('div', this.CSS.editorZone);
this.nodes.wrapper = $.make('div', UI.CSS.editorWrapper);
this.nodes.redactor = $.make('div', UI.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(this.CSS.editorWrapperNarrow);
this.nodes.wrapper.classList.add(UI.CSS.editorWrapperNarrow);
}
this.nodes.wrapper.appendChild(this.nodes.redactor);
@ -189,8 +189,8 @@ export default class UI extends Module {
(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), 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), false);
}
/**
@ -213,7 +213,7 @@ export default class UI extends Module {
* @param {KeyboardEvent} event
*/
private defaultBehaviour(event: KeyboardEvent): void {
const keyDownOnEditor = (event.target as HTMLElement).closest(`.${this.CSS.editorWrapper}`);
const keyDownOnEditor = (event.target as HTMLElement).closest(`.${UI.CSS.editorWrapper}`);
const {currentBlock} = this.Editor.BlockManager;
const isMetaKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
@ -287,7 +287,7 @@ export default class UI extends Module {
*/
const target = event.target as HTMLElement;
const clickedOnInlineToolbarButton = target.closest(`.${this.Editor.InlineToolbar.CSS.inlineToolbar}`);
const clickedInsideofEditor = target.closest(`.${this.CSS.editorWrapper}`);
const clickedInsideofEditor = target.closest(`.${UI.CSS.editorWrapper}`);
/** Clear highlightings and pointer on BlockManager */
if (!clickedInsideofEditor && !Selection.isAtEditor) {
@ -361,9 +361,11 @@ export default class UI extends Module {
this.Editor.BlockManager.highlightCurrentNode();
} catch (e) {
/**
* If clicked outside first-level Blocks, set Caret to the last empty Block
* If clicked outside first-level Blocks and it is not RectSelection, set Caret to the last empty Block
*/
this.Editor.Caret.setToTheLastBlock();
if (!this.Editor.RectangleSelection.isRectActivated()) {
this.Editor.Caret.setToTheLastBlock();
}
}
event.stopImmediatePropagation();

View file

@ -63,6 +63,52 @@
opacity: 0.001;
}
&-overlay {
position: fixed;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 999;
pointer-events: none;
overflow: hidden;
&__container {
position: relative;
pointer-events: auto;
z-index: 0;
}
&__rectangle {
position: absolute;
pointer-events: none;
background-color: rgba(46, 170, 220, 0.2);
border: 1px solid transparent;
}
&__scroll-zone {
&--top {
top: 0;
width: 100%;
height: 30px;
position: fixed;
pointer-events: auto;
z-index: 10500;
}
&--bottom {
bottom: 0;
width: 100%;
height: 30px;
position: fixed;
pointer-events: auto;
z-index: 10500;
}
}
}
svg {
fill: currentColor;
vertical-align: middle;

View file

@ -29,11 +29,13 @@ import NotifierAPI from '../components/modules/api/notifier';
import SaverAPI from '../components/modules/api/saver';
import Saver from '../components/modules/saver';
import BlockSelection from '../components/modules/blockSelection';
import RectangleSelection from '../components/modules/RectangleSelection';
export interface EditorModules {
UI: UI;
BlockEvents: BlockEvents;
BlockSelection: BlockSelection;
RectangleSelection: RectangleSelection;
Listeners: Listeners;
Toolbar: Toolbar;
InlineToolbar: InlineToolbar;