mirror of
https://github.com/codex-team/editor.js
synced 2024-06-08 00:42:31 +02:00
parent
e4b0ca9b71
commit
beeeef0914
1
build/codex-editor.js.map
Normal file
1
build/codex-editor.js.map
Normal file
File diff suppressed because one or more lines are too long
10
dist/codex-editor.js
vendored
10
dist/codex-editor.js
vendored
File diff suppressed because one or more lines are too long
70
dist/codex-editor.licenses.txt
vendored
70
dist/codex-editor.licenses.txt
vendored
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -249,6 +249,7 @@ export default class Core {
|
|||
'DragNDrop',
|
||||
'ModificationsObserver',
|
||||
'BlockSelection',
|
||||
'RectangleSelection',
|
||||
];
|
||||
|
||||
await modulesToPrepare.reduce(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
433
src/components/modules/rectangleSelection.ts
Normal file
433
src/components/modules/rectangleSelection.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
2
src/types-internal/editor-modules.d.ts
vendored
2
src/types-internal/editor-modules.d.ts
vendored
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue